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