Version 3 #40
|
@ -68,10 +68,7 @@ let main args =
|
||||||
let _ = app.UseAuthorization ()
|
let _ = app.UseAuthorization ()
|
||||||
let _ = app.UseSession ()
|
let _ = app.UseSession ()
|
||||||
let _ = app.UseGiraffeErrorHandler Handlers.Error.unexpectedError
|
let _ = app.UseGiraffeErrorHandler Handlers.Error.unexpectedError
|
||||||
let _ = app.UseEndpoints (
|
let _ = app.UseEndpoints (fun e -> e.MapGiraffeEndpoints Handlers.allEndpoints |> ignore)
|
||||||
fun e ->
|
|
||||||
e.MapGiraffeEndpoints Handlers.allEndpoints
|
|
||||||
e.MapFallbackToFile "index.html" |> ignore)
|
|
||||||
|
|
||||||
app.Run ()
|
app.Run ()
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,6 @@ module Helpers =
|
||||||
open Microsoft.AspNetCore.Antiforgery
|
open Microsoft.AspNetCore.Antiforgery
|
||||||
open Microsoft.Extensions.Configuration
|
open Microsoft.Extensions.Configuration
|
||||||
open Microsoft.Extensions.DependencyInjection
|
open Microsoft.Extensions.DependencyInjection
|
||||||
open Microsoft.Extensions.Options
|
|
||||||
|
|
||||||
/// Get the NodaTime clock from the request context
|
/// Get the NodaTime clock from the request context
|
||||||
let now (ctx : HttpContext) = ctx.GetService<IClock>().GetCurrentInstant ()
|
let now (ctx : HttpContext) = ctx.GetService<IClock>().GetCurrentInstant ()
|
||||||
|
@ -437,11 +436,37 @@ module Citizen =
|
||||||
let resetPassword token : HttpHandler = fun next ctx -> task {
|
let resetPassword token : HttpHandler = fun next ctx -> task {
|
||||||
match! Citizens.trySecurityByToken token with
|
match! Citizens.trySecurityByToken token with
|
||||||
| Some security ->
|
| Some security ->
|
||||||
// TODO: create form and page
|
return!
|
||||||
return! Home.home |> render "TODO" next ctx
|
Citizen.resetPassword { Id = CitizenId.toString security.Id; Token = token; Password = "" } (csrf ctx)
|
||||||
|
|> render "Reset Password" next ctx
|
||||||
| None -> return! Error.notFound 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
|
// POST: /citizen/save-account
|
||||||
let saveAccount : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
|
let saveAccount : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
|
||||||
let! theForm = ctx.BindFormAsync<AccountProfileForm> ()
|
let! theForm = ctx.BindFormAsync<AccountProfileForm> ()
|
||||||
|
@ -870,6 +895,7 @@ let allEndpoints = [
|
||||||
route "/forgot-password" Citizen.doForgotPassword
|
route "/forgot-password" Citizen.doForgotPassword
|
||||||
route "/log-on" Citizen.doLogOn
|
route "/log-on" Citizen.doLogOn
|
||||||
route "/register" Citizen.doRegistration
|
route "/register" Citizen.doRegistration
|
||||||
|
route "/reset-password" Citizen.doResetPassword
|
||||||
route "/save-account" Citizen.saveAccount
|
route "/save-account" Citizen.saveAccount
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -309,3 +309,17 @@ module RegisterViewModel =
|
||||||
Question2Index = 0
|
Question2Index = 0
|
||||||
Question2Answer = ""
|
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" ] [
|
div [ _class "col-12 col-md-6 offset-md-3" ] [
|
||||||
textBox [ _type "email"; _autofocus ] (nameof m.Email) m.Email "E-mail Address" true
|
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
|
/// The page displayed after a forgotten / reset request has been processed
|
||||||
let forgotPasswordSent (m : ForgotPasswordForm) =
|
let forgotPasswordSent (m : ForgotPasswordForm) =
|
||||||
pageWithTitle "Reset Request Processed" [
|
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."
|
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