Skip to content
This repository has been archived by the owner on Feb 25, 2019. It is now read-only.

Passwordless or Email only login (2nd) #260

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
58 changes: 26 additions & 32 deletions boot/mailer.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,38 +66,32 @@ function sendMail (template, locals, options, callback) {
/**
* Get mailer
*/
function setup () {
var fromVerifier = /^(?:\w|\s)+<[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}>$/igm
var transport = settings.mailer &&
nodemailer.createTransport(settings.mailer)

engineName = (settings.mailer && settings.mailer.view_engine) ||
settings.view_engine ||
'hogan'
engine = cons[engineName]

if (transport && (typeof settings.mailer.from !== 'string' ||
!fromVerifier.test(settings.mailer.from))) {
console.error(settings.mailer.from)
throw new Error('From field not provided for mailer. ' +
'Expected "Display Name <[email protected]>"')
}

defaultFrom = settings.mailer && settings.mailer.from

var mailer

exports.getMailer = function () {
if (mailer) {
return mailer
} else {
var fromVerifier = /^(?:\w|\s)+<[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}>$/igm
var transport = settings.mailer &&
nodemailer.createTransport(settings.mailer)

engineName = (settings.mailer && settings.mailer.view_engine) ||
settings.view_engine ||
'hogan'
engine = cons[engineName]

if (transport && (typeof settings.mailer.from !== 'string' ||
!fromVerifier.test(settings.mailer.from))) {
console.error(settings.mailer.from)
throw new Error('From field not provided for mailer. ' +
'Expected "Display Name <[email protected]>"')
}

defaultFrom = settings.mailer && settings.mailer.from

mailer = {
from: defaultFrom,
render: render,
transport: transport,
sendMail: sendMail
}

return mailer
var mailer = {
from: defaultFrom,
render: render,
transport: transport,
sendMail: sendMail
}
return mailer
}

module.exports = setup()
2 changes: 1 addition & 1 deletion boot/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var settings = require('./settings')
var setup = require('./setup')
var client = require('./redis').getClient()
var logger = require('./logger')(settings.logger)
require('./mailer').getMailer()
require('./mailer')
require('./migrate')()
var authenticator = require('../lib/authenticator')
var express = require('express')
Expand Down
27 changes: 27 additions & 0 deletions email/passwordlessSignin.hogan
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<html>
<body style="background: #fafafa">
<style>
.button:hover {
background: #F07911 !important;
}
.button:active, .button:focus {
box-shadow: 0 0 0 !important;
margin: 2px 4px -2px 10px !important;
}
</style>
<div style="font: normal normal 400 14px Roboto, Noto, 'Helvetica Neue', Helvetica, Arial, sans-serif; background: #fafafa; color: #212121">
<p style="margin: 8px"><strong style="color: #757575">{{providerName}}</strong></p>
<h1 style="margin: 8px; font-size: 36px; color: #e65100">
Sign in to {{providerName}}
</h1>
<p style="margin: 8px">Follow the link below by clicking the button if you
want to sign in to {{providerName}}.
</p>
<p style="margin: 8px"><a href="{{verifyURL}}" class="button"
style="display: block; display: inline-block; outline: none; width: 200px; text-align: center; margin: 0 8px; padding: 12px; box-shadow: 2px 2px 2px #757575; border-radius: 2px; -webkit-border-radius: 2px; background: #E37719; color: #fff; text-decoration: none; font-weight: 600">
Sign in to {{providerName}}</a></p>
<p style="margin: 8px; font-size: 12px">If you don't see a link above, paste the following URL into your browser: {{verifyURL}}</p>
<p style="margin: 8px; font-size: 12px">This e-mail was addressed to {{email}}</p>
</div>
</body>
</html>
25 changes: 25 additions & 0 deletions email/passwordlessSignup.hogan
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<html>
<body style="background: #fafafa">
<style>
.button:hover {
background: #F07911 !important;
}
.button:active, .button:focus {
box-shadow: 0 0 0 !important;
margin: 2px 4px -2px 10px !important;
}
</style>
<div style="font: normal normal 400 14px Roboto, Noto, 'Helvetica Neue', Helvetica, Arial, sans-serif; background: #fafafa; color: #212121">
<p style="margin: 8px"><strong style="color: #757575">{{providerName}}</strong></p>
<h1 style="margin: 8px; font-size: 36px; color: #e65100">
Verify your e-mail address
</h1>
<p style="margin: 8px">Great! You're almost done setting up your new {{providerName}} account.</p>
<p style="margin: 8px">All that's left is to <a href="{{verifyURL}}" class="button"
style="display: block; display: inline-block; outline: none; width: 200px; text-align: center; margin: 0 8px; padding: 12px; box-shadow: 2px 2px 2px #757575; border-radius: 2px; -webkit-border-radius: 2px; background: #E37719; color: #fff; text-decoration: none; font-weight: 600">
Verify your e-mail address</a></p>
<p style="margin: 8px; font-size: 12px">If you don't see a link above, paste the following URL into your browser: {{verifyURL}}</p>
<p style="margin: 8px; font-size: 12px">This e-mail was addressed to {{email}}</p>
</div>
</body>
</html>
23 changes: 23 additions & 0 deletions errors/PasswordlessDisabledError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Module dependencies
*/

var util = require('util')

/**
* PasswordlessDisabledError
*/

function PasswordlessDisabledError () {
this.name = 'PasswordlessDisabledError'
this.message = 'Email sign-in is disabled'
this.statusCode = 400
}

util.inherits(PasswordlessDisabledError, Error)

/**
* Exports
*/

module.exports = PasswordlessDisabledError
24 changes: 18 additions & 6 deletions oidc/determineProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,33 @@

var settings = require('../boot/settings')
var providers = require('../providers')
var InvalidRequestError = require('../errors/InvalidRequestError')

/**
* Determine provider middleware
*/

function determineProvider (req, res, next) {
var providerID = req.params.provider || req.body.provider
function determineProvider (options, req, res, next) {
var providerID = req.params.provider || req.connectParams.provider
if (providerID && settings.providers[providerID]) {
req.provider = providers[providerID]
}
if (options.requireProvider && !req.provider) {
return next(new InvalidRequestError('Invalid provider'))
}
next()
}

/**
* Module export
*/
module.exports = function (req, res, next) {
determineProvider({}, req, res, next)
}

module.exports = determineProvider
module.exports.setup = function (options) {
options = options || {}
options = {
requireProvider: options.requireProvider || false
}
return function (req, res, next) {
determineProvider(options, req, res, next)
}
}
6 changes: 4 additions & 2 deletions oidc/enforceReferrer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ module.exports = function (pathname) {
pathname = [ pathname ]
}

var host = url.parse(settings.issuer).host

return function enforceReferrer (req, res, next) {
// allows changing settings for test by not reading them
// during module initialization.

var host = url.parse(settings.issuer).host
var referrer = req.get('referrer')

// Only allow requests with a referrer defined
Expand Down
2 changes: 2 additions & 0 deletions oidc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ var oidc = {
verifyClientToken: require('./verifyClientToken'),
verifyClientIdentifiers: require('./verifyClientIdentifiers'),
verifyEmail: require('./verifyEmail'),
verifyEmailValid: require('./verifyEmailValid'),
verifyMailerConfigured: require('./verifyMailerConfigured'),
verifyRedirectURI: require('./verifyRedirectURI'),
verifyAuthorizationCode: require('./verifyAuthorizationCode')
}
Expand Down
2 changes: 1 addition & 1 deletion oidc/requireVerifiedEmail.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/

var Role = require('../models/Role')
var mailer = require('../boot/mailer').getMailer()
var mailer = require('../boot/mailer')
var settings = require('../boot/settings')
var url = require('url')

Expand Down
2 changes: 1 addition & 1 deletion oidc/sendVerificationEmail.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function sendVerificationEmail (req, res, next) {
}

// Send verification email
mailer.getMailer().sendMail('verifyEmail', locals, {
mailer.sendMail('verifyEmail', locals, {
to: user.email,
subject: 'Verify your e-mail address'
}, function (err, responseStatus) {
Expand Down
32 changes: 32 additions & 0 deletions oidc/verifyEmailValid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Module dependencies
*/

// TODO share this with other email validations

var revalidator = require('revalidator')

/**
* Verify Email in req.connectParams.email is valid.
*
* This checks the validate on a syntax level.
*
* This middleware will not cause any errors, but instead remove
* the invalid values off of req.connectParams object.
*/

function verifyEmailValid (req, res, next) {
if (
!req.connectParams.email ||
!revalidator.validate.formats.email.test(req.connectParams.email)
) {
delete req.connectParams.email
}
next()
}

/**
* Exports
*/

module.exports = verifyEmailValid
17 changes: 17 additions & 0 deletions oidc/verifyMailerConfigured.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Module dependencies
*/

// TODO share this with other email validations

var mailer = require('../boot/mailer')

function verifyMailerConfigured (req, res, next) {
if (!mailer.transport) {
return next(new Error('Mailer not configured.'))
} else {
return next()
}
}

module.exports = verifyMailerConfigured
41 changes: 41 additions & 0 deletions protocols/Passwordless.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Passwordless Protocol
*
* This is essentially empty but is used as stub object so that other
* code dealing with protocols can stay ignorant of this.
*
* The logic which in other case is implemented in the
* Protocol class is implemented in routes/passwordless.
*/

function Passwordless (provider, configuration) {
this._provider = provider
this._configuration = configuration
this._emailField = 'email'
}

/**
* Initialize
*/

function initialize (provider, configuration) {
return new Passwordless(provider, configuration)
}

Passwordless.initialize = initialize

/*
* Authenticate request based on the contents of a form submission.
*
* @param {Object} req
* @api protected
*/
Passwordless.prototype.authenticate = function (req, options) {
throw new Error('Passwordless authenticate must not be called')
}

/**
* Exports
*/

module.exports = Passwordless
5 changes: 5 additions & 0 deletions providers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ function loadProviders (dir, files) {
if (typeof oamr !== 'undefined') {
provider.amr = oamr
}
// override the default token TTL (expiration) for the provider
var ottl = settings.providers[providerName].tokenTTL
if (typeof ottl !== 'undefined') {
provider.tokenTTL = ottl
}

// provider-specific refresh_userinfo setting
var orefuser_info = settings.providers[providerName].refresh_userinfo
Expand Down
19 changes: 19 additions & 0 deletions providers/passwordless.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Passwordless
*
* Signin/Signup by clicking a link send by email.
*/

module.exports = function (config) {
return {
id: 'passwordless',
name: 'Sign in or create account with your email',
protocol: 'Passwordless',
amr: 'email', // TODO: this is not a standard value, see https://tools.ietf.org/html/draft-jones-oauth-amr-values-01#section-2
tokenTTL: 60 * 15,
fields: [
{ name: 'email', type: 'email' }
],
usernameField: 'email' // TODO: The usernameField appears nowhere to be referenced?. This was copied from the Password provider.
}
}
36 changes: 36 additions & 0 deletions render/renderSignin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Module dependencies
*/
var _ = require('lodash')
var qs = require('qs')

var settings = require('../boot/settings')
var mailer = require('../boot/mailer')
var providers = require('../providers')

var providerInfo = {}
var providerNames = Object.keys(providers)
for (var i = 0; i < providerNames.length; i++) {
providerInfo[providerNames[i]] = providers[providerNames[i]]
}
var visibleProviders = {}
// Only render providers that are not marked as hidden
Object.keys(settings.providers).forEach(function (providerID) {
if (!settings.providers[providerID].hidden) {
visibleProviders[providerID] = settings.providers[providerID]
}
})

module.exports = function (res, params, options) {
var locals = {
params: qs.stringify(params),
request: params,
providers: visibleProviders,
providerInfo: providerInfo,
mailSupport: !!(mailer.transport)
}
if (typeof options === 'object') {
_.assign(locals, options)
}
res.render('signin', locals)
}
Loading