loklak_depot – The Beginning: Accounts (Part 4)

So here I am with another blog post. This, my fourth post in this series, will focus on SignUp verification and how I implemented it. The fifth and the final post in this series, will be on Password Resetting. This will finish the Accounts system, and I’ll then move onto talking about some Twitter analysis applications I’m working on (the Q&A apps I spoke about in the previous posts).

I had covered the Sign Up page before, but there was still a problem. What if the page were to be spammed by bots / unauthorised users? It would then create unnecessary number of accounts and DAO.authentication will then overflow. That’s why it was deemed necessary to have a SignUp verification system.

The SignUp verification system is simple: send email to user once he signs up. The email consists of a verification link which has a token which can be used to verify. Clicking on that will verify his account.

Without further ado, let’s dive into the code. The following code snippet should be self explanatory through the added comments, though I’ll explain at the bottom:


		if (post.get("signup", null) == null || post.get("password", null) == null) {
			throw new APIException(400, "signup or password empty");
		}

		// get credentials
		String signup, password;
		try {
			signup = URLDecoder.decode(post.get("signup", null), "UTF-8");
			password = URLDecoder.decode(post.get("password", null), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw new APIException(400, "malformed query");
		}

		// check email pattern
		Pattern pattern = Pattern.compile(LoklakEmailHandler.EMAIL_PATTERN);
		if (!pattern.matcher(signup).matches()) {
			throw new APIException(400, "no valid email address");
		}

		// check password pattern
		String passwordPattern = DAO.getConfig("users.password.regex", "^(?=.*\d).{6,64}$");

		pattern = Pattern.compile(passwordPattern);

		if (signup.equals(password) || !pattern.matcher(password).matches()) {
			throw new APIException(400, "invalid password");
		}

		// check if id exists already

		ClientCredential credential = new ClientCredential(ClientCredential.Type.passwd_login, signup);
		Authentication authentication = new Authentication(credential, DAO.authentication);

		if (authentication.getIdentity() != null) {
			throw new APIException(422, "email already taken");
		}

		// create new id
		ClientIdentity identity = new ClientIdentity(ClientIdentity.Type.email, credential.getName());
		authentication.setIdentity(identity);

		// set authentication details
		String salt = createRandomString(20);
		authentication.put("salt", salt);
		authentication.put("passwordHash", getHash(password, salt));
		authentication.put("activated", activated);

		// set authorization details
		Authorization authorization = new Authorization(identity, DAO.authorization, DAO.userRoles);
		authorization.setUserRole(DAO.userRoles.getDefaultUserRole(BaseUserRole.USER));

		if (sendEmail) {
			String token = createRandomString(30);
			ClientCredential access_token = new ClientCredential(ClientCredential.Type.access_token, token);
			Authentication tokenAuthentication = new Authentication(access_token, DAO.authentication);
			tokenAuthentication.setIdentity(identity);
			tokenAuthentication.setExpireTime(7 * 24 * 60 * 60);
			tokenAuthentication.put("one_time", true);

			try {
				LoklakEmailHandler.sendEmail(signup, "Loklak verification", getVerificationMailContent(token));

				result.put("message",
						"You successfully signed-up! An email with a verification link was send to your address.");

			} catch (ConfigurationException e) {
				result.put("message",
						"You successfully signed-up, but no email was sent as it's disabled by the server.");
			} catch (Exception e) {
				result.put("message",
						"You successfully signed-up, but an error occurred while sending the verification mail.");
			}
		} else {
			result.put("message", "You successfully signed-up!");
		}

		return result;
	}

This code is from a class SignUpService.java which again extends AbstractAPIHandler (please see my previous blogposts and Robert’s posts if you aren’t clear about it), so post is obviously the POST request, and is of type Query, and this code returns a JSON (as AbstractAPIHandler implements an abstract method called serviceImpl which returns JSON). We use APIException (a predefined exception class we made) to make it more convenient to report errors with codes (earlier we used JSON). The signup and password posted from the webpage are encoded URI components, so we decode them. The rule for password and email is that they should match the regex scheming + they can’t be equal to each other and that email should be something not in the DB i.e DAO.authentication. The latter is checked by simply using the ClientCredential keys (passwd_login enum) to access DAO.auth and checking.

Here comes the verification part. We first encode the password in SHA256 using a random salt. If the user is permitted to send the email (I’ll come to that in just a bit), we set an access token for it (random int), give it an expiry time, keep it one-time, sent the token as a GET parameter to the base URL i.e localhost (in this case), and mail the url to him using loklakemailhandler.

This is how the getVerificationMailContent function looks like. It makes up the URL and fixes the email body.


private String getVerificationMailContent(String token){
    	
    	String verificationLink = DAO.getConfig("host.name", "http://localhost:9000") + "/api/signup.json?access_token="+token+"&validateEmail=true&request_session=true";
    	
    	// get template file
    	String result;
    	try{
    		result = IO.readFileCached(Paths.get(DAO.conf_dir + "/templates/verification-mail.txt"));
    	} catch(IOException e){
    		result = "";
    	}
    	
    	result = result.contains(verificationLinkPlaceholder) ? result.replace(verificationLinkPlaceholder, verificationLink) : verificationLink;
    	
    	return result;
    }

