loklak_depot – The Beginning: Accounts (Part 5)

So here we are, the culmination of the Accounts system. This final blog post in the series will be on the Password Reset system. It will be a continuation of the Password Recovery system, and takes in concepts from the SignUp Verification system, so if you haven’t read those two yet, I strongly recommend you do so.

From the Password Recovery system, we now know how to accept an email ID from the user (whose email needs to be recovered), and send him an email with a Password Reset link. What we now need to do, is implement the reset link, and make a system which actually resets the password. Again, we’re gonna do this using Servlets.

First up, we needed to figure out the reset link. Here, we’ll do exactly as we did in the SignUp verification: use tokens. We give them a link to the Reset Password page (will talk about it after this) and send in a token so that the link is unique. So in the PasswordRecoveryService servlet, I added the tokens feature as well.

The way I did it is the same as what I wrote in the last article, so I’ll just show the code here, for the explanation please refer to the last article:


String usermail;
		try {
			usermail = URLDecoder.decode(call.get("forgotemail", null), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw new APIException(400, "malformed query");
		}

//code redacted on purpose, read below

		String subject = "Password Recovery";
		try {
			LoklakEmailHandler.sendEmail(usermail, subject, getVerificationMailContent(token));
			result.put("message", "Recovery email sent to your email ID. Please check");
		} catch (Exception e) {
			result.put("message", e.toString());
		}
		return result;
	}


	private String getVerificationMailContent(String token) {

		String verificationLink = DAO.getConfig("host.name", "http://localhost:9000")
				+ "/apps/resetpass/index.html?token=" + token;
		String result;
		try {
			result = IO.readFileCached(Paths.get(DAO.conf_dir + "/templates/reset-mail.txt"));
		} catch (IOException e) {
			result = "";
		}

		result = result.contains(resetLinkPlaceholder) ? result.replace(resetLinkPlaceholder, verificationLink)
				: verificationLink;

		return result;
	}

We basically get the forgotemail (i.e email ID whose password user forgot which he entered in the Password Recovery page). We check if the email even exists in the Authentication database: if it does, the recovery email is sent to him. The token generation is obvious (resetpass is the Password Reset page as I mentioned), and like in the SignUp verification, we again kept a file reset-mail.txt which is the body of the email we have to send. But, there’s another portion in that code which I redacted. What did I do in there?

Now, the user gets the mail, and he clicks the link. He is led to localhost:9000/apps/resetpass/index.html?token=<random-token> (localhost in this case). What we want now is the program to figure out from the token what email ID’s password needs to be reset, and then once the user resets the password from that Email ID, make the changes to the database. Now comes the main part i.e resetauth

I had to figure out what email ID the token refers to, so I figured out, the best solution would be to have another JsonTray in DAO, which maps token names with email IDs and also contains their expiry times. The reason why I did so, and didn’t do the other way round, will be explained later in this article.

So, I decided to add another enum to ClientCredential.Type, named resetpass_token which stores the keys (i.e tokens) for the JsonTray (which I named passwordreset and it accesses passwordreset.json). And I made the JsonTray non-persistent, i.e it has a definite size.


Path passwordreset_path = settings_dir.resolve("passwordreset.json");
passwordreset = new JsonTray(passwordreset_path.toFile(), 10000);
OS.protectPath(passwordreset_path);

And ClientCredential.Type looks like:


public enum Type {
    	passwd_login(true),
        cookie(false),
        access_token(false),
        resetpass_token(false),
        host(false);
        private final boolean persistent;
        Type(final boolean persistent) {
            this.persistent = persistent;
        }
        public boolean isPersistent() {
            return this.persistent;
        }
    }

So here’s what we do once we get forgotemail (i.e the Email ID whose password is forgotten): We create a random token, add it as a ClientCredential.Type.access_token key to passwordreset.json, and then to the JSONObject we add its expiry time, and the ID etc. Basically, resetauth is an Authentication object which points to the JSONObject with the appropriate token. So, this is the code to be added in place of //code redacted as written above:


ClientCredential credential = new ClientCredential(ClientCredential.Type.passwd_login, usermail);
		ClientIdentity identity = new ClientIdentity(ClientIdentity.Type.email, credential.getName());

		if (!DAO.authentication.has(credential.toString())) {
			throw new APIException(422, "email does not exist");
		}

		String token = createRandomString(30);
		ClientCredential tokenkey = new ClientCredential(ClientCredential.Type.resetpass_token, token);
		Authentication resetauth = new Authentication(tokenkey, DAO.passwordreset);
		resetauth.setIdentity(identity);
		resetauth.setExpireTime(7 * 24 * 60 * 60);
		resetauth.put("one_time", true);

This completes most of PasswordRecoveryService, except one part, which I’ll now come to.

So now, when the email is sent, we store the token and the corresponding email ID in a non-persistent database. And now, the user clicks on the link in the email. The link has a token and it leads to the Password Reset page. We now need to figure out whose email ID’s password needs to be reset, given the token and the database, and then display that email ID to the user (so that he knows he’s resetting the right email ID). So this is the last part of the code of PasswordRecoveryService:

