B4J Tutorial [ABMaterial]: Creating a Sign In Modal Dialog with Options: Part 3.1

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.

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.

activateemail.png


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!
 

Attachments

  • frmCreateAccount.bas
    30 KB · Views: 472
  • frmLogin.bas
    23.9 KB · Views: 469
  • activate.zip
    1.4 KB · Views: 426

XbNnX_507

Active Member
Licensed User
Longtime User
Hi, Minute 1:02 to 1:07 when you click the activate button at the mail...
How did you managed to hide the url parameters email and Guid ?
I've read the modules you uploaded but i just can't get my head around it.
 

Mashiane

Expert
Licensed User
Longtime User
Hi, Minute 1:02 to 1:07 when you click the activate button at the mail...
How did you managed to hide the url parameters email and Guid ?
I've read the modules you uploaded but i just can't get my head around it.
Ohh that is inside the HTML email that gets sent. There should be a template of that inside the project files.
 

Mashiane

Expert
Licensed User
Longtime User
I see, I was watching this on my cell so it was too fast. I think its the browser that does that, I dont remember doing anything specific. I'm using Opera.

Remember, the GUID on activation is compared to an existing GUID saved in the db for that particular email and thus both the email and GUID are verified to that db record and not any two should be the same. So if the email and the GUID dont match to the activation wont happen. One thing one could do is to hash the GUID before saving and sending it and then decode this and compare results.

Sorry cant help much with that.
 
Top