where the value of verificationLinkPlaceholder is "%VERIFICATION-LINK%". We take up the token, and send it as a GET parameter along with two more params (validateEmail=true and request_session=true) to the signup servlet. The body of the email contains this URL, which we are reading from a custom file which has the placeholder. The file is as follows: (verification-mail.txt)


You just signed up for Loklak!

To finish the process, you have to verify you email address by clicking on the following link:

%VERIFICATION-LINK%

If you didn't sign up for Loklak, please ignore this email.

As is self explanatory, the link is inserted in the place of %VERIFICATION-LINK%. We read the file using IO.readFileCached and replace using replace as is seen.

Now the last part. What happens when the user clicks on the link? It still leads to the SignUp servlet, isn’t it? So what happens? The following code snippet answers that very question:


public JSONObject serviceImpl(Query post, Authorization rights, final JSONObjectWithDefault permissions) throws APIException {

    	BaseUserRole serviceLevel = getCustomServiceLevel(rights);
    	
    	JSONObject result = new JSONObject();
    	
    	// if regex is requested
    	if(post.get("getParameters", false)){
    		String passwordPattern = DAO.getConfig("users.password.regex", "^(?=.*\d).{6,64}$");
    		String passwordPatternTooltip = DAO.getConfig("users.password.regex.tooltip", "Enter a combination of atleast six characters");
    		if("false".equals(DAO.getConfig("users.public.signup", "false"))){
				throw new APIException(403, "Public signup disabled");
    		}
    		result.put("regex", passwordPattern);
    		result.put("regexTooltip", passwordPatternTooltip);
    		
    		return result;
    	}
    	
    	// is this a verification?
    	if(post.get("validateEmail", false) && serviceLevel.ordinal() > BaseUserRole.ANONYMOUS.ordinal()){
    		ClientCredential credential = new ClientCredential(ClientCredential.Type.passwd_login, rights.getIdentity().getName());
    		Authentication authentication = new Authentication(credential, DAO.authentication);
    		
    		authentication.put("activated", true);

    		result.put("message", "You successfully verified your account!");
    		return result;
    	}
    	
    	
    	
    	// check if this is done by admin or public and if verification is needed
    	boolean activated = true;
    	boolean sendEmail = false;
    	
    	if(serviceLevel != BaseUserRole.ADMIN){
    		switch(DAO.getConfig("users.public.signup", "false")){
    			case "false":
                    throw new APIException(403, "Public signup disabled");
    			case "admin":
    				activated = false;
    				break;
    			case "email":
    				activated = false;
    				sendEmail = true;
    		}
    	}

Just a reminder: all the code before this one, was also a part of serviceImpl function, so the function gets completed now.

We GET the validateEmail variable from the POST request (if the access level of the user is above anonymous i.e he / she is authorised), and if it is true, we simply convert the access_token key (which was used to store the email ID) into passwd_login, so that it is like all the other email IDs (all verified email IDs have key passwd_login, and access_token key is for those for which we need to verify). Also, as in the last article, we had four options for users.public.signup: false, admin, email and true. The default (i.e true) is that the account is activated and no email will be sent (i.e SignUp page will directly lead to activation). For admin (since admin is for testing purposes), the account won’t really be activated but he can check if the system works. For sending the verification email, we set the config property to “email”, making sendEmail (spoken about earlier) true for “email”. That’s all there is.

And just one last question you may be having: what is the regex code for (written above the validation code)? We made a small improvement to the SignUp system: instead of hardcoding password strength by ourselves, we let the admin to decide for himself, so we kept password strength and password strength description as fields in the config file. And we modified the JavaScript of SignUp page, we added a JQuery AJAX call as follows:


$.ajax(	"/api/signup.json", {
			data: { getParameters: true },
			dataType: 'json',
			success: function (response) {
                regex = response.regex;
                var regexTooltip = response.regexTooltip;
                $('#pass').tooltip({'trigger':'focus', 'placement': 'left', 'title': regexTooltip});
			},
			error: function (xhr, ajaxOptions, thrownError) {
			    $('#status-box').text(thrownError);
                $('#status-box').addClass("error");
                $('#email').prop( "disabled", true );
                $('#pass').prop( "disabled", true );
                $('#confirmpass').prop( "disabled", true );
                $('#signup').prop( "disabled", true );
			},
	});

We simply send an AJAX call with the getParameters variable to the SignUp servlet (which again retrieves this variable and returns a JSON which specifies the regex descriptions). We use this JSON and retrieve the regex and the regex descriptions from it, and display that on the webpage (we modified the Password Strength function accordingly as well) Additionally, we also kept a status-box to display errors (if there is any error i.e Public SignUp disabled / emails disabled) which will display the error on the screen itself and block all the fields of the SignUp form.

So that’s it for the SignUp verification. My next and final article on the Accounts system will be on the Password Reset system and how we finally got the Password Recovery system to a full circle. Feedback, as always, is welcome. 🙂

loklak_depot – The Beginning: Accounts (Part 4)

One thought on “loklak_depot – The Beginning: Accounts (Part 4)

Comments are closed.