Skip to content

whitef0x0/node-email-verification

Repository files navigation

Node Email Verification

Build Status Gitter

NPM

Verify user signup over email with NodeJS and MongoDB!

The way this works is as follows:

  • temporary user is created with a randomly generated URL assigned to it and then saved to a MongoDB collection
  • email is sent to the email address the user signed up with
  • when the URL is accessed, the user's data is transferred to the real collection

A temporary user document has a TTL of 24 hours by default, but this (as well as many other things) can be configured. See the options section for more details. It is also possible to resend the verification email if needed.

Installation

via npm:

npm install email-verification

Quick Example/Guide

Before you start, make sure you have a directory structure like so:

app/
-- userModel.js
-- tempUserModel.js
node_modules/
server.js

Step 1: Add your dependencies

All of the code in this section takes place in server.js. Note that mongoose has to be passed as an argument when requiring the module:

var User = require('./app/userModel'),
    mongoose = require('mongoose'),
    nev = require('email-verification')(mongoose);
mongoose.connect('mongodb://localhost/YOUR_DB');

Step 2: Configure your settings

Next, make sure to configure the options (see the section below for more extensive detail on this):

nev.configure({
    verificationURL: 'http://myawesomewebsite.com/email-verification/${URL}',
    persistentUserModel: User,
    tempUserCollection: 'myawesomewebsite_tempusers',

    transportOptions: {
        service: 'Gmail',
        auth: {
            user: '[email protected]',
            pass: 'mysupersecretpassword'
        }
    },
    verifyMailOptions: {
        from: 'Do Not Reply <[email protected]>',
        subject: 'Please confirm account',
        html: 'Click the following link to confirm your account:</p><p>${URL}</p>',
        text: 'Please confirm your account by clicking the following link: ${URL}'
    }
}, function(error, options){
});

Note: Any options not included in the object you pass will take on the default value specified in the section below. Calling configure multiple times with new options will simply change the previously defined options.

Step 3: Create a Temporary user Model

To create a temporary user model, you can either generate it using a built-in function, or you can predefine it in a separate file. If you are pre-defining it, it must be IDENTICAL to the user model with an extra field for the URL; the default one is GENERATED_VERIFYING_URL: String.

// configuration options go here...

// generating the model, pass the User model defined earlier
nev.generateTempUserModel(User);

// using a predefined file
var TempUser = require('./app/tempUserModel');
nev.configure({
    tempUserModel: TempUser
}, function(error, options){
});

Step 4: Create a TempUser Model in your Signup Handler

Then, create an instance of the User model, and then pass it as well as a custom callback to createTempUser. Inside your createTempUser callback, make a call to the sendVerificationEmail function.

// get the credentials from request parameters or something
var email = "...",
    password = "...";

var newUser = User({
    email: email,
    password: password
});

nev.createTempUser(newUser, function(err, existingPersistentUser, newTempUser) {
    // some sort of error
    if (err)
        // handle error...

    // user already exists in persistent collection...
    if (existingPersistentUser)
        // handle user's existence... violently.

    // a new user
    if (newTempUser) {
        var URL = newTempUser[nev.options.URLFieldName];
        nev.sendVerificationEmail(email, URL, function(err, info) {
            if (err)
                // handle error...

            // flash message of success
        });

    // user already exists in temporary collection...
    } else {
        // flash message of failure...
    }
});

Step 4.5: Hash your users password

Note: An email will be sent to the email address that the user signed up with. If you are interested in hashing the password (which you probably should be), all you need to do is set the option hashingFunction to a function that takes the parameters password, tempUserData, insertTempUser, callback and returns insertTempUser(hash, tempUserData, callback), e.g.:

// sync version of hashing function
var myHasher = function(password, tempUserData, insertTempUser, callback) {
  var hash = bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
  return insertTempUser(hash, tempUserData, callback);
};

// async version of hashing function
myHasher = function(password, tempUserData, insertTempUser, callback) {
  bcrypt.genSalt(8, function(err, salt) {
    bcrypt.hash(password, salt, function(err, hash) {
      return insertTempUser(hash, tempUserData, callback);
    });
  });
};

Step 5: Confirm your user and save your user to persistent storage

To move a user from the temporary storage to 'persistent' storage (e.g. when they actually access the URL we sent them), we call confirmTempUser, which takes the URL as well as a callback with two parameters: an error, and the instance of the User model (or null if there are any errors, or if the user wasn't found - i.e. their data expired).

If you want to send a confirmation email, note that in your options the shouldSendConfirmation default value is true, which means that on calling confirmTempUser you will automatically send a confirmation e-mail. Creating a call to sendConfirmationEmail will end up sending two confirmation e-mails to the user. In your configurations, you should either have shouldSendConfirmation equal true or use sendConfirmationEmail.

If shouldSendConfirmation is false and you want to send a confirmation email, you need to make a call to the sendConfirmationEmail function, inside the confirmTempUser callback, which takes two parameters: the user's email and a callback. This callback takes two parameters: an error if any occured, and the information returned by Nodemailer.

var url = '...';
nev.confirmTempUser(url, function(err, user) {
    if (err)
        // handle error...

    // user was found!
    if (user) {
        // optional
        nev.sendConfirmationEmail(user['email_field_name'], function(err, info) {
            // redirect to their profile...
        });
    }

    // user's data probably expired...
    else
        // redirect to sign-up
});

Step 5.5: Allow user to resend verification email

If you want the user to be able to request another verification email, simply call resendVerificationEmail, which takes the user's email address and a callback with two parameters: an error, and a boolean representing whether or not the user was found.

var email = '...';
nev.resendVerificationEmail(email, function(err, userFound) {
    if (err)
        // handle error...

    if (userFound)
        // email has been sent
    else
        // flash message of failure...
});

To see a fully functioning example that uses Express as the backend, check out the examples section.

NEV supports Bluebird's PromisifyAll! Check out the examples section for that too.

API

configure(optionsToConfigure, callback(err, options))

Changes the default configuration by passing an object of options to configure (optionsToConfigure); see the section below for a list of all options. options will be the result of the configuration, with the default values specified below if they were not given. If there are no errors, err is null.

generateTempUserModel(UserModel, callback(err, tempUserModel))

Generates a Mongoose Model for the temporary user based off of UserModel, the persistent user model. The temporary model is essentially a duplicate of the persistent model except that it has the field {GENERATED_VERIFYING_URL: String} for the randomly generated URL by default (the field name can be changed in the options). If the persistent model has the field createdAt, then an expiration time (expires) is added to it with a default value of 24 hours; otherwise, the field is created as such:

{
    ...
    createdAt: {
        type: Date,
        expires: 86400,
        default: Date.now
    }
    ...
}

tempUserModel is the Mongoose model that is created for the temporary user. If there are no errors, err is null.

Note that createdAt will not be transferred to persistent storage (yet?).

createTempUser(user, callback(err, newTempUser))

Attempts to create an instance of a temporary user model based off of an instance of a persistent user, user, and add it to the temporary collection. newTempUser is the temporary user instance if the user doesn't exist in the temporary collection, or null otherwise. If there are no errors, err is null.

If a temporary user model hasn't yet been defined (generated or otherwise), err will NOT be null.

sendVerificationEmail(email, url, callback(err, info))

Sends a verification email to to the email provided, with a link to the URL to verify the account. If sending the email succeeds, then err will be null and info will be some value. See Nodemailer's documentation for information.

confirmTempUser(url, callback(err, newPersistentUser))

Transfers a temporary user (found by url) from the temporary collection to the persistent collection and removes the URL assigned with the user. newPersistentUser is the persistent user instance if the user has been successfully transferred (i.e. the user accessed URL before expiration) and null otherwise; this can be used for redirection and what not. If there are no errors, err is null.

sendConfirmationEmail(email, callback(err, info))

Sends a confirmation email to to the email provided. If sending the email succeeds, then err will be null and info will be some value. See Nodemailer's documentation for information.

resendVerificationEmail(email, callback(err, userFound))

Resends the verification email to a user, given their email. userFound is true if the user has been found in the temporary collection (i.e. their data hasn't expired yet) and false otherwise. If there are no errors, err is null.

Options

Here are the default options:

var options = {
    verificationURL: 'http://example.com/email-verification/${URL}',
    URLLength: 48,

    // mongo-stuff
    persistentUserModel: null,
    tempUserModel: null,
    tempUserCollection: 'temporary_users',
    emailFieldName: 'email',
    passwordFieldName: 'password',
    URLFieldName: 'GENERATED_VERIFYING_URL',
    expirationTime: 86400,

    // emailing options
    transportOptions: {
        service: 'Gmail',
        auth: {
            user: '[email protected]',
            pass: 'password'
        }
    },
    verifyMailOptions: {
        from: 'Do Not Reply <[email protected]>',
        subject: 'Confirm your account',
        html: '<p>Please verify your account by clicking <a href="${URL}">this link</a>. If you are unable to do so, copy and ' +
                'paste the following link into your browser:</p><p>${URL}</p>',
        text: 'Please verify your account by clicking the following link, or by copying and pasting it into your browser: ${URL}'
    },
    shouldSendConfirmation: true,
    confirmMailOptions: {
        from: 'Do Not Reply <[email protected]>',
        subject: 'Successfully verified!',
        html: '<p>Your account has been successfully verified.</p>',
        text: 'Your account has been successfully verified.'
    },

    hashingFunction: null,
}
  • verificationURL: the URL for the user to click to verify their account. ${URL} determines where the randomly generated part of the URL goes, and is needed. Required.

  • URLLength: the length of the randomly-generated string. Must be a positive integer. Required.

  • persistentUserModel: the Mongoose Model for the persistent user.

  • tempUserModel: the Mongoose Model for the temporary user. you can generate the model by using generateTempUserModel and passing it the persistent User model you have defined, or you can define your own model in a separate file and pass it as an option in configure instead.

  • tempUserCollection: the name of the MongoDB collection for temporary users.

  • emailFieldName: the field name for the user's email. If the field is nested within another object(s), use dot notation to access it, e.g. {local: {email: ...}} would use 'local.email'. Required.

  • passwordFieldName: the field name for the user's password. If the field is nested within another object(s), use dot notation to access it (see above). Required.

  • URLFieldName: the field name for the randomly-generated URL. Required.

  • expirationTime: the amount of time that the temporary user will be kept in collection, measured in seconds. Must be a positive integer. Required.

  • transportOptions: the options that will be passed to nodemailer.createTransport.

  • verifyMailOptions: the options that will be passed to nodemailer.createTransport({...}).sendMail when sending an email for verification. You must include ${URL} somewhere in the html and/or text fields to put the URL in these strings.

  • shouldSendConfirmation: send an email upon the user verifiying their account to notify them of verification.

  • confirmMailOptions: the options that will be passed to nodemailer.createTransport({...}).sendMail when sending an email to notify the user that their account has been verified. You must include ${URL} somewhere in the html and/or text fields to put the URL in these strings.

  • hashingFunction: the function that hashes passwords. Must take four parameters password, tempUserData, insertTempUser, callback and return insertTempUser(hash, tempUserData, callback).

Development

To beautify the code:

npm run format:main
npm run format:examples
npm run format:test
npm run format  # runs all

To lint the code (will error if there are any warnings):

npm run lint:main
npm run lint:examples
npm run lint:test
npm run lint  # runs all

To test:

npm test

Acknowledgements

thanks to Dakota St. Lauren for starting this project thanks to Frank Cash for looking over the code and adding tests.

license

ISC

About

✔️ Send user verification emails with NodeJS and MongoDB

Resources

Stars

Watchers

Forks

Packages

No packages published