Version 3 #40
|
@ -99,6 +99,14 @@ module Helpers =
|
||||||
|
|
||||||
// -- NEW --
|
// -- NEW --
|
||||||
|
|
||||||
|
/// Add a message to the response
|
||||||
|
let sendMessage (msg : string) : HttpHandler =
|
||||||
|
setHttpHeader "X-Message" msg
|
||||||
|
|
||||||
|
/// Add an error message to the response
|
||||||
|
let sendError (msg : string) : HttpHandler =
|
||||||
|
sendMessage $"ERROR|||{msg}"
|
||||||
|
|
||||||
/// Render a page-level view
|
/// Render a page-level view
|
||||||
let render pageTitle content : HttpHandler = fun _ ctx -> task {
|
let render pageTitle content : HttpHandler = fun _ ctx -> task {
|
||||||
let renderFunc = if ctx.Request.IsHtmx && not ctx.Request.IsHtmxRefresh then Layout.partial else Layout.full
|
let renderFunc = if ctx.Request.IsHtmx && not ctx.Request.IsHtmxRefresh then Layout.partial else Layout.full
|
||||||
|
@ -138,36 +146,67 @@ module Citizen =
|
||||||
seq {
|
seq {
|
||||||
for idx in 0..4 do
|
for idx in 0..4 do
|
||||||
let section = qs.GetSection(string idx)
|
let section = qs.GetSection(string idx)
|
||||||
yield section["Question"], section["Answer"]
|
yield section["Question"], (section["Answer"].ToLowerInvariant ())
|
||||||
}
|
}
|
||||||
|> Array.ofSeq
|
|> Array.ofSeq
|
||||||
challenges <- Some qAndA
|
challenges <- Some qAndA
|
||||||
qAndA
|
qAndA
|
||||||
|
|
||||||
|
// GET: /citizen/confirm/[token]
|
||||||
|
let confirm token = fun next ctx -> task {
|
||||||
|
let! isConfirmed = Citizens.confirmAccount token
|
||||||
|
return! render "Account Confirmation" (Citizen.confirmAccount isConfirmed) next ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: /citizen/deny/[token]
|
||||||
|
let deny token = fun next ctx -> task {
|
||||||
|
let! wasDeleted = Citizens.denyAccount token
|
||||||
|
return! render "Account Deletion" (Citizen.denyAccount wasDeleted) next ctx
|
||||||
|
}
|
||||||
|
|
||||||
// GET: /citizen/log-on
|
// GET: /citizen/log-on
|
||||||
let logOn : HttpHandler =
|
let logOn : HttpHandler =
|
||||||
render "Log On" (Citizen.logOn { ErrorMessage = None; Email = ""; Password = "" })
|
render "Log On" (Citizen.logOn { ErrorMessage = None; Email = ""; Password = "" })
|
||||||
|
|
||||||
// GET: /citizen/register
|
// GET: /citizen/register
|
||||||
let register : HttpHandler = fun next ctx -> task {
|
let register : HttpHandler = fun next ctx ->
|
||||||
// Get two different indexes for NA-knowledge challenge questions
|
// Get two different indexes for NA-knowledge challenge questions
|
||||||
let q1Index = System.Random.Shared.Next(0, 5)
|
let q1Index = System.Random.Shared.Next(0, 5)
|
||||||
let mutable q2Index = System.Random.Shared.Next(0, 5)
|
let mutable q2Index = System.Random.Shared.Next(0, 5)
|
||||||
while q1Index = q2Index do
|
while q1Index = q2Index do
|
||||||
q2Index <- System.Random.Shared.Next(0, 5)
|
q2Index <- System.Random.Shared.Next(0, 5)
|
||||||
let qAndA = Support.questions ctx
|
let qAndA = Support.questions ctx
|
||||||
return! render "Register"
|
render "Register"
|
||||||
(Citizen.register (fst qAndA[q1Index]) (fst qAndA[q2Index])
|
(Citizen.register (fst qAndA[q1Index]) (fst qAndA[q2Index])
|
||||||
{ RegisterViewModel.empty with Question1Index = q1Index; Question2Index = q2Index }) next ctx
|
{ RegisterViewModel.empty with Question1Index = q1Index; Question2Index = q2Index }) next ctx
|
||||||
}
|
|
||||||
|
|
||||||
// POST: /citizen/register
|
// POST: /citizen/register
|
||||||
let doRegistration : HttpHandler = fun next ctx -> task {
|
let doRegistration : HttpHandler = fun next ctx -> task {
|
||||||
let! form = ctx.BindFormAsync<RegisterViewModel> ()
|
let! form = ctx.BindFormAsync<RegisterViewModel> ()
|
||||||
// FIXME: stopped here; add validation
|
let qAndA = Support.questions ctx
|
||||||
if form.Password.Length < 8 then
|
let mutable badForm = false
|
||||||
return! RequestErrors.BAD_REQUEST "Password out of range" next ctx
|
let errors = [
|
||||||
else
|
if form.FirstName.Length < 1 then "First name is required"
|
||||||
|
if form.LastName.Length < 1 then "Last name is required"
|
||||||
|
if form.Email.Length < 1 then "E-mail address is required"
|
||||||
|
if form.Password.Length < 8 then "Password is too short"
|
||||||
|
if form.Question1Index < 0 || form.Question1Index > 4
|
||||||
|
|| form.Question2Index < 0 || form.Question2Index > 4
|
||||||
|
|| form.Question1Index = form.Question2Index then
|
||||||
|
badForm <- true
|
||||||
|
else if (snd qAndA[form.Question1Index]) <> (form.Question1Answer.Trim().ToLowerInvariant ())
|
||||||
|
|| (snd qAndA[form.Question2Index]) <> (form.Question2Answer.Trim().ToLowerInvariant ()) then
|
||||||
|
"Question answers are incorrect"
|
||||||
|
]
|
||||||
|
let refreshPage () =
|
||||||
|
render "Register"
|
||||||
|
(Citizen.register (fst qAndA[form.Question1Index]) (fst qAndA[form.Question2Index])
|
||||||
|
{ form with Password = "" })
|
||||||
|
if badForm then
|
||||||
|
let handle = sendError "The form posted was invalid; please complete it again" >=> register
|
||||||
|
return! handle next ctx
|
||||||
|
else if List.isEmpty errors then
|
||||||
let now = now ctx
|
let now = now ctx
|
||||||
let noPass =
|
let noPass =
|
||||||
{ Citizen.empty with
|
{ Citizen.empty with
|
||||||
|
@ -191,12 +230,17 @@ module Citizen =
|
||||||
let! success = Citizens.register citizen security
|
let! success = Citizens.register citizen security
|
||||||
if success then
|
if success then
|
||||||
let! emailResponse = Email.sendAccountConfirmation citizen security
|
let! emailResponse = Email.sendAccountConfirmation citizen security
|
||||||
let logFac = logger ctx
|
let logFac = logger ctx
|
||||||
let log = logFac.CreateLogger "JobsJobsJobs.Api.Handlers.Citizen"
|
let log = logFac.CreateLogger "JobsJobsJobs.Handlers.Citizen"
|
||||||
log.LogInformation $"Confirmation e-mail for {citizen.Email} received {emailResponse}"
|
log.LogInformation $"Confirmation e-mail for {citizen.Email} received {emailResponse}"
|
||||||
return! ok next ctx
|
return! render "Registration Successful" Citizen.registered next ctx
|
||||||
else
|
else
|
||||||
return! RequestErrors.CONFLICT "" next ctx
|
return! (sendError "There is already an account registered to the e-mail address provided"
|
||||||
|
>=> refreshPage ()) next ctx
|
||||||
|
else
|
||||||
|
let errMsg = String.Join ("</li><li>", errors)
|
||||||
|
return! (sendError $"Please correct the following errors:<ul><li>{errMsg}</li></ul>" >=> refreshPage ())
|
||||||
|
next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handlers for /api/citizen routes
|
/// Handlers for /api/citizen routes
|
||||||
|
@ -596,8 +640,10 @@ let allEndpoints = [
|
||||||
GET_HEAD [ route "/" Home.home ]
|
GET_HEAD [ route "/" Home.home ]
|
||||||
subRoute "/citizen" [
|
subRoute "/citizen" [
|
||||||
GET_HEAD [
|
GET_HEAD [
|
||||||
route "/log-on" Citizen.logOn
|
routef "/confirm/%s" Citizen.confirm
|
||||||
route "/register" Citizen.register
|
routef "/deny/%s" Citizen.deny
|
||||||
|
route "/log-on" Citizen.logOn
|
||||||
|
route "/register" Citizen.register
|
||||||
]
|
]
|
||||||
POST [ route "/register" Citizen.doRegistration ]
|
POST [ route "/register" Citizen.doRegistration ]
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,6 +6,34 @@ open Giraffe.ViewEngine
|
||||||
open Giraffe.ViewEngine.Htmx
|
open Giraffe.ViewEngine.Htmx
|
||||||
open JobsJobsJobs.ViewModels
|
open JobsJobsJobs.ViewModels
|
||||||
|
|
||||||
|
/// The account confirmation page
|
||||||
|
let confirmAccount isConfirmed =
|
||||||
|
article [] [
|
||||||
|
h3 [ _class "pb-3" ] [ rawText "Account Confirmation" ]
|
||||||
|
p [] [
|
||||||
|
if isConfirmed then
|
||||||
|
rawText "Your account was confirmed successfully! You may "
|
||||||
|
a [ _href "/citizen/log-on" ] [ rawText "log on here" ]; rawText "."
|
||||||
|
else
|
||||||
|
rawText "The confirmation token did not match any pending accounts. Confirmation tokens are only valid "
|
||||||
|
rawText "for 3 days; if the token expired, you will need to re-register, which "
|
||||||
|
a [ _href "/citizen/register" ] [ rawText "you can do here" ]; rawText "."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
/// The account denial page
|
||||||
|
let denyAccount wasDeleted =
|
||||||
|
article [] [
|
||||||
|
h3 [ _class "pb-3" ] [ rawText "Account Deletion" ]
|
||||||
|
p [] [
|
||||||
|
if wasDeleted then
|
||||||
|
rawText "The account was deleted successfully; sorry for the trouble."
|
||||||
|
else
|
||||||
|
rawText "The confirmation token did not match any pending accounts; if this was an inadvertently "
|
||||||
|
rawText "created account, it has likely already been deleted."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
/// The log on page
|
/// The log on page
|
||||||
let logOn (m : LogOnViewModel) =
|
let logOn (m : LogOnViewModel) =
|
||||||
article [] [
|
article [] [
|
||||||
|
@ -153,3 +181,22 @@ let register q1 q2 (m : RegisterViewModel) =
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/// The confirmation page for user registration
|
||||||
|
let registered =
|
||||||
|
article [] [
|
||||||
|
h3 [ _class "pb-3" ] [ rawText "Registration Successful" ]
|
||||||
|
p [] [
|
||||||
|
rawText "You have been successfully registered with Jobs, Jobs, Jobs. Check your e-mail for a confirmation "
|
||||||
|
rawText "link; it will be valid for the next 72 hours (3 days). Once you confirm your account, you will be "
|
||||||
|
rawText "able to log on using the e-mail address and password you provided."
|
||||||
|
]
|
||||||
|
p [] [
|
||||||
|
rawText "If the account is not confirmed within the 72-hour window, it will be deleted, and you will need "
|
||||||
|
rawText "to register again."
|
||||||
|
]
|
||||||
|
p [] [
|
||||||
|
rawText "If you encounter issues, feel free to reach out to @danieljsummers on No Agenda Social for "
|
||||||
|
rawText "assistance."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user