Saturday, January 9, 2010

Validating User Emails

First step in adding user email addresses is pretty basic.

We need to add an email field to the User object, and add it to the ProfileServlet form and processing, so that the user can enter the email address. We'll also need a field that tells us if the email has been validated or not. That field won't be user editable via the profile form.

I'll leave the first step as an exercise for the reader (it uses only techniques we've already seen), and move on to email verification.

The basic strategy with email verification is threefold:

1) The user enters their email address in their profile

2) The web site sends an email to that email address, with a link to click

3) The user clicks that link, taking them back to the web site

That way, we know that the user entered a valid email address they control.

Step 1 is pretty basic and uses techniques we've already covered. Step 2 requires sending email, which we'll look at. Step 3 requires having a page the link in the email goes to, where we can mark the email as verified.

Tying together step 2 and step 3 means we'll have to generate a unique code for this particular email verification. This unique code needs to be impossible to predict, so users entering false email addresses cannot simply guess the verification code. It also needs to be something that can be used as a query parameter in a URL. An MD5 hash is a good candidate, and we can check the database to make sure it's unique before sending the email.

Okay, lots to do!

1) Create a class to send the activation email

2) Create a servlet to receive the click from the activation email. Either mark the email as verified, or tell the user the verification email cannot be found

3) Change ProfileServlet to automatically send the activation email when a new email is entered, and to set the verified status to false

4) Change ProfileServlet to provide a link to resend the activation email (only display when the email isn't verified)

Also, the GAE Mail API allows an application to receive email. The email gets transformed into an HTTP request to a servlet, which can then save the email text in the datastore or take some other action based on the email. So you can do some pretty sophisticated email driven functionality (such as allowing people to send an email to your app to get a listing of contests in their area). For now, we'll use this feature just to handle when the verification email bounces.

Sending Emails

Since this is something we might have to do often, I'll write a MailUtil class that will hide a little of the complexity. Error handling will still be exposed, since we'll need to display an error message on the web page. Here's what the only method so far in the MailUtil class will look like:


public static void sendOne (String subject, String body, String name, String address, String fromName, String fromAddress) throws Exception
{
Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);

Message msg = new MimeMessage(session);

msg.setFrom(new InternetAddress(fromAddress, fromName));
msg.addRecipient(Message.RecipientType.TO,
new InternetAddress(address, name));
msg.setSubject(subject);
msg.setText(body);
Transport.send(msg);
}


To put multiple lines in your message body, use the standard \n escape in the body text.

Note that running this in Eclipse doesn't seem to actually send email. Before this will work, you'll have to get an actual application created on Google App Engine for Java, and deploy the application. But you can safely call the method to send email when running it in Eclipse...it just won't send the email.

Verification Servlet

It might seem odd to create the servlet to respond to the user clicking the link in the email when we haven't written the code that generates that link yet. But it all has to be done in some order, so I'll do the processing of the click first.

I'll start by copying FrontPage.java and calling it ValidateEmail. I'll also add in web.xml mappings so that /validate-email calls the ValidateEmail servlet.

To recap the changes to User to support all this, I added in an email field (a String), an emailVerified field (a boolean that starts out as false), and a verificationCode field (String).

In the verification servlet, I want to look at the verification code passed in as a query parameter, and use it to look up the matching User in the datastore. The verification codes will be unique, so I'll only get zero or one User matching it.

If it's one User, I'll mark the email as verified. Otherwise, I display an error message. Here's a portion of that code.


Map root = new HashMap();

String code = req.getParameter("code");

query = pm.newQuery("select from omega.server.User where verificationCode == codeParam");
query.declareParameters("String codeParam");

try
{
results = (List<User>) query.execute(code);

if (results.size () == 0)
root.put ("errorMessage", "Verification code not found. Try resending the verification email.");
else
{
user = results.get(0);
user.setEmailVerified(true);
pm.makePersistent(user);
root.put ("errorMessage", "Email verified!");
}
}
finally
{
query.closeAll();
pm.close();
}

root.put("heading", "Email Verification Results");
root.put("loggedIn", loggedIn);
root.put("name", name);
root.put("userLevel", userLevel);
root.put("title", "GamesByTeens Email Verification");

Template temp = config.getTemplate("error.ftl");

All of that is added toward the bottom of the copy of the FrontPage servlet (the root.put statements show where the overlap is.

To test this I first load the page with a bogus verification code, to make sure it displays the error. Then I would need to force a valid verification code into the datastore. I can do this up at the start of the verification servlet itself, to make sure that the code I wrote works. Just don't forget to remove your testing code after you're done (or at least comment it out in case you need to use it again later).

This highlights an important part of any development, but web development especially. Make sure your code works on its own before you try to hook it to other parts of your site.

That's enough for one post. In the next I'll come back to the last two steps, both of which are modifications to the ProfileServlet.

No comments:

Post a Comment