Technical description of the Users Page

User management is concerned with login and logout, forgotten passwords and so on. It sounds simple enough but there are many different ways to do it and lots of different scenarios to deal with.

As with the other scripts, this one starts by naming itself and declaring all its variables. One of these, the containing DIV, is imported from the main script.

  script Users

  import div Container

  div LoginDiv
  div ItemDiv
  div ButtonDiv
  div EmailDiv
  div ResetDiv
  div PasswordDiv
  div Password2Div
  div NameDiv
  div ConfirmationDiv
  div LoggedInDiv
  span ZoomSpan
  span Span
  label Label
  label Reset
  label Back
  input Email
  input ResetInput
  input Password
  input Password2
  input Name
  input ConfirmationInput
  button LoginButton
  button RegisterButton
  button ResetPasswordButton
  a Link
  a LogoutLink
  a NameLink
  variable LabelWidth
  variable FieldWidth
  variable FieldHeight
  variable Record
  variable Zoom
  variable Item
  variable PasswordHash
  variable ConfirmationCode
  variable UserName
  variable SavedEmail
  variable SavedPassword
  variable Validated
  variable Message
  variable N

 

EasyCoder doesn't have constants so we set up fixed values in ordinary variables.

  put 25 into LabelWidth
  put 78 into FieldWidth
  put `height:2em` into FieldHeight
  set the style of Container to `padding-top:0.5em`

 

The module builds a tabular form, using flexboxes to keep the columns aligned. All of this should be pretty simple to follow.

  create LoginDiv in Container
  set the style of LoginDiv to `display:none`

  create EmailDiv in LoginDiv
  set the style of EmailDiv to `display:flex`
  create Label in EmailDiv
  set the style of Label to `padding-top:0.8em;flex:` cat LabelWidth
  set the content of Label to `Email:`
  create Email in EmailDiv
  set the style of Email to FieldHeight cat `;padding:4px;flex:` cat FieldWidth
  set the size of Email to 40

  create PasswordDiv in LoginDiv
  set the style of PasswordDiv to `display:flex`
  create Label in PasswordDiv
  set the style of Label to `padding-top:0.8em;flex:` cat LabelWidth
  set the content of Label to `Password:`
  create Password in PasswordDiv
  set the style of Password to FieldHeight cat `;flex:` cat FieldWidth
  set the size of Password to 40
  set attribute `type` of Password to `password`

  create Password2Div in LoginDiv
  set the style of Password2Div to `display:none`
  create Label in Password2Div
  set the style of Label to `padding-top:0.8em;flex:` cat LabelWidth
  set the content of Label to `Password (again):`
  create Password2 in Password2Div
  set the style of Password2 to FieldHeight cat `;flex:` cat FieldWidth
  set the size of Password2 to 40
  set attribute `type` of Password2 to `password`

  create ResetDiv in LoginDiv
  set the style of ResetDiv to `display:none`
  create Label in ResetDiv
  set the style of Label to `padding-top:0.8em;flex:` cat LabelWidth
  set the content of Label to `Reset Code:`
  create ResetInput in ResetDiv
  set the style of ResetInput to FieldHeight cat `;padding:4px;flex:` cat FieldWidth
  set the size of ResetInput to 6

  create NameDiv in LoginDiv
  set the style of NameDiv to `display:none`
  create Label in NameDiv
  set the style of Label to `padding-top:0.8em;flex:` cat LabelWidth
  set the content of Label to `Name or Nickname:`
  create Name in NameDiv
  set the style of Name to FieldHeight cat `;padding:4px;flex:` cat FieldWidth
  set the size of Name to 40

  create ConfirmationDiv in LoginDiv
  set the style of ConfirmationDiv to `display:none`
  create Label in ConfirmationDiv
  set the style of Label to `padding-top:0.8em;flex:` cat LabelWidth
  set the content of Label to `Confirmation Code:`
  create ConfirmationInput in ConfirmationDiv
  set the style of ConfirmationInput to FieldHeight cat `;padding:4px;flex:` cat FieldWidth
  set the size of ConfirmationInput to 6

  create ItemDiv in LoginDiv
  set the style of ItemDiv to `display:flex;margin-top:0.5em`
  create Label in ItemDiv
  set the style of Label to `flex:` cat LabelWidth

  create ButtonDiv in ItemDiv
  set the style of ButtonDiv to `flex:` cat FieldWidth
  create LoginButton in ButtonDiv
  set the style of LoginButton to `margin-right:1em`
  set the text of LoginButton to `Login`
  on click LoginButton go to Login
  create RegisterButton in ButtonDiv
  set the style of RegisterButton to `margin-right:1em`
  set the text of RegisterButton to `Register`
  on click RegisterButton go to Register
  create ResetPasswordButton in ButtonDiv
  set style `display` of ResetPasswordButton to `none`
  set the text of ResetPasswordButton to `Reset Password`
  on click ResetPasswordButton go to ResetPassword2
  create Link in ButtonDiv
  set the style of Link to `margin-left:3em`
  create Reset in Link
  set the text of Reset to `I lost my password`
  on click Reset go to ResetPassword
  create Link in ButtonDiv
  set the style of Link to `margin-left:3em`
  create Back in Link
  set style `display` of Back to `none`
  set the text of Back to `Back`
  on click Back go to GoBack

  create LoggedInDiv in Container
  set the style of LoggedInDiv to `display:none`

 