In the Password Reset page (resetpass), I made an AJAX GET call to the PasswordRecoveryService servlet, and sent them two parameters: getParameters = true and the token of the url. This is how the AJAX call looks like:


function getParameter(parameter) {  //parses URL and returns the value of the GET parameter

    	var params = window.location.search.substr(1).split('&');
    	
    	for (var i = 0; i < params.length; i++) {
    		var p=params[i].split('=');
    		if (p[0] == parameter) {
    			return p[1];
    		}   
    	}
    	return null;
    }

var urltoken = getParameter('token'); //retrieves value of token from the url

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

And the last part of PasswordRecoveryService, i.e the part which processes this AJAX call, is this:


JSONObject result = new JSONObject();

		// check if token exists

		if (call.get("getParameters", false)) {
			if (call.get("token", null) != null && !call.get("token", null).isEmpty()) {
				ClientCredential credentialcheck = new ClientCredential(ClientCredential.Type.resetpass_token,
						call.get("token", null));
				if (DAO.passwordreset.has(credentialcheck.toString())) {
					Authentication authentication = new Authentication(credentialcheck, DAO.passwordreset);
					if (authentication.checkExpireTime()) {
						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");
						result.put("message", "Email ID: " + authentication.getIdentity().getName());
						result.put("regex", passwordPattern);
						result.put("regexTooltip", passwordPatternTooltip);
						return result;
					}
					authentication.delete();
					throw new APIException(422, "Expired token");
				}
				throw new APIException(422, "Invalid token");
			} else {
				throw new APIException(422, "No token specified");
			}
		}

This code is pretty obvious: we retrieve the token, and if it exists in the passwordreset.json AND the current time is before the expiry date of the token (we get the expiry time using Instant.now.getEpochSecond(), I’m not going into the entire code of the expiry time because it’s a bit long), we retrieve the token’s email ID and display it to the user. Else, it gives an error (Expired Token, Invalid Token or No token as per the condition).

So now at the resetpass page (it’s a simple HTML page so I’ll focus on the JavaScript rather than the page design etc), the user can see his email ID on the screen, he sets a new password, and clicks on Submit. On clicking of the button, we need to reset the password in the actual database, which is Authentication (i.e DAO.authentication).

So the final part of the entire Password Recovery / Reset system comes here, the way to actually reset the password. For this, we create another servlet, the PasswordResetServlet. It accesses the DB, resets the password if it can, and displays the appropriate message to the user. This is the code of PasswordResetServlet: (API endpoint set as /api/resetpassword.json)


JSONObject result = new JSONObject();

		String newpass;
		try {
			newpass = URLDecoder.decode(call.get("newpass", null), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw new APIException(400, "malformed query");
		}

		ClientCredential credential = new ClientCredential(ClientCredential.Type.resetpass_token,
				call.get("token", null));
		Authentication authentication = new Authentication(credential, DAO.passwordreset);
		ClientCredential emailcred = new ClientCredential(ClientCredential.Type.passwd_login,
				authentication.getIdentity().getName());

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

		Pattern pattern = Pattern.compile(passwordPattern);

		if ((authentication.getIdentity().getName()).equals(newpass) || !pattern.matcher(newpass).matches()) {
			// password can't equal email and regex should match
			throw new APIException(400, "invalid password");
		}

		if (DAO.authentication.has(emailcred.toString())) {
			Authentication emailauth = new Authentication(emailcred, DAO.authentication);
			String salt = createRandomString(20);
			emailauth.remove("salt");
			emailauth.remove("passwordHash");
			emailauth.put("salt", salt);
			emailauth.put("passwordHash", getHash(newpass, salt));
		}

		if (authentication.has("one_time") && authentication.getBoolean("one_time")) {
			authentication.delete();
		}
		result.put("message", "Your password has been changed!");
		return result;

On clicking of the submit button, we send the newly set password (in encoded URI format) AND the token from the URL (remember, we had done the token processing earlier in the getParameters ONLY to show the user the email ID he’s resetting, we hadn’t actually done the actual resetting so we send the token back to the PasswordResetServlet). We retrieve both the new password and the token. Then straightaway, we look out for the token in the passwordreset.json, get the email ID from there, then go to Authentication.json. We remove the salt and the passwordHash from there, create a new salt, and store another passwordHash by encoding the newpass (i.e the new password) with the salt. Once this is done, we then go back to passwordreset.json and remove the token from there, since it was one-time, so the token can’t be used again. This finishes the process, and now, the user’s password is reset.

Phew, that was long!

So finally we have seen how the accounts system of loklak_depot was developed. This was just the mere beginning of what’s next to come and was a relatively trivial part (which was only complicated because it was all servlet based and we developed all the security measures ourselves). The next thing will be when loklak_depot’s functionality actually begins: Q&A apps, apps which use the Twitter data and give the user valuable information by using the APIs available. This is what I’ll be working on now, and will start talking about it in my next article. Feedback, as always, is welcome. 🙂

loklak_depot – The Beginning: Accounts (Part 5)