Implements reset password modal under login modal.
This commit is contained in:
parent
595b0ea086
commit
c0ecf782c8
4 changed files with 129 additions and 27 deletions
|
|
@ -87,10 +87,21 @@ proc getGravatarUrl(email: string, size = 80): string =
|
|||
# -----------------------------------------------------------------------------
|
||||
template `||`(x: untyped): untyped = (if not isNil(x): x else: "")
|
||||
|
||||
proc validateCaptcha(recaptchaResp, ip: string) {.async.} =
|
||||
# captcha validation:
|
||||
if config.recaptchaSecretKey.len > 0:
|
||||
var verifyFut = captcha.verify(recaptchaResp, ip)
|
||||
yield verifyFut
|
||||
if verifyFut.failed:
|
||||
raise newForumError(
|
||||
"Invalid recaptcha answer", @[]
|
||||
)
|
||||
|
||||
proc resetPassword(
|
||||
proc sendResetPassword(
|
||||
c: TForumData,
|
||||
email: string
|
||||
email: string,
|
||||
recaptchaResp: string,
|
||||
userIp: string
|
||||
) {.async.} =
|
||||
# Gather some extra information to determine ident hash.
|
||||
let row = db.getRow(
|
||||
|
|
@ -101,7 +112,9 @@ proc resetPassword(
|
|||
email, email
|
||||
)
|
||||
if row[0] == "":
|
||||
raise newForumError("Email not found", @["email"])
|
||||
raise newForumError("Email or username not found", @["email"])
|
||||
|
||||
await validateCaptcha(recaptchaResp, userIp)
|
||||
|
||||
await sendSecureEmail(
|
||||
mailer,
|
||||
|
|
@ -560,14 +573,7 @@ proc executeRegister(c: TForumData, name, pass, antibot, userIp,
|
|||
if pass.len < 4:
|
||||
raise newForumError("Please choose a longer password", @["password"])
|
||||
|
||||
# captcha validation:
|
||||
if config.recaptchaSecretKey.len > 0:
|
||||
var verifyFut = captcha.verify(antibot, userIp)
|
||||
yield verifyFut
|
||||
if verifyFut.failed:
|
||||
raise newForumError(
|
||||
"Invalid recaptcha answer", @[]
|
||||
)
|
||||
await validateCaptcha(antibot, userIp)
|
||||
|
||||
# perform registration:
|
||||
var salt = makeSalt()
|
||||
|
|
@ -1197,17 +1203,28 @@ routes:
|
|||
|
||||
post "/sendResetPassword":
|
||||
createTFD()
|
||||
if not c.loggedIn():
|
||||
let err = PostError(
|
||||
errorFields: @[],
|
||||
message: "Not logged in."
|
||||
)
|
||||
resp Http401, $(%err), "application/json"
|
||||
|
||||
let formData = request.formData
|
||||
let recaptcha =
|
||||
if "g-recaptcha-response" in formData:
|
||||
formData["g-recaptcha-response"].body
|
||||
else:
|
||||
""
|
||||
|
||||
if not c.loggedIn():
|
||||
if not config.isDev:
|
||||
if "g-recaptcha-response" notin formData:
|
||||
let err = PostError(
|
||||
errorFields: @[],
|
||||
message: "Not logged in."
|
||||
)
|
||||
resp Http401, $(%err), "application/json"
|
||||
|
||||
cond "email" in formData
|
||||
try:
|
||||
await resetPassword(c, formData["email"].body)
|
||||
await sendResetPassword(
|
||||
c, formData["email"].body, recaptcha, request.host
|
||||
)
|
||||
resp Http200, "{}", "application/json"
|
||||
except ForumError:
|
||||
let exc = (ref ForumError)(getCurrentException())
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ when defined(js):
|
|||
text submessage
|
||||
|
||||
proc genFormField*(error: Option[PostError], name, label, typ: string,
|
||||
isLast: bool): VNode =
|
||||
isLast: bool, placeholder=""): VNode =
|
||||
let hasError =
|
||||
not error.isNone and (
|
||||
name in error.get().errorFields or
|
||||
|
|
@ -63,13 +63,14 @@ when defined(js):
|
|||
tdiv(class=class({"has-error": hasError}, "form-group")):
|
||||
label(class="form-label", `for`=name):
|
||||
text label
|
||||
input(class="form-input", `type`=typ, name=name)
|
||||
input(class="form-input", `type`=typ, name=name,
|
||||
placeholder=placeholder)
|
||||
|
||||
if not error.isNone:
|
||||
let e = error.get()
|
||||
if (e.errorFields.len == 1 and e.errorFields[0] == name) or
|
||||
(isLast and e.errorFields.len == 0):
|
||||
p(class="form-input-hint"):
|
||||
span(class="form-input-hint"):
|
||||
text e.message
|
||||
|
||||
template postFinished*(onSuccess: untyped): untyped =
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ when defined(js):
|
|||
include karax/prelude
|
||||
import karax / [kajax, kdom]
|
||||
|
||||
import error
|
||||
import error, resetpassword
|
||||
import karaxutils
|
||||
|
||||
type
|
||||
|
|
@ -15,6 +15,7 @@ when defined(js):
|
|||
onLogIn: proc ()
|
||||
onSignUp: proc ()
|
||||
error: Option[PostError]
|
||||
resetPasswordModal: ResetPasswordModal
|
||||
|
||||
proc onLogInPost(httpStatus: int, response: kstring, state: LoginModal) =
|
||||
postFinished:
|
||||
|
|
@ -40,7 +41,8 @@ when defined(js):
|
|||
LoginModal(
|
||||
shown: false,
|
||||
onLogIn: onLogIn,
|
||||
onSignUp: onSignUp
|
||||
onSignUp: onSignUp,
|
||||
resetPasswordModal: newResetPasswordModal()
|
||||
)
|
||||
|
||||
proc show*(state: LoginModal) =
|
||||
|
|
@ -52,7 +54,7 @@ when defined(js):
|
|||
onLogInClick(e, n, state)
|
||||
|
||||
proc render*(state: LoginModal): VNode =
|
||||
result = buildHtml():
|
||||
result = buildHtml(tdiv()):
|
||||
tdiv(class=class({"active": state.shown}, "modal modal-sm"),
|
||||
id="login-modal"):
|
||||
a(href="", class="modal-overlay", "aria-label"="close",
|
||||
|
|
@ -76,7 +78,8 @@ when defined(js):
|
|||
"password",
|
||||
true
|
||||
)
|
||||
a(href="#reset-password-modal"):
|
||||
a(href="", onClick=(e: Event, n: VNode) =>
|
||||
(state.resetPasswordModal.show(); e.preventDefault())):
|
||||
text "Reset your password"
|
||||
tdiv(class="modal-footer"):
|
||||
button(class=class(
|
||||
|
|
@ -88,4 +91,6 @@ when defined(js):
|
|||
button(class="btn",
|
||||
onClick=(ev: Event, n: VNode) =>
|
||||
(state.onSignUp(); state.shown = false)):
|
||||
text "Create account"
|
||||
text "Create account"
|
||||
|
||||
render(state.resetPasswordModal)
|
||||
|
|
@ -64,4 +64,83 @@ when defined(js):
|
|||
),
|
||||
onClick=(ev: Event, n: VNode) =>
|
||||
(onSetClick(ev, n, state))):
|
||||
text "Set password"
|
||||
text "Set password"
|
||||
|
||||
|
||||
type
|
||||
ResetPasswordModal* = ref object
|
||||
shown: bool
|
||||
loading: bool
|
||||
error: Option[PostError]
|
||||
sent: bool
|
||||
|
||||
proc onPost(httpStatus: int, response: kstring, state: ResetPasswordModal) =
|
||||
postFinished:
|
||||
state.sent = true
|
||||
|
||||
proc onClick(ev: Event, n: VNode, state: ResetPasswordModal) =
|
||||
state.loading = true
|
||||
state.error = none[PostError]()
|
||||
|
||||
let uri = makeUri("sendResetPassword")
|
||||
let form = dom.document.getElementById("resetpassword-form")
|
||||
# TODO: This is a hack, karax should support this.
|
||||
let formData = newFormData(form)
|
||||
ajaxPost(uri, @[], cast[cstring](formData),
|
||||
(s: int, r: kstring) => onPost(s, r, state))
|
||||
|
||||
proc onClose(ev: Event, n: VNode, state: ResetPasswordModal) =
|
||||
state.shown = false
|
||||
ev.preventDefault()
|
||||
|
||||
proc newResetPasswordModal*(): ResetPasswordModal =
|
||||
ResetPasswordModal(
|
||||
shown: false
|
||||
)
|
||||
|
||||
proc show*(state: ResetPasswordModal) =
|
||||
state.shown = true
|
||||
|
||||
proc onKeyDown(e: Event, n: VNode, state: ResetPasswordModal) =
|
||||
let event = cast[KeyboardEvent](e)
|
||||
if event.key == "Enter":
|
||||
onClick(e, n, state)
|
||||
|
||||
proc render*(state: ResetPasswordModal): VNode =
|
||||
result = buildHtml():
|
||||
tdiv(class=class({"active": state.shown}, "modal"),
|
||||
id="resetpassword-modal"):
|
||||
a(href="", class="modal-overlay", "aria-label"="close",
|
||||
onClick=(ev: Event, n: VNode) => onClose(ev, n, state))
|
||||
tdiv(class="modal-container"):
|
||||
tdiv(class="modal-header"):
|
||||
a(href="", class="btn btn-clear float-right",
|
||||
"aria-label"="close",
|
||||
onClick=(ev: Event, n: VNode) => onClose(ev, n, state))
|
||||
tdiv(class="modal-title h5"):
|
||||
text "Reset your password"
|
||||
tdiv(class="modal-body"):
|
||||
tdiv(class="content"):
|
||||
form(id="resetpassword-form",
|
||||
onKeyDown=(ev: Event, n: VNode) => onKeyDown(ev, n, state)):
|
||||
genFormField(
|
||||
state.error,
|
||||
"email",
|
||||
"Enter your email or username and we will send you a " &
|
||||
"password reset email.",
|
||||
"text",
|
||||
true,
|
||||
placeholder="Username or email"
|
||||
)
|
||||
tdiv(class="modal-footer"):
|
||||
if state.sent:
|
||||
span(class="text-success"):
|
||||
italic(class="fas fa-check-circle")
|
||||
text " Sent"
|
||||
else:
|
||||
button(class=class(
|
||||
{"loading": state.loading},
|
||||
"btn btn-primary"
|
||||
),
|
||||
onClick=(ev: Event, n: VNode) => onClick(ev, n, state)):
|
||||
text "Reset password"
|
||||
Loading…
Add table
Add a link
Reference in a new issue