Repository: whitef0x0/node-email-verification
Branch: master
Commit: b45be17e6951
Files: 13
Total size: 55.6 KB
Directory structure:
gitextract_zjuzp3q7/
├── .github/
│ └── PULL_REQUEST_TEMPLATE
├── .gitignore
├── .jshintrc
├── .npmignore
├── .travis.yml
├── README.md
├── examples/
│ └── express/
│ ├── app/
│ │ └── userModel.js
│ ├── index.html
│ ├── server.js
│ └── server_promisified.js
├── index.js
├── package.json
└── test/
└── tests.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/PULL_REQUEST_TEMPLATE
================================================
#### What's in this pull request?
# Fixed confirmTempUser() function to copy all the properties
# of the tempUserData variable to userData.
#### Bugs Squashed
Resolves issue #68.
#### Changes proposed
- Used updated version of nodemailer to ^4.0.1.
- Changed TempUser.findOne() query in the confirmTempUser() function in index.js,
- the previous line has been commented out and added another one after variable declaration
- in if(tempUserData) statement.
================================================
FILE: .gitignore
================================================
.idea/
node_modules
*.log
*.sublime-*
TEST.js
tempuser.js
# compiled markdown
README.html
.DS_Store
development.js
================================================
FILE: .jshintrc
================================================
{
"maxerr" : 100, // {int} Maximum error before stopping
// Enforcing
"bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : false, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
"freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
"immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
"indent" : 2, // {int} Number of spaces to use for indentation
"latedef" : true, // true: Require variables/functions to be defined before being used
"newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
"noempty" : true, // true: Prohibit use of empty blocks
"nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters.
"nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
"plusplus" : false, // true: Prohibit use of `++` & `--`
"quotmark" : "single", // Quotation mark consistency:
// false : do nothing (default)
// true : ensure whatever is used is consistent
// "single" : require single quotes
// "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : true, // true: Require all defined variables be used
"strict" : false, // true: Requires all functions run in ES5 Strict Mode
"maxparams" : false, // {int} Max number of formal params allowed per function
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
"maxstatements" : false, // {int} Max number statements per function
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
"maxlen" : false, // {int} Max number of characters per line
// Relaxing
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"boss" : false, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // true: Tolerate use of `== null`
"esversion" : "6",
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// (ex: `for each`, multiple try/catch, function expression…)
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
"expr" : false, // true: Tolerate `ExpressionStatement` as Programs
"funcscope" : false, // true: Tolerate defining variables inside control statements
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
"iterator" : false, // true: Tolerate using the `__iterator__` property
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
"laxcomma" : false, // true: Tolerate comma-first style coding
"loopfunc" : false, // true: Tolerate functions being defined in loops
"multistr" : false, // true: Tolerate multi-line strings
"noyield" : false, // true: Tolerate generator functions with no yield statement in them.
"notypeof" : false, // true: Tolerate invalid typeof operator values
"proto" : false, // true: Tolerate using the `__proto__` property
"scripturl" : false, // true: Tolerate script-targeted URLs
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
"validthis" : false, // true: Tolerate using this in a non-constructor function
// Environments
"browser" : true, // Web Browser (window, document, etc)
"browserify" : false, // Browserify (node.js code in the browser)
"couch" : false, // CouchDB
"devel" : true, // Development/debugging (alert, confirm, etc)
"dojo" : false, // Dojo Toolkit
"jasmine" : false, // Jasmine
"jquery" : false, // jQuery
"mocha" : true, // Mocha
"mootools" : false, // MooTools
"node" : true, // Node.js
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
"prototypejs" : false, // Prototype and Scriptaculous
"qunit" : false, // QUnit
"rhino" : false, // Rhino
"shelljs" : false, // ShellJS
"worker" : false, // Web Workers
"wsh" : false, // Windows Scripting Host
"yui" : false, // Yahoo User Interface
// Custom Globals
"globals" : {} // additional predefined global variables
}
================================================
FILE: .npmignore
================================================
examples
*.sublime-*
*.log
README.html
test
.github
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "7"
- "6"
- "6.1"
- "5.11"
services:
- mongodb
================================================
FILE: README.md
================================================
#
Node Email Verification
[](https://travis-ci.org/whitef0x0/node-email-verification)
[](https://gitter.im/whitef0x0/node-email-verification?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[](https://nodei.co/npm/email-verification/)
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:
```javascript
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):
```javascript
nev.configure({
verificationURL: 'http://myawesomewebsite.com/email-verification/${URL}',
persistentUserModel: User,
tempUserCollection: 'myawesomewebsite_tempusers',
transportOptions: {
service: 'Gmail',
auth: {
user: 'myawesomeemail@gmail.com',
pass: 'mysupersecretpassword'
}
},
verifyMailOptions: {
from: 'Do Not Reply
${URL}
', 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`. ```javascript // 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. ```javascript // 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.: ```javascript // 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. ```javascript 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. ```javascript 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**](https://github.com/SaintDako/node-email-verification/tree/master/examples/express). **NEV supports Bluebird's PromisifyAll!** Check out the examples section for that too. ## API * [`configure`](#configure) * [`generateTempUserModel`](#generateTempUserModel) * [`createTempUser`](#createTempUser) * [`sendVerificationEmail`](#sendVerificationEmail) * [`confirmTempUser`](#confirmTempUser) * [`sendConfirmationEmail`](#Options) * [`resendVerificationEmail`](#resendVerificationEmail) * [`Options`](#Options) ### `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: ```javascript { ... 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](https://github.com/andris9/Nodemailer#sending-mail) 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](https://github.com/andris9/Nodemailer#sending-mail) 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: ```javascript 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: 'user@gmail.com', pass: 'password' } }, verifyMailOptions: { from: 'Do Not ReplyPlease verify your account by clicking this link. If you are unable to do so, copy and ' + 'paste the following link into your browser:
${URL}
', 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 ReplyYour account has been successfully verified.
', 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](https://github.com/SaintDako) for starting this project thanks to [Frank Cash](https://github.com/frankcash) for looking over the code and adding tests. ### license ISC ================================================ FILE: examples/express/app/userModel.js ================================================ var mongoose = require('mongoose'), bcrypt = require('bcryptjs'); var userSchema = mongoose.Schema({ email: String, pw: String, }); userSchema.methods.validPassword = function(password) { return bcrypt.compareSync(password, this.pw); }; module.exports = mongoose.model('real_users', userSchema); ================================================ FILE: examples/express/index.html ================================================
Please verify your account by clicking this link. If you are unable to do so, copy and ' + 'paste the following link into your browser:
${URL}
', text: 'Please verify your account by clicking the following link, or by copying and pasting it into your browser: ${URL}' }, verifySendMailCallback: function(err, info) { if (err) { throw err; } else { console.log(info.response); } }, shouldSendConfirmation: true, confirmMailOptions: { from: 'Do Not ReplyYour account has been successfully verified.
', text: 'Your account has been successfully verified.' }, confirmSendMailCallback: function(err, info) { if (err) { throw err; } else { console.log(info.response); } }, hashingFunction: null, }; var transporter; /** * Modify the default configuration. * * @func configure * @param {object} o - options to be changed */ var configure = function(optionsToConfigure, callback) { for (let key in optionsToConfigure) { if (optionsToConfigure.hasOwnProperty(key)) { options[key] = optionsToConfigure[key]; } } transporter = nodemailer.createTransport(options.transportOptions); var err; if (typeof options.verificationURL !== 'string') { err = err || createOptionError('verificationURL', options.verificationURL, 'string'); } else if (options.verificationURL.indexOf('${URL}') === -1) { err = err || new Error('Verification URL does not contain ${URL}'); } if (typeof options.URLLength !== 'number') { err = err || createOptionError('URLLength', options.URLLength, 'number'); } else if (!isPositiveInteger(options.URLLength)) { err = err || new Error('URLLength must be a positive integer'); } if (typeof options.tempUserCollection !== 'string') { err = err || createOptionError('tempUserCollection', options.tempUserCollection, 'string'); } if (typeof options.emailFieldName !== 'string') { err = err || createOptionError('emailFieldName', options.emailFieldName, 'string'); } if (typeof options.passwordFieldName !== 'string') { err = err || createOptionError('passwordFieldName', options.passwordFieldName, 'string'); } if (typeof options.URLFieldName !== 'string') { err = err || createOptionError('URLFieldName', options.URLFieldName, 'string'); } if (typeof options.expirationTime !== 'number') { err = err || createOptionError('expirationTime', options.expirationTime, 'number'); } else if (!isPositiveInteger(options.expirationTime)) { err = err || new Error('expirationTime must be a positive integer'); } if (err) { return callback(err, null); } return callback(null, options); }; /** * Create a Mongoose Model for the temporary user, based off of the persistent * User model, i.e. the temporary user inherits the persistent user. An * additional field for the URL is created, as well as a TTL. * * @func generateTempUserModel * @param {object} User - the persistent User model. * @return {object} the temporary user model */ var generateTempUserModel = function(User, callback) { if (!User) { return callback(new TypeError('Persistent user model undefined.'), null); } var tempUserSchemaObject = {}, // a copy of the schema tempUserSchema; // copy over the attributes of the schema Object.keys(User.schema.paths).forEach(function(field) { tempUserSchemaObject[field] = User.schema.paths[field].options; }); tempUserSchemaObject[options.URLFieldName] = String; // create a TTL tempUserSchemaObject.createdAt = { type: Date, expires: options.expirationTime.toString() + 's', default: Date.now }; tempUserSchema = mongoose.Schema(tempUserSchemaObject); // copy over the methods of the schema Object.keys(User.schema.methods).forEach(function(meth) { // tread lightly tempUserSchema.methods[meth] = User.schema.methods[meth]; }); options.tempUserModel = mongoose.model(options.tempUserCollection, tempUserSchema); return callback(null, mongoose.model(options.tempUserCollection)); }; /** * Helper function for actually inserting the temporary user into the database. * * @func insertTempUser * @param {string} password - the user's password, possibly hashed * @param {object} tempUserData - the temporary user's data * @param {function} callback - a callback function, which takes an error and the * temporary user object as params * @return {function} returns the callback function */ var insertTempUser = function(password, tempUserData, callback) { // password may or may not be hashed tempUserData[options.passwordFieldName] = password; var newTempUser = new options.tempUserModel(tempUserData); newTempUser.save(function(err, tempUser) { if (err) { return callback(err, null, null); } return callback(null, null, tempUser); }); }; /** * Attempt to create an instance of a temporary user based off of an instance of a * persistent user. If user already exists in the temporary collection, passes null * to the callback function; otherwise, passes the instance to the callback, with a * randomly generated URL associated to it. * * @func createTempUser * @param {object} user - an instance of the persistent User model * @param {function} callback - a callback function that takes an error (if one exists), * a persistent user (if it exists) and the new temporary user as arguments; if the * temporary user already exists, then null is returned in its place * @return {function} returns the callback function */ var createTempUser = function(user, callback) { if (!options.tempUserModel) { return callback(new TypeError('Temporary user model not defined. Either you forgot' + 'to generate one or you did not predefine one.'), null); } // create our mongoose query var query = {}; if (options.emailFieldName.split('.').length > 1) { var levels = options.emailFieldName.split('.'); query[levels[0]] = {}; var queryObj = query[levels[0]]; var userObj = user[levels[0]]; for (var i = 0; i < levels.length; i++) { queryObj[levels[i + 1]] = {}; queryObj = queryObj[levels[i + 1]]; userObj = userObj[levels[i + 1]]; } queryObj = userObj; } else { query[options.emailFieldName] = user[options.emailFieldName]; } options.persistentUserModel.findOne(query, function(err, existingPersistentUser) { if (err) { return callback(err, null, null); } // user has already signed up and confirmed their account if (existingPersistentUser) { return callback(null, existingPersistentUser, null); } options.tempUserModel.findOne(query, function(err, existingTempUser) { if (err) { return callback(err, null, null); } // user has already signed up but not yet confirmed their account if (existingTempUser) { return callback(null, null, null); } else { var tempUserData = {}; // copy the credentials for the user Object.keys(user._doc).forEach(function(field) { tempUserData[field] = user[field]; }); tempUserData[options.URLFieldName] = randtoken.generate(options.URLLength); if (options.hashingFunction) { return options.hashingFunction(tempUserData[options.passwordFieldName], tempUserData, insertTempUser, callback); } else { return insertTempUser(tempUserData[options.passwordFieldName], tempUserData, callback); } } }); }); }; /** * Send an email to the user requesting confirmation. * * @func sendVerificationEmail * @param {string} email - the user's email address. * @param {string} url - the unique url generated for the user. * @param {function} callback - the callback to pass to Nodemailer's transporter */ var sendVerificationEmail = function(email, url, callback) { var r = /\$\{URL\}/g; // inject newly-created URL into the email's body and FIRE // stringify --> parse is used to deep copy var URL = options.verificationURL.replace(r, url), mailOptions = JSON.parse(JSON.stringify(options.verifyMailOptions)); mailOptions.to = email; mailOptions.html = mailOptions.html.replace(r, URL); mailOptions.text = mailOptions.text.replace(r, URL); if (!callback) { callback = options.verifySendMailCallback; } transporter.sendMail(mailOptions, callback); }; /** * Send an email to the user requesting confirmation. * * @func sendConfirmationEmail * @param {string} email - the user's email address. * @param {function} callback - the callback to pass to Nodemailer's transporter */ var sendConfirmationEmail = function(email, callback) { var mailOptions = JSON.parse(JSON.stringify(options.confirmMailOptions)); mailOptions.to = email; if (!callback) { callback = options.confirmSendMailCallback; } transporter.sendMail(mailOptions, callback); }; /** * Transfer a temporary user from the temporary collection to the persistent * user collection, removing the URL assigned to it. * * @func confirmTempUser * @param {string} url - the randomly generated URL assigned to a unique email */ var confirmTempUser = function(url, callback) { var TempUser = options.tempUserModel, query = {}; query[options.URLFieldName] = url; TempUser.findOne(query, function(err, tempUserData) { if (err) { return callback(err, null); } // temp user is found (i.e. user accessed URL before their data expired) if (tempUserData) { // var userData = JSON.parse(JSON.stringify(tempUserData)), var userData = {}, User = options.persistentUserModel, user; for (var property in tempUserData) userData[property] = tempUserData[property]; delete userData[options.URLFieldName]; user = new User(userData); // save the temporary user to the persistent user collection user.save(function(err, savedUser) { if (err) { return callback(err, null); } TempUser.remove(query, function(err) { if (err) { return callback(err, null); } if (options.shouldSendConfirmation) { sendConfirmationEmail(savedUser[options.emailFieldName], null); } return callback(null, user); }); }); // temp user is not found (i.e. user accessed URL after data expired, or something else...) } else { return callback(null, null); } }); }; /** * Resend the verification email to the user given only their email. * * @func resendVerificationEmail * @param {object} email - the user's email address */ var resendVerificationEmail = function(email, callback) { var query = {}; query[options.emailFieldName] = email; options.tempUserModel.findOne(query, function(err, tempUser) { if (err) { return callback(err, null); } // user found (i.e. user re-requested verification email before expiration) if (tempUser) { // generate new user token tempUser[options.URLFieldName] = randtoken.generate(options.URLLength); tempUser.save(function(err) { if (err) { return callback(err, null); } sendVerificationEmail(getNestedValue(tempUser, options.emailFieldName), tempUser[options.URLFieldName], function(err) { if (err) { return callback(err, null); } return callback(null, true); }); }); } else { return callback(null, false); } }); }; return { options: options, configure: configure, generateTempUserModel: generateTempUserModel, createTempUser: createTempUser, confirmTempUser: confirmTempUser, resendVerificationEmail: resendVerificationEmail, sendConfirmationEmail: sendConfirmationEmail, sendVerificationEmail: sendVerificationEmail, }; }; ================================================ FILE: package.json ================================================ { "name": "email-verification", "version": "0.4.6", "description": "Verify email sign-up using MongoDB.", "main": "index.js", "scripts": { "format:examples": "js-beautify -r examples/**/*.js", "format:main": "js-beautify -r index.js", "format:test": "js-beautify -s 2 -r test/*.js", "format": "npm run format:main && npm run format:examples && npm run format:test", "lint:examples": "jshint --reporter=node_modules/jshint-stylish examples/**/*.js", "lint:main": "jshint --reporter=node_modules/jshint-stylish index.js", "lint:test": "jshint --reporter=node_modules/jshint-stylish test/*.js", "lint": "npm run lint:main && npm run lint:examples && npm run lint:test", "test": "mocha" }, "repository": { "type": "git", "url": "https://github.com/whitef0x0/node-email-verification" }, "keywords": [ "mongodb", "auth", "authentication", "email" ], "author": "Dakota St. Laurent