Once initialization is done the module waits for a trigger. The command 'set ready' allows the calling script to resume.

  on trigger go to Start
  set ready
  stop

 

The user's name and his encrypted password are held in the browser local storage, so we read them in.

  Start:
  get SavedEmail from storage as `email`
  get SavedPassword from storage as `password`
  if SavedEmail is empty go to NotLoggedIn
  if SavedPassword is not empty go to SetupLogin

 

There are several functional blocks of code. The order they appear is not important but we try to keep related parts close to each other. Here we do the actions that are needed when the script concludes the user is not logged in. A message line under the map shows the current login state and present a 'Login/Register' link. We also create a SPAN containing the current zoom value and post its CSS id back to the main script so it can display it. Every DOM element created by EasyCoder has a unique id that contains the name of the variable and a sequence number that increments for every element created. These ids are quite easy to spot in the browser debugger.

NotLoggedIn:
  set the content of LoggedInDiv to
    `You are not logged in.`
  set style `display` of LoginDiv to `none`
  set style `display` of LoggedInDiv to `block`
  create LogoutLink in LoggedInDiv
  set the style of LogoutLink to `margin-left:1em`
  set the content of LogoutLink to `Login/Register`
  on click LogoutLink go to ShowLoginForm
  create ZoomSpan in LoggedInDiv
  set the style of ZoomSpan to `float:right`
  get Zoom from storage as `zoom`
  set the content of ZoomSpan to `Z:` cat Zoom
  put `{}` into Message
  json set property `request` of Message to `logout`
  json set property `zoom` of Message to attribute `id` of ZoomSpan
  send Message to parent
  stop

 

The next section deals with the case where the user is logged in. It's a little more complex because it shows the name of the user as a clickable link, that lets you display just your own pins. This capability requires a message to be sent to the main script when the link is clicked.

The rest of this section is very much like the previous one.

DoLoggedIn:
  clear LoggedInDiv
  create Span in LoggedInDiv
  set the content of Span to `Hi, `
  create NameLink in LoggedInDiv
  set the content of NameLink to property `name` of Record
  set attribute `title` of NameLink to `Show only your own pins`
  on click NameLink
  begin
    put `{}` into Message
    json set property `request` of Message to `select`
    json set property `select` of Message to `author`
    json set property `author` of Message to property `name` of Record
    json set property `email` of Message to Email
    send Message to parent
  end
  create Span in LoggedInDiv
  set the content of Span to ` (` cat Email cat `)`

  set style `display` of LoginDiv to `none`
  set style `display` of LoggedInDiv to `block`
  create LogoutLink in LoggedInDiv
  set the style of LogoutLink to `margin-left:1em`
  set the content of LogoutLink to `Logout`
  on click LogoutLink go to Logout
  put Email into storage as `email`
  create ZoomSpan in LoggedInDiv
  set the style of ZoomSpan to `float:right`
  get Zoom from storage as `zoom`
  set the content of ZoomSpan to `Z:` cat Zoom
  put `{}` into Message
  json set property `request` of Message to `login`
  json set property `email` of Message to Email
  json set property `zoom` of Message to attribute `id` of ZoomSpan
  send Message to parent
  return

 

The form built earlier is ready to go, only requiring elements to be shown or hidden.

