Saturday, February 24, 2018

Submission status (20)

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