Version 3 #40
|
@ -68,10 +68,7 @@ let main args =
|
|||
let _ = app.UseAuthorization ()
|
||||
let _ = app.UseSession ()
|
||||
let _ = app.UseGiraffeErrorHandler Handlers.Error.unexpectedError
|
||||
let _ = app.UseEndpoints (
|
||||
fun e ->
|
||||
e.MapGiraffeEndpoints Handlers.allEndpoints
|
||||
e.MapFallbackToFile "index.html" |> ignore)
|
||||
let _ = app.UseEndpoints (fun e -> e.MapGiraffeEndpoints Handlers.allEndpoints |> ignore)
|
||||
|
||||
app.Run ()
|
||||
|
||||
|
|
|
@ -63,7 +63,6 @@ module Helpers =
|
|||
open Microsoft.AspNetCore.Antiforgery
|
||||
open Microsoft.Extensions.Configuration
|
||||
open Microsoft.Extensions.DependencyInjection
|
||||
open Microsoft.Extensions.Options
|
||||
|
||||
/// Get the NodaTime clock from the request context
|
||||
let now (ctx : HttpContext) = ctx.GetService<IClock>().GetCurrentInstant ()
|
||||
|
@ -437,11 +436,37 @@ module Citizen =
|
|||
let resetPassword token : HttpHandler = fun next ctx -> task {
|
||||
match! Citizens.trySecurityByToken token with
|
||||
| Some security ->
|
||||
// TODO: create form and page
|
||||
return! Home.home |> render "TODO" next ctx
|
||||
return!
|
||||
Citizen.resetPassword { Id = CitizenId.toString security.Id; Token = token; Password = "" } (csrf ctx)
|
||||
|> render "Reset Password" next ctx
|
||||
| None -> return! Error.notFound next ctx
|
||||
}
|
||||
|
||||
// POST: /citizen/reset-password
|
||||
let doResetPassword : HttpHandler = validateCsrf >=> fun next ctx -> task {
|
||||
let! form = ctx.BindFormAsync<ResetPasswordForm> ()
|
||||
let errors = [
|
||||
if form.Id = "" then "Request invalid; please return to the link in your e-mail and try again"
|
||||
if form.Token = "" then "Request invalid; please return to the link in your e-mail and try again"
|
||||
if form.Password.Length < 8 then "Password too short"
|
||||
]
|
||||
if List.isEmpty errors then
|
||||
match! Citizens.trySecurityByToken form.Token with
|
||||
| Some security when security.Id = CitizenId.ofString form.Id ->
|
||||
match! Citizens.findById security.Id with
|
||||
| Some citizen ->
|
||||
do! Citizens.saveSecurityInfo { security with Token = None; TokenUsage = None; TokenExpires = None }
|
||||
do! Citizens.save { citizen with PasswordHash = Auth.Passwords.hash citizen form.Password }
|
||||
do! addSuccess "Password reset successfully; you may log on with your new credentials" ctx
|
||||
return! redirectToGet "/citizen/log-on" next ctx
|
||||
| None -> return! Error.notFound next ctx
|
||||
| Some _
|
||||
| None -> return! Error.notFound next ctx
|
||||
else
|
||||
do! addErrors errors ctx
|
||||
return! Citizen.resetPassword form (csrf ctx) |> render "Reset Password" next ctx
|
||||
}
|
||||
|
||||
// POST: /citizen/save-account
|
||||
let saveAccount : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
|
||||
let! theForm = ctx.BindFormAsync<AccountProfileForm> ()
|
||||
|
@ -870,6 +895,7 @@ let allEndpoints = [
|
|||
route "/forgot-password" Citizen.doForgotPassword
|
||||
route "/log-on" Citizen.doLogOn
|
||||
route "/register" Citizen.doRegistration
|
||||
route "/reset-password" Citizen.doResetPassword
|
||||
route "/save-account" Citizen.saveAccount
|
||||
]
|
||||
]
|
||||
|
|
|
@ -309,3 +309,17 @@ module RegisterViewModel =
|
|||
Question2Index = 0
|
||||
Question2Answer = ""
|
||||
}
|
||||
|
||||
|
||||
/// The form for a user resetting their password
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type ResetPasswordForm =
|
||||
{ /// The ID of the citizen whose password is being reset
|
||||
Id : string
|
||||
|
||||
/// The verification token for the password reset
|
||||
Token : string
|
||||
|
||||
/// The new password for the account
|
||||
Password : string
|
||||
}
|
||||
|
|
|
@ -255,7 +255,7 @@ let forgotPassword csrf =
|
|||
div [ _class "col-12 col-md-6 offset-md-3" ] [
|
||||
textBox [ _type "email"; _autofocus ] (nameof m.Email) m.Email "E-mail Address" true
|
||||
]
|
||||
div [ _class "col-12" ] [ submitButton "login" "Send Reset Link" ]
|
||||
div [ _class "col-12" ] [ submitButton "send-lock-outline" "Send Reset Link" ]
|
||||
]
|
||||
]
|
||||
|
||||
|
@ -263,7 +263,10 @@ let forgotPassword csrf =
|
|||
/// The page displayed after a forgotten / reset request has been processed
|
||||
let forgotPasswordSent (m : ForgotPasswordForm) =
|
||||
pageWithTitle "Reset Request Processed" [
|
||||
p [] [ txt "The reset link request has been processed; check your e-mail for further instructions." ]
|
||||
p [] [
|
||||
txt "The reset link request has been processed. If the e-mail address matched an account, further "
|
||||
txt "instructions were sent to that address."
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
|
@ -370,3 +373,23 @@ let resetCanceled wasCanceled =
|
|||
else txt "There was no active password reset request found; it may have already expired."
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
/// The password reset page
|
||||
let resetPassword (m : ResetPasswordForm) csrf =
|
||||
pageWithTitle "Reset Password" [
|
||||
p [] [ txt "Enter your new password in the fields below" ]
|
||||
form [ _class "row g-3"; _method "POST"; _action "/citizen/reset-password" ] [
|
||||
antiForgery csrf
|
||||
input [ _type "hidden"; _name (nameof m.Id); _value m.Id ]
|
||||
input [ _type "hidden"; _name (nameof m.Token); _value m.Token ]
|
||||
div [ _class "col-12 col-md-6 col-xl-4 offset-xl-2" ] [
|
||||
textBox [ _type "password"; _minlength "8"; _autofocus ] (nameof m.Password) "" "New Password" true
|
||||
]
|
||||
div [ _class "col-12 col-md-6 col-xl-4" ] [
|
||||
textBox [ _type "password"; _minlength "8" ] "ConfirmPassword" "" "Confirm New Password" true
|
||||
]
|
||||
div [ _class "col-12" ] [ submitButton "lock-reset" "Reset Password" ]
|
||||
jsOnLoad $"jjj.citizen.validatePasswords('{nameof m.Password}', 'ConfirmPassword', true)"
|
||||
]
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue
Block a user