ShowLoginForm:
  set the content of Email to ``
  set the content of Password to ``
  set style `display` of LoginDiv to `block`
  set style `display` of LoggedInDiv to `none`
  stop

 

When the user clicks the Login button we first check all the fields have content, then we validate the password by calling the server with the saved value and the one typed into the field. Because REST URLs have forward slashes we must ensure none of our data does; the server will translate tilde characters back into forward slashes.

SetupLogin:
  set the content of Email to SavedEmail
  set the content of Password to SavedPassword

Login:
  if Email is empty go to FillAllFields
  if Password is empty go to FillAllFields
  rest get Record from `_/ec_users/get/` cat Email
  if Record is empty go to NoRecord
  if Email is not property `email` of Record go to NoRecord
  put property `password` of Record into Item
  replace `/` with `~` in Item
  rest get Validated from `_validate/` cat Password cat `/` cat Item
  if Validated is `yes`
  begin
    put Email into storage as `email`
    put Password into storage as `password`
    set style `display` of LoginDiv to `none`
    gosub to DoLoggedIn
    stop
  end

 

This is called from a couple of places if the login data is invalid:

NoRecord:
  put `` into storage as `email`
  put `` into storage as `password`
  alert `No record exists or incorrect password for ` cat Email cat `.`
  go to NotLoggedIn

 

Here we handle a logout, by clearing the stored data and making appropriate elements visible or invisible. Then we inform the main script so it can act appropriately.

Logout:
  put `` into storage as `email`
  put `` into storage as `password`
  set the content of Email to ``
  set the content of Password to ``
  set the content of Password2 to ``
  set the content of Name to ``
  set style `display` of LoginDiv to `block`
  set style `display` of LoggedInDiv to `none`
  set style `display` of LoginButton to `inline-block`
  set style `display` of RegisterButton to `inline-block`
  set style `display` of Reset to `inline-block`
  set style `display` of Back to `none`
  set style `display` of Password2Div to `none`
  set style `display` of NameDiv to `none`
  put `{}` into Message
  json set property `requet` of Message to `logout`
  go to NotLoggedIn

 

The other main activity is new user registration. This requires a fuller form to be displayed:

Register:
  set style `display` of PasswordDiv to `flex`
  set style `display` of Password2Div to `flex`
  set style `display` of ResetDiv to `none`
  set style `display` of NameDiv to `flex`
  set style `display` of LoginButton to `none`
  set style `display` of RegisterButton to `inline-block`
  set style `display` of ResetPasswordButton to `none`
  set style `display` of Reset to `none`
  set style `display` of Back to `inline`
  on click RegisterButton go to ProcessRegistration
  stop

 

When the Register button is clicked we check that required fields are present and that the password is given twice:

ProcessRegistration:
  if Email is empty go to FillAllFields
  if Password is empty go to FillAllFields
  if Password2 is empty go to FillAllFields
  if Name is empty go to FillAllFields
  if Password is not Password2
  begin
    alert `Passwords do not match`
    stop
  end

 

Now we check if there is already a user record for the email address given. If there isn't we can create one. We generate a random 6-digit number and ask the server to send it in an email to the user. we then reveal a confirmation box in which they can type the code when they receive it, put a message up to tell them what to do and wait for them to click the Confirm button. If it matches, the rest of the data is a valid user record so we can save it. The server provides us with a REST endpoint to create a one-way hashed password record so it can't be read by anyone, and the final action its to inform the main script that we now have a logged-in user.

  rest get Record from `_/ec_users/get/` cat Email
  if Record is empty
  begin
    put random 900000 into ConfirmationCode
    add 100000 to ConfirmationCode
    put `{}` into Record
    json set property `from` of Record to `admin@hereonthemap.com`
    json set property `to` of Record to Email
    json set property `subject` of Record to `Confirmation code`
    json set property `message` of Record to `<html><body>`
      cat `Hi ` cat Name cat `<br /><br />`
      cat `Please use this code to confirm your registration at Map Stories:<br />`
      cat `<h1>` cat ConfirmationCode cat `</h1>`
      cat `If you did not request this email, please ignore it and no action will be taken.`
      cat `</body></html>`
    rest post Record to `_email`
    set style `display` of ConfirmationDiv to `flex`
    set the text of RegisterButton to `Confirm registration`
    wait 10 ticks
    alert `A confirmation code has been sent to ` cat Email cat `.` cat newline
      cat `When it arrives, type it in the "Confirmation Code" box `
      cat `and click "Confirm Registration".`
    on click RegisterButton
    begin
      if ConfirmationInput is not ConfirmationCode
      begin
        alert `Invalid confirmation code - no action taken.`
        stop
      end
      put Email into storage as `email`
      put Password into storage as `password`
      put `{}` into Record
      json set property `email` of Record to Email
      rest get PasswordHash from `_hash/` cat Password
      json set property `password` of Record to PasswordHash
      json set property `name` of Record to Name
      rest post Record to `_/ec_users/set`
      set style `display` of ConfirmationDiv to `none`
      set the text of RegisterButton to `Register`
      gosub to DoLoggedIn
    end
  end
  else alert `A record already exists for ` cat Email cat `.`
  stop

 

This is called from a couple of places when users leave a field empty:

FillAllFields:
  alert `Please fill in all the fields.`
  stop

 

The remaining action is to deal with a lost password. First we display the appropriate form:

ResetPassword:
  set style `display` of ResetDiv to `none`
  set style `display` of PasswordDiv to `none`
  set style `display` of Password2Div to `none`
  set style `display` of LoginButton to `none`
  set style `display` of RegisterButton to `none`
  set style `display` of ResetPasswordButton to `inline-block`
  set style `display` of Reset to `none`
  set style `display` of Back to `inline-block`
  wait 10 ticks
  alert `Please type the email you used for your registration then click Reset Password.`
  on click ResetPasswordButton go to ResetPassword2
  stop

 

When the user clicks the Reset button we check if the email is valid. (If the user has forgotten it there's not a lot we can do.) As with registration we generate a random confirmation code and send it in an email.

ResetPassword2:
  if Email is empty go to FillAllFields
  rest get Record from `_/ec_users/get/` cat Email
  if Record is empty
  begin
    alert ` No record exists for ` cat Email cat `.`
    go to Register
  end
  put property `name` of Record into UserName
  put random 900000 into ConfirmationCode
  add 100000 to ConfirmationCode
  put `{}` into Record
  json set property `from` of Record to `admin@hereonthemap.com`
  json set property `to` of Record to Email
  json set property `subject` of Record to `Password reset key`
  json set property `message` of Record to `<html><body>`
    cat `Hi ` cat Name cat `<br /><br />`
    cat `Please use this code to confirm your password reset:<br />`
    cat `<h1>` cat ConfirmationCode cat `</h1>`
    cat `If you did not request this email, please ignore it and no action will be taken.`
    cat `</body></html>`
  rest post Record to `_email`
  set style `display` of ResetDiv to `flex`
  set style `display` of PasswordDiv to `flex`
  set the content of Password to ``
  set style `display` of Password2Div to `flex`
  set the content of Password2 to ``
  set the text of ConfirmationInput to ``
  on click ResetPasswordButton go to ResetPassword3
  wait 10 ticks
  alert `A password reset code has been sent to ` cat Email cat `.` cat newline
    cat `When it arrives, use it on this screen to confirm your new password.`
  stop

 

When the user submits their new password, along with the confirmation code, their record will be updated and the main script informed about the login.

ResetPassword3:
  if Password is empty go to FillAllFields
  if Password2 is empty go to FillAllFields
  if Password is not Password2
  begin
    alert `Passwords do not match`
    stop
  end
  if ResetInput is not ConfirmationCode
  begin
    alert `Invalid password reset code - no action taken.`
    stop
  end
  put `{}` into Record
  json set property `email` of Record to Email
  rest get PasswordHash from `_hash/` cat Password
  json set property `password` of Record to PasswordHash
  json set property `name` of Record to UserName
  rest post Record to `_/ec_users/set`
  put Email into storage as `email`
  put Password into storage as `password`
  go to DoLoggedIn

 

Finally, there's a button to back out of the registration process and return to Login. It just resets the visibility of a few elements.

GoBack:
  set style `display` of ResetDiv to `none`
  set style `display` of PasswordDiv to `flex`
  set style `display` of Password2Div to `none`
  set style `display` of NameDiv to `none`
  set style `display` of LoginButton to `inline-block`
  set style `display` of Reset to `inline`
  set style `display` of Back to `none`
  on click RegisterButton go to Register
  stop