A few posts ago I added a status
field to the registration types, but so far the only value used there is N
for new.
I'd like to introduce some more statuses: most importantly C for completed, set when a registrant submits their form.
That's a small change: in handleRegistrationPost
, instead of saving the Registration
provided by digestive functors, change the status first:
(_, Just newRegistration) -> do let n = newRegistration { status = "C" } withDB $ \conn -> gupdateInto conn "registration" "nonce = ?" n [identifier] return "Record updated."
Now when a registrant submits a valid form, it will be marked as completed in the database. You'll be able to see that if you look at the registration
table in the database, but this changed status won't otherwise affect the behaviour of the registration system.
I'd like the registration system to prohibit changes once a form has been completed: in real life, perhaps because that information is going to be used to print admission wristbands or attendee lists. That's not to say that a registration can't ever be amended after this: but there is more happening in real life that needs to be adjusted too, and this little registration system isn't going to do that.
People coming to edit the form do so via handleRegistration
in app/Main.hs
and it's there that I'll put in a check for completion status.
First I'll define a predicate on status
to decide if a form is editable: by default, not editable; and explicitly, editable if New and not-editable if Completed.
editableStatus :: String -> Bool editableStatus "N" = True editableStatus "C" = False editableStatus _ = False
handleRegistration
can split into two:
handleRegistration :: String -> S.Handler B.Html handleRegistration identifier = do registration <- selectByNonce identifier if (editableStatus . status) registration then handleEditableRegistration registration else handleReadOnlyRegistration registration
... with handleEditableRegistration containing the remainder of the original handleRegistration
, with a tweak to extract the nonce: where we previously had identifier
in scope we do not, so instead extract it with nonce registration
.
handleEditableRegistration :: Registration -> S.Handler B.Html handleEditableRegistration registration = do ... B.h1 $ do "Registration " B.toHtml (show $ nonce registration) ...
... and handleReadOnlyRegistration
should deliver some non-editable HTML description, for example:
handleReadOnlyRegistration :: Registration -> S.Handler B.Html handleReadOnlyRegistration registration = return $ B.docTypeHtml $ do B.body $ do B.h1 $ do "Completed registration " B.toHtml (show $ nonce registration) B.p $ "First name: " <> (fromString . firstname) registration B.p $ "Last name: " <> (fromString . lastname) registration B.p $ "Date of birth: " <> (fromString . dob) registration
If there are next steps to be taken by a user, this is an appropriate place to include links (for example, in the real life version, at this point there is a downloadable PDF to print and sign - that we only want available once the form has been completed).
There's a security problem here though: although the server no longer delivers an editable form to the end user, so that they aren't encouraged to edit the form, nothing else stops a POST
request being sent some other way to cause changes to the database. With friendly users, that's most likely to happen (I think) with the form being opened twice, and then submitted twice. With malicious users, it's probably not too hard to fake a submission with curl
(for example) as the protocol is really simple.
So next I'm going to look at verifying updates a bit more strictly.
No comments:
Post a Comment