Implements reset password modal under login modal.

This commit is contained in:
Dominik Picheta 2018-05-21 15:39:47 +01:00
commit c0ecf782c8
4 changed files with 129 additions and 27 deletions

View file

@ -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())

View file

@ -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 =

View file

@ -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)

View file

@ -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"