Hi again
This is the final installment of this important tutorial and is a continuation of part 3, which dealt with the Sign Up process.
So far, we have demonstrated how one can Sign Up, get Forgot Password, Change Password with the respective notifications and the ABM app sending emails during the process. What was left out, which is extremely crucial was how one can during the registration process ensure that only Signed Up uses can use the app.
This then brings us to the final step, activating a user account after the user has done a Sign Up.
Goal
Any user who is Signed Up but who has not activated their account by confirming their email, will not be able to use the app.
1. A user signs up to use the app
2. The app sends a user a HTML email to click an activation link to activate their account
3. Once this activation link is selected by the user, the account is activated.
Reproduction
1. Ensure each user has a unique GUID on Sign Up
To be able to achieve this, each user being signed up should have a unique identifier. For this an extra field was created in the backend user database to store the user GUID.
When the user record is saved, a "guid" field is updated using this sub. Any in-active user will not be able to sign into the app.
2. Sending the activation email to the user
As the first name, email address are specified by the user during Sign Up, these details, including the guid for the user are emailed in html format to the user so that they can click on a link to activate the account.
To achieve this email being sent, an inline html template was used to send the email. Inside this email was a link to the app, inside "Activate My Account", that has the email address and the GUID for this particular user. When a user clicks the link, the app is opened and the email and GUID are used to activate the account. A GUID should match to its email address.
In production, the localhost will be changed to my domain off course. From the above link, the frmLogin screen is being opened, meaning that all the activation code should be in the frmLogin page, though the call to send the email to the user should be in the Sign Up screen.
This means, the Next button code for the Sign Up button needed to be changed to meet this requirement.
frmCreateAccount.btnNext
This first validates and verifies the entered data and saves it. Then read the user details, sends the activation email and once the email has been sent, tell the user with another modal sheet that the account was registered.
3. The user has clicked the Activate My Account link
This opens up the Sign In screen, if the email and guid exists, the account is activated so that the user can continue using the app.
frmLogin.ConnectPage
From above, the modal sheet to login is built and opened and then the ActivateAccount sub is called.
This method reads the email and the GUID, verifies those against the underlying database and then updates the useractive field to 1, meaning that the user can sign in. Remember, from part 1 of this article, clicking the Next button on the Sign In screen validates and verifies the user. If the account is in-active, the user is kept out.
frmLogin.btnNext
When everything is fine, account activated, the frmMainMenu is shown.
That's all folks. This has been an eye opening chapter for me.
Ta!
This is the final installment of this important tutorial and is a continuation of part 3, which dealt with the Sign Up process.
So far, we have demonstrated how one can Sign Up, get Forgot Password, Change Password with the respective notifications and the ABM app sending emails during the process. What was left out, which is extremely crucial was how one can during the registration process ensure that only Signed Up uses can use the app.
This then brings us to the final step, activating a user account after the user has done a Sign Up.
Goal
Any user who is Signed Up but who has not activated their account by confirming their email, will not be able to use the app.
1. A user signs up to use the app
2. The app sends a user a HTML email to click an activation link to activate their account
3. Once this activation link is selected by the user, the account is activated.
Reproduction
1. Ensure each user has a unique GUID on Sign Up
To be able to achieve this, each user being signed up should have a unique identifier. For this an extra field was created in the backend user database to store the user GUID.
B4X:
Sub GUID As String
Dim jo As JavaObject
Return jo.InitializeStatic("java.util.UUID").RunMethod("randomUUID", Null)
End Sub
When the user record is saved, a "guid" field is updated using this sub. Any in-active user will not be able to sign into the app.
2. Sending the activation email to the user
As the first name, email address are specified by the user during Sign Up, these details, including the guid for the user are emailed in html format to the user so that they can click on a link to activate the account.
To achieve this email being sent, an inline html template was used to send the email. Inside this email was a link to the app, inside "Activate My Account", that has the email address and the GUID for this particular user. When a user clicks the link, the app is opened and the email and GUID are used to activate the account. A GUID should match to its email address.
B4X:
Sub EmailActivation(semail As String,subject As String) As ResumableSub
'define the email to search for
Dim jSQL As SQL = SQLGet
Dim w As Map = CreateMap("email=":semail)
Dim uMap As Map = SQLSelectRecordWhereMap(jSQL,"users", w)
If uMap.IsInitialized = False Then Return False
Dim firstname As String = uMap.GetDefault("firstname","")
Dim sGUID As String = uMap.GetDefault("guid","")
'get the activate.html
Dim html As String = File.ReadString(File.dirapp,"activate.html")
'update the details
html = html.Replace("{{user}}",firstname)
html = html.Replace("{{appname}}",AppName)
Dim sLink As String = $"http://localhost:51049/askteencoach/frmLogin/frmLogin.html?email=${semail}&guid=${sGUID}"$
html = html.Replace("{{link}}",sLink)
html = html.Replace("=","=3D")
Try
Dim es As Map = GetEmailSettings
Dim sserver As String = es.GetDefault("server","")
Dim sport As String = es.GetDefault("port","25")
Dim susername As String = es.GetDefault("username","")
Dim spassword As String = es.GetDefault("password","")
Dim sStartTLSMode As String = es.GetDefault("starttlsmode","0")
Dim sUseSSL As String = es.GetDefault("usessl","0")
'start sending the email
esmtp.Initialize(sserver, sport, susername, spassword, "smtp")
esmtp.To.Add(semail)
esmtp.HtmlBody = True
esmtp.Body = html
esmtp.Subject = subject
If sStartTLSMode = "1" Then
esmtp.StartTLSMode = True
Else
esmtp.StartTLSMode = False
End If
If sUseSSL = "1" Then
esmtp.UseSSL = True
Else
esmtp.UseSSL = False
End If
Wait For (esmtp.Send) smtp_MessageSent(Success As Boolean)
Return Success
Catch
Return False
End Try
End Sub
In production, the localhost will be changed to my domain off course. From the above link, the frmLogin screen is being opened, meaning that all the activation code should be in the frmLogin page, though the call to send the email to the user should be in the Sign Up screen.
This means, the Next button code for the Sign Up button needed to be changed to meet this requirement.
frmCreateAccount.btnNext
B4X:
Sub btnNEXT_Clicked(Target As String)
If contLoginSignUp = False Then Return
Dim m As Map = contLoginGetContents
Dim semail As String = m.GetDefault("email","")
Dim rs As ResumableSub = ABMShared.EmailActivation(semail,"askteencoach: Activate Account")
Wait For (rs) Complete (Result As Boolean)
If Result = False Then Return
ABMShared.NavigateToPage(ws, ABMPageId, "../frmTellCreateAccount/frmTellCreateAccount.html")
End Sub
This first validates and verifies the entered data and saves it. Then read the user details, sends the activation email and once the email has been sent, tell the user with another modal sheet that the account was registered.
3. The user has clicked the Activate My Account link
This opens up the Sign In screen, if the email and guid exists, the account is activated so that the user can continue using the app.
frmLogin.ConnectPage
B4X:
Public Sub ConnectPage()
'connect navigation bar
ConnectNavigationBar
'add components for the page
AdminAccess
page.SetFontStack("arial,sans-serif")
' this page uses uploads, so needs some settings
page.ws.session.SetAttribute("abmcallback", Me)
page.ws.session.SetAttribute("abmdownloadfolder", DownloadFolder)
page.ws.session.SetAttribute("abmmaxsize", DownloadMaxSize)
page.AddModalSheetTemplate(msLoginBuild)
page.Refresh ' IMPORTANT
' NEW, because we use ShowLoaderType=ABM.LOADER_TYPE_MANUAL
page.FinishedLoading 'IMPORTANT
page.RestoreNavigationBarPosition
page.ShowModalSheet("msLogin")
ActivateAccount
End Sub
From above, the modal sheet to login is built and opened and then the ActivateAccount sub is called.
B4X:
Private Sub ActivateAccount()
'get the details that need to be activated
Dim email As String = ws.UpgradeRequest.GetParameter("email")
Dim guid As String = ws.UpgradeRequest.GetParameter("guid")
If email = "" Or guid = "" Then Return
'define the search criteria, fields must equal
Dim w As Map
w.Initialize
w.put("email=", email)
w.put("guid=", guid)
'Get connection from current pool if MySQL/MSSQL
Dim jSQL As SQL = ABMShared.SQLGet
'find the user meeting this email and guid
Dim UserMap As Map = ABMShared.SQLSelectRecordWhereMap(jSQL,"users", w)
If UserMap.IsInitialized = False Then
ABMShared.Warn(page,"The User specified could not be found in our records, this account cannot be activated.")
Return
End If
'The email has been found, update the passwords
UserMap.put("useractive","1")
Dim bUpdate As Boolean = ABMShared.SQLRecordUpdate(jSQL, "users", UserMap, "email", email)
If bUpdate = True Then
myToastId = myToastId + 1
page.ShowToast("toast" & myToastId, "toastgreen", "Account activated successfully.", 3000,False)
'Close the connection to the database
ABMShared.SQLClose(jSQL)
Return
Else
myToastId = myToastId + 1
page.ShowToast("toast" & myToastId, "toastred", "Account could NOT be activated successfully.", 3000,False)
'Close the connection to the database
ABMShared.SQLClose(jSQL)
Return
End If
End Sub
This method reads the email and the GUID, verifies those against the underlying database and then updates the useractive field to 1, meaning that the user can sign in. Remember, from part 1 of this article, clicking the Next button on the Sign In screen validates and verifies the user. If the account is in-active, the user is kept out.
B4X:
Private Sub msLoginSignIn() As Boolean
'define a map to hold the form contents
Dim m As Map
'read the file contents to a map
m = msLoginGetContents
'validate the form contents where required
If msLoginValidate(m) = False Then
Return False
End If
'the form contents are ok, continue with the sign in process
'define the search criteria, fields must equal
page.pause
Dim w As Map
Dim nk As String
Dim nv As String
w.Initialize
For Each strKey As String In m.Keys
nv = m.Get(strKey)
nk = strKey & "="
w.put(nk, nv)
Next
'Get connection from current pool if MySQL/MSSQL
Dim jSQL As SQL = ABMShared.SQLGet
Dim UserMap As Map = ABMShared.SQLSelectRecordWhereMap(jSQL,"users", w)
If UserMap.IsInitialized = False Then
page.resume
ABMShared.Warn(page,"The Sign In credentials you have provided are incorrect. Please check them and try to sign in again.")
ws.Session.SetAttribute("IsAuthorized", "")
Return False
End If
Dim uactive As String = UserMap.get("useractive")
ws.Session.SetAttribute("UserActive", uactive)
If uactive = 0 Then
page.resume
ABMShared.Warn(page,"Your Profile is currently in-active, please contact the App Administrator.")
Return False
End If
Dim uemail As String = UserMap.get("email")
ws.Session.SetAttribute("UserEmail", uemail)
Dim uid As String = UserMap.get("id")
ws.Session.SetAttribute("UserID", uid)
Dim utype As String = UserMap.get("groupid")
ws.Session.SetAttribute("UserType", utype)
ws.Session.SetAttribute("IsAuthorized", "true")
ws.Session.SetAttribute("authType", "local")
'Build the permissions
ABMShared.PermissionsL.Initialize
ABMShared.Permissions.Initialize
ABMShared.PermissionsL = ABMShared.SQLExecuteMaps(jSQL,"select * from GroupPermissions where groupid = ?", Array As String(utype))
For Each permission As Map In ABMShared.PermissionsL
Dim spageName As String = permission.get("pagename")
spageName = spageName.tolowercase
ABMShared.Permissions.put(spageName,permission)
Next
'Close the connection to the database
ABMShared.SQLClose(jSQL)
page.resume
Return True
End Sub
frmLogin.btnNext
B4X:
'An ABMButton has been clicked
Sub btnNext_Clicked(Target As String)
If msLoginSignIn = False Then Return
ABMShared.NavigateToPage(ws, ABMPageId, "../frmMainMenu/frmMainMenu.html")
End Sub
When everything is fine, account activated, the frmMainMenu is shown.
That's all folks. This has been an eye opening chapter for me.
Ta!