Add basic email verification
This commit is contained in:
parent
2f13c53161
commit
42766a28c0
77
README.md
77
README.md
|
@ -866,7 +866,7 @@ Setup an authentication strategy.
|
|||
|
||||
#### Login a User
|
||||
|
||||
Create a session for a user. When called remotely the password is required.
|
||||
Create a session for a user.
|
||||
|
||||
User.login({username: 'foo', password: 'bar'}, function(err, session) {
|
||||
console.log(session);
|
||||
|
@ -893,70 +893,55 @@ You must provide a username and password over rest. To ensure these values are e
|
|||
"uid": "123"
|
||||
}
|
||||
|
||||
**Note:** The `uid` type will be the same type you specify when creating your model. In this case it is a string.
|
||||
|
||||
#### Logout a User
|
||||
|
||||
User.logout({username: 'foo'}, function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
**Note:** When calling this method remotely, the first argument will automatically be populated with the current user's id. If the caller is not logged in the method will fail with an error status code `401`.
|
||||
**Note:** When calling this method remotely, the first argument will be populated with the current user's id. If the caller is not logged in the method will fail with an error status code `401`.
|
||||
|
||||
#### Verify Email Addresses
|
||||
|
||||
To require email verification before a user is allowed to login, supply a verification property with a `verify` settings object.
|
||||
Require a user to verify their email address before being able to login. This will send an email to the user containing a link to verify their address. Once the user follows the link they will be redirected to `/` and be able to login normally.
|
||||
|
||||
// define a User model
|
||||
var User = asteroid.User.extend(
|
||||
'user',
|
||||
{
|
||||
email: {
|
||||
type: 'EmailAddress',
|
||||
username: true
|
||||
},
|
||||
password: {
|
||||
hideRemotely: true, // default for Password
|
||||
type: 'Password',
|
||||
min: 4,
|
||||
max: 26
|
||||
},
|
||||
verified: {
|
||||
hideRemotely: true,
|
||||
type: 'Boolean',
|
||||
verify: {
|
||||
// the model field
|
||||
// that contains the email
|
||||
// to verify
|
||||
email: 'email',
|
||||
template: 'email.ejs',
|
||||
redirect: '/'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
// the model field
|
||||
// that contains the user's email
|
||||
// for verification and password reset
|
||||
// defaults to 'email'
|
||||
email: 'email',
|
||||
resetTemplate: 'reset.ejs'
|
||||
}
|
||||
);
|
||||
User.requireEmailVerfication = true;
|
||||
User.afterRemote('create', function(ctx, user, next) {
|
||||
var options = {
|
||||
type: 'email',
|
||||
to: user.email,
|
||||
from: 'noreply@myapp.com',
|
||||
subject: 'Thanks for Registering at FooBar',
|
||||
text: 'Please verify your email address!'
|
||||
template: 'verify.ejs',
|
||||
redirect: '/'
|
||||
};
|
||||
|
||||
user.verify(options, next);
|
||||
});
|
||||
|
||||
When a user is created (on the server or remotely) and the verification property exists, an email is sent to the field that corresponds to `verify.email` or `options.email`. The email contains a link the user must navigate to in order to verify their email address. Once they verify, users are allowed to login normally. Otherwise login attempts will respond with a 'must verify' error.
|
||||
|
||||
#### Send Reset Password Email
|
||||
|
||||
Send an email to the user's supplied email address containing a link to reset their password.
|
||||
|
||||
User.sendResetPasswordEmail(email, function(err) {
|
||||
// email sent
|
||||
|
||||
User.reset(email, function(err) {
|
||||
console.log('email sent');
|
||||
});
|
||||
|
||||
#### Remote Password Reset
|
||||
|
||||
The password reset email will send users to a page rendered by asteroid with fields required to reset the user's password. You may customize this template by providing a `resetTemplate` option when defining your user model.
|
||||
The password reset email will send users to a page rendered by asteroid with fields required to reset the user's password. You may customize this template by defining a `resetTemplate` setting.
|
||||
|
||||
User.settings.resetTemplate = 'reset.ejs';
|
||||
|
||||
#### Remote Password Reset Confirmation
|
||||
|
||||
Confirm the password reset.
|
||||
|
||||
User.confirmReset(token, function(err) {
|
||||
console.log(err || 'your password was reset');
|
||||
});
|
||||
|
||||
### Email Model
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
var express = require('express')
|
||||
, fs = require('fs')
|
||||
, ejs = require('ejs')
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
, path = require('path')
|
||||
, proto = require('./application')
|
||||
|
@ -212,7 +213,7 @@ asteroid.createModel = function (name, properties, options) {
|
|||
if(this.app) {
|
||||
var remotes = this.app.remotes();
|
||||
remotes.before(self.pluralModelName + '.' + name, function (ctx, next) {
|
||||
fn(ctx, ctx.instance, next);
|
||||
fn(ctx, ctx.result, next);
|
||||
});
|
||||
} else {
|
||||
var args = arguments;
|
||||
|
@ -228,7 +229,7 @@ asteroid.createModel = function (name, properties, options) {
|
|||
if(this.app) {
|
||||
var remotes = this.app.remotes();
|
||||
remotes.after(self.pluralModelName + '.' + name, function (ctx, next) {
|
||||
fn(ctx, ctx.instance, next);
|
||||
fn(ctx, ctx.result, next);
|
||||
});
|
||||
} else {
|
||||
var args = arguments;
|
||||
|
@ -257,10 +258,27 @@ asteroid.remoteMethod = function (fn, options) {
|
|||
fn.http = fn.http || {verb: 'get'};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a template helper.
|
||||
*
|
||||
* var render = asteroid.template('foo.ejs');
|
||||
* var html = render({foo: 'bar'});
|
||||
*
|
||||
* @param {String} path Path to the template file.
|
||||
* @returns {Function}
|
||||
*/
|
||||
|
||||
asteroid.template = function (file) {
|
||||
var templates = this._templates || (this._templates = {});
|
||||
var str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8'));
|
||||
return ejs.compile(str);
|
||||
}
|
||||
|
||||
/*
|
||||
* Built in models
|
||||
* Built in models / services
|
||||
*/
|
||||
|
||||
asteroid.Model = asteroid.createModel('model');
|
||||
asteroid.Email = require('./models/email');
|
||||
asteroid.User = require('./models/user');
|
||||
asteroid.Session = require('./models/session');
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* Module Dependencies.
|
||||
*/
|
||||
|
||||
var Model = require('../asteroid').Model
|
||||
, asteroid = require('../asteroid')
|
||||
, mailer = require("nodemailer");
|
||||
|
||||
/**
|
||||
* Default Email properties.
|
||||
*/
|
||||
|
||||
var properties = {
|
||||
to: {type: String, required: true},
|
||||
from: {type: String, required: true},
|
||||
subject: {type: String, required: true},
|
||||
text: {type: String},
|
||||
html: {type: String}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extends from the built in `asteroid.Model` type.
|
||||
*/
|
||||
|
||||
var Email = module.exports = Model.extend('email', properties);
|
||||
|
||||
/*!
|
||||
* Setup the Email class after extension.
|
||||
*/
|
||||
|
||||
Email.setup = function (settings) {
|
||||
settings = settings || this.settings;
|
||||
var transports = settings.transports || [];
|
||||
|
||||
transports.forEach(this.setupTransport.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a transport to the available transports. See https://github.com/andris9/Nodemailer#setting-up-a-transport-method.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* Email.setupTransport({
|
||||
* type: 'SMTP',
|
||||
* host: "smtp.gmail.com", // hostname
|
||||
* secureConnection: true, // use SSL
|
||||
* port: 465, // port for secure SMTP
|
||||
* auth: {
|
||||
* user: "gmail.user@gmail.com",
|
||||
* pass: "userpass"
|
||||
* }
|
||||
* });
|
||||
*
|
||||
*/
|
||||
|
||||
Email.setupTransport = function (setting) {
|
||||
var Email = this;
|
||||
Email.transports = Email.transports || [];
|
||||
Email.transportsIndex = Email.transportsIndex || {};
|
||||
var transport = mailer.createTransport(setting.type, setting);
|
||||
Email.transportsIndex[setting.type] = transport;
|
||||
Email.transports.push(transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email with the given `options`.
|
||||
*
|
||||
* Example Options:
|
||||
*
|
||||
* {
|
||||
* from: "Fred Foo ✔ <foo@blurdybloop.com>", // sender address
|
||||
* to: "bar@blurdybloop.com, baz@blurdybloop.com", // list of receivers
|
||||
* subject: "Hello ✔", // Subject line
|
||||
* text: "Hello world ✔", // plaintext body
|
||||
* html: "<b>Hello world ✔</b>" // html body
|
||||
* }
|
||||
*
|
||||
* See https://github.com/andris9/Nodemailer for other supported options.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Function} callback Called after the e-mail is sent or the sending failed
|
||||
*/
|
||||
|
||||
Email.send = function (options, fn) {
|
||||
var transport = this.transportsIndex[options.transport || 'SMTP'] || this.transports[0];
|
||||
assert(transport, 'You must supply an Email.settings.transports array containing at least one transport');
|
||||
|
||||
transport.sendMail(options, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the node mailer object.
|
||||
*
|
||||
* Email.mailer
|
||||
* // or
|
||||
* var email = new Email({to: 'foo@bar.com', from: 'bar@bat.com'});
|
||||
* email.mailer
|
||||
*/
|
||||
|
||||
Email.mailer =
|
||||
Email.prototype.mailer = mailer;
|
||||
|
||||
/**
|
||||
* Send an email instance using `Email.send()`.
|
||||
*/
|
||||
|
||||
Email.prototype.send = function (fn) {
|
||||
this.constructor.send(this, fn);
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
var Model = require('../asteroid').Model
|
||||
, asteroid = require('../asteroid')
|
||||
, path = require('path')
|
||||
, crypto = require('crypto')
|
||||
, passport = require('passport')
|
||||
, LocalStrategy = require('passport-local').Strategy;
|
||||
|
||||
|
@ -13,7 +15,7 @@ var Model = require('../asteroid').Model
|
|||
|
||||
var properties = {
|
||||
id: {type: String, required: true},
|
||||
realm: {type: String},
|
||||
realm: {type: String, },
|
||||
username: {type: String, required: true},
|
||||
password: {type: String, transient: true}, // Transient property
|
||||
hash: {type: String}, // Hash code calculated from sha256(realm, username, password, salt, macKey)
|
||||
|
@ -21,6 +23,7 @@ var properties = {
|
|||
macKey: {type: String}, // HMAC to calculate the hash code
|
||||
email: String,
|
||||
emailVerified: Boolean,
|
||||
verificationToken: String,
|
||||
credentials: [
|
||||
'UserCredential' // User credentials, private or public, such as private/public keys, Kerberos tickets, oAuth tokens, facebook, google, github ids
|
||||
],
|
||||
|
@ -91,6 +94,32 @@ User.login = function (credentials, fn) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout a user with the given session id.
|
||||
*
|
||||
* User.logout('asd0a9f8dsj9s0s3223mk', function (err) {
|
||||
* console.log(err || 'Logged out');
|
||||
* });
|
||||
*
|
||||
* @param {String} sessionID
|
||||
*/
|
||||
|
||||
User.logout = function (sid, fn) {
|
||||
var UserCtor = this;
|
||||
|
||||
var Session = UserCtor.settings.session || asteroid.Session;
|
||||
|
||||
Session.findById(sid, function (err, session) {
|
||||
if(err) {
|
||||
fn(err);
|
||||
} else if(session) {
|
||||
session.destroy(fn);
|
||||
} else {
|
||||
fn(new Error('could not find session'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the given `password` with the users hashed password.
|
||||
*
|
||||
|
@ -103,19 +132,114 @@ User.prototype.hasPassword = function (plain, fn) {
|
|||
fn(null, this.password === plain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a user's identity.
|
||||
*
|
||||
* var options = {
|
||||
* type: 'email',
|
||||
* to: user.email,
|
||||
* template: 'verify.ejs',
|
||||
* redirect: '/'
|
||||
* };
|
||||
*
|
||||
* user.verify(options, next);
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
User.prototype.verify = function (options, fn) {
|
||||
var user = this;
|
||||
assert(typeof options === 'object', 'options required when calling user.verify()');
|
||||
assert(options.type, 'You must supply a verification type (options.type)');
|
||||
assert(options.type === 'email', 'Unsupported verification type');
|
||||
assert(options.to || this.email, 'Must include options.to when calling user.verify() or the user must have an email property');
|
||||
assert(options.from, 'Must include options.from when calling user.verify() or the user must have an email property');
|
||||
|
||||
options.redirect = options.redirect || '/';
|
||||
options.template = path.resolve(options.template || path.join(__dirname, '..', '..', 'templates', 'verify.ejs'));
|
||||
options.user = this;
|
||||
options.protocol = options.protocol || 'http';
|
||||
options.host = options.host || 'localhost';
|
||||
options.verifyHref = options.verifyHref ||
|
||||
options.protocol
|
||||
+ '://'
|
||||
+ options.host
|
||||
+ (User.sharedCtor.http.path || '/' + User.pluralModelName)
|
||||
+ User.confirm.http.path;
|
||||
|
||||
|
||||
|
||||
// Email model
|
||||
var Email = options.mailer || this.constructor.settings.email || asteroid.Email;
|
||||
|
||||
crypto.randomBytes(64, function(err, buf) {
|
||||
if(err) {
|
||||
fn(err);
|
||||
} else {
|
||||
user.verificationToken = buf.toString('base64');
|
||||
user.save(function (err) {
|
||||
if(err) {
|
||||
fn(err);
|
||||
} else {
|
||||
sendEmail(user);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// TODO - support more verification types
|
||||
function sendEmail(user) {
|
||||
options.verifyHref += '?token=' + user.verificationToken;
|
||||
|
||||
options.text = options.text || 'Please verify your email by opening this link in a web browser:\n\t{href}';
|
||||
|
||||
options.text = options.text.replace('{href}', options.verifyHref);
|
||||
|
||||
var template = asteroid.template(options.template);
|
||||
Email.send({
|
||||
to: options.to || user.email,
|
||||
subject: options.subject || 'Thanks for Registering',
|
||||
text: options.text,
|
||||
html: template(options)
|
||||
}, function (err, email) {
|
||||
if(err) {
|
||||
fn(err);
|
||||
} else {
|
||||
fn(null, {email: email, token: user.verificationToken, uid: user.id});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
User.confirm = function (uid, token, redirect, fn) {
|
||||
this.findById(uid, function (err, user) {
|
||||
if(err) {
|
||||
fn(err);
|
||||
} else {
|
||||
if(user.verificationToken === token) {
|
||||
user.verificationToken = undefined;
|
||||
user.emailVerified = true;
|
||||
user.save(function (err) {
|
||||
if(err) {
|
||||
fn(err)
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fn(new Error('invalid token'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the extend method to setup any extended user models.
|
||||
*/
|
||||
|
||||
User.extend = function () {
|
||||
var EUser = Model.extend.apply(User, arguments);
|
||||
User.setup = function () {
|
||||
var UserModel = this;
|
||||
|
||||
setup(EUser);
|
||||
|
||||
return EUser;
|
||||
}
|
||||
|
||||
function setup(UserModel) {
|
||||
asteroid.remoteMethod(
|
||||
UserModel.login,
|
||||
{
|
||||
|
@ -127,7 +251,43 @@ function setup(UserModel) {
|
|||
}
|
||||
);
|
||||
|
||||
asteroid.remoteMethod(
|
||||
UserModel.logout,
|
||||
{
|
||||
accepts: [
|
||||
{arg: 'sid', type: 'string', required: true}
|
||||
],
|
||||
http: {verb: 'all'}
|
||||
}
|
||||
);
|
||||
|
||||
asteroid.remoteMethod(
|
||||
UserModel.confirm,
|
||||
{
|
||||
accepts: [
|
||||
{arg: 'uid', type: 'string', required: true},
|
||||
{arg: 'token', type: 'string', required: true},
|
||||
{arg: 'redirect', type: 'string', required: true}
|
||||
],
|
||||
http: {verb: 'get', path: '/confirm'}
|
||||
}
|
||||
);
|
||||
|
||||
UserModel.on('attached', function () {
|
||||
UserModel.afterRemote('confirm', function (ctx, inst, next) {
|
||||
if(ctx.req) {
|
||||
ctx.res.redirect(ctx.req.param('redirect'));
|
||||
} else {
|
||||
fn(new Error('transport unsupported'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return UserModel;
|
||||
}
|
||||
|
||||
setup(User);
|
||||
/*!
|
||||
* Setup the base user.
|
||||
*/
|
||||
|
||||
User.setup();
|
|
@ -13,7 +13,9 @@
|
|||
"inflection": "~1.2.5",
|
||||
"bcrypt": "~0.7.6",
|
||||
"passport": "~0.1.17",
|
||||
"passport-local": "~0.1.6"
|
||||
"passport-local": "~0.1.6",
|
||||
"nodemailer": "~0.4.4",
|
||||
"ejs": "~0.8.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "latest",
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<h1>Thank You</h1>
|
||||
|
||||
<p>
|
||||
Thanks for registering. Please follow the link below to complete your registration.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="<%= verifyHref %>"><%= verifyHref %></a>
|
||||
</p>
|
|
@ -288,7 +288,7 @@ describe('Model', function() {
|
|||
it('Run a function before a remote method is called by a client.', function(done) {
|
||||
var hookCalled = false;
|
||||
|
||||
User.beforeRemote('*.save', function(ctx, user, next) {
|
||||
User.beforeRemote('create', function(ctx, user, next) {
|
||||
hookCalled = true;
|
||||
next();
|
||||
});
|
||||
|
@ -312,12 +312,12 @@ describe('Model', function() {
|
|||
var beforeCalled = false;
|
||||
var afterCalled = false;
|
||||
|
||||
User.beforeRemote('*.save', function(ctx, user, next) {
|
||||
User.beforeRemote('create', function(ctx, user, next) {
|
||||
assert(!afterCalled);
|
||||
beforeCalled = true;
|
||||
next();
|
||||
});
|
||||
User.afterRemote('*.save', function(ctx, user, next) {
|
||||
User.afterRemote('create', function(ctx, user, next) {
|
||||
assert(beforeCalled);
|
||||
afterCalled = true;
|
||||
next();
|
||||
|
@ -349,7 +349,7 @@ describe('Model', function() {
|
|||
it("The express ServerRequest object", function(done) {
|
||||
var hookCalled = false;
|
||||
|
||||
User.beforeRemote('*.save', function(ctx, user, next) {
|
||||
User.beforeRemote('create', function(ctx, user, next) {
|
||||
hookCalled = true;
|
||||
assert(ctx.req);
|
||||
assert(ctx.req.url);
|
||||
|
@ -378,7 +378,7 @@ describe('Model', function() {
|
|||
it("The express ServerResponse object", function(done) {
|
||||
var hookCalled = false;
|
||||
|
||||
User.beforeRemote('*.save', function(ctx, user, next) {
|
||||
User.beforeRemote('create', function(ctx, user, next) {
|
||||
hookCalled = true;
|
||||
assert(ctx.req);
|
||||
assert(ctx.req.url);
|
||||
|
@ -466,7 +466,7 @@ describe('Model', function() {
|
|||
|
||||
describe('Model.extend()', function(){
|
||||
it('Create a new model by extending an existing model.', function() {
|
||||
var User = asteroid.Model.extend('user', {
|
||||
var User = asteroid.Model.extend('test-user', {
|
||||
email: String
|
||||
});
|
||||
|
||||
|
@ -486,7 +486,6 @@ describe('Model', function() {
|
|||
assert.equal(MyUser.prototype.bar, User.prototype.bar);
|
||||
assert.equal(MyUser.foo, User.foo);
|
||||
|
||||
debugger;
|
||||
var user = new MyUser({
|
||||
email: 'foo@bar.com',
|
||||
a: 'foo',
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
var User = asteroid.User;
|
||||
var Session = asteroid.Session;
|
||||
var passport = require('passport');
|
||||
|
||||
var userMemory = asteroid.createDataSource({
|
||||
connector: asteroid.Memory
|
||||
});
|
||||
asteroid.User.attachTo(userMemory);
|
||||
asteroid.Session.attachTo(userMemory);
|
||||
asteroid.Email.setup({transports: [{type: 'STUB'}]});
|
||||
|
||||
describe('User', function(){
|
||||
|
||||
beforeEach(function (done) {
|
||||
var memory = asteroid.createDataSource({
|
||||
connector: asteroid.Memory
|
||||
});
|
||||
asteroid.User.attachTo(memory);
|
||||
asteroid.Session.attachTo(memory);
|
||||
app.use(asteroid.cookieParser());
|
||||
app.use(asteroid.auth());
|
||||
app.use(asteroid.rest());
|
||||
|
@ -17,7 +20,11 @@ describe('User', function(){
|
|||
asteroid.User.create({email: 'foo@bar.com', password: 'bar'}, done);
|
||||
});
|
||||
|
||||
describe('User.login', function(){
|
||||
afterEach(function (done) {
|
||||
Session.destroyAll(done);
|
||||
});
|
||||
|
||||
describe('User.login', function() {
|
||||
it('Login a user by providing credentials.', function(done) {
|
||||
request(app)
|
||||
.post('/users/login')
|
||||
|
@ -35,4 +42,135 @@ describe('User', function(){
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('User.logout', function() {
|
||||
it('Logout a user by providing the current session id.', function(done) {
|
||||
login(logout);
|
||||
|
||||
function login(fn) {
|
||||
request(app)
|
||||
.post('/users/login')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.send({email: 'foo@bar.com', password: 'bar'})
|
||||
.end(function(err, res){
|
||||
if(err) return done(err);
|
||||
var session = res.body;
|
||||
|
||||
assert(session.uid);
|
||||
assert(session.id);
|
||||
|
||||
fn(null, session.id);
|
||||
});
|
||||
}
|
||||
|
||||
function logout(err, sid) {
|
||||
request(app)
|
||||
.post('/users/logout')
|
||||
.expect(200)
|
||||
.send({sid: sid})
|
||||
.end(verify(sid));
|
||||
}
|
||||
|
||||
function verify(sid) {
|
||||
return function (err) {
|
||||
if(err) return done(err);
|
||||
Session.findById(sid, function (err, session) {
|
||||
assert(!session, 'session should not exist after logging out');
|
||||
done(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('user.hasPassword(plain, fn)', function(){
|
||||
it('Determine if the password matches the stored password.', function(done) {
|
||||
var u = new User({username: 'foo', password: 'bar'});
|
||||
|
||||
u.hasPassword('bar', function (err, isMatch) {
|
||||
assert(isMatch, 'password doesnt match');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Verification', function(){
|
||||
|
||||
describe('user.verify(options, fn)', function(){
|
||||
it('Verify a user\'s email address', function(done) {
|
||||
User.afterRemote('create', function(ctx, user, next) {
|
||||
assert(user, 'afterRemote should include result');
|
||||
|
||||
var options = {
|
||||
type: 'email',
|
||||
to: user.email,
|
||||
from: 'noreply@myapp.org',
|
||||
redirect: '/',
|
||||
protocol: ctx.req.protocol,
|
||||
host: ctx.req.get('host')
|
||||
};
|
||||
|
||||
user.verify(options, function (err, result) {
|
||||
assert(result.email);
|
||||
assert(result.email.message);
|
||||
assert(result.token);
|
||||
|
||||
|
||||
var lines = result.email.message.split('\n');
|
||||
assert(lines[4].indexOf('To: bar@bat.com') === 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
request(app)
|
||||
.post('/users')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.send({data: {email: 'bar@bat.com', password: 'bar'}})
|
||||
.end(function(err, res){
|
||||
if(err) return done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('User.confirm(options, fn)', function(){
|
||||
it('Confirm a user verification', function(done) {
|
||||
User.afterRemote('create', function(ctx, user, next) {
|
||||
assert(user, 'afterRemote should include result');
|
||||
|
||||
var options = {
|
||||
type: 'email',
|
||||
to: user.email,
|
||||
from: 'noreply@myapp.org',
|
||||
redirect: 'http://foo.com/bar',
|
||||
protocol: ctx.req.protocol,
|
||||
host: ctx.req.get('host')
|
||||
};
|
||||
|
||||
user.verify(options, function (err, result) {
|
||||
if(err) return done(err);
|
||||
|
||||
request(app)
|
||||
.get('/users/confirm?uid=' + result.uid + '&token=' + encodeURIComponent(result.token) + '&redirect=' + encodeURIComponent(options.redirect))
|
||||
.expect(302)
|
||||
.expect('location', options.redirect)
|
||||
.end(function(err, res){
|
||||
if(err) return done(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
request(app)
|
||||
.post('/users')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(302)
|
||||
.send({data: {email: 'bar@bat.com', password: 'bar'}})
|
||||
.end(function(err, res){
|
||||
if(err) return done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue