Adds sign up email verification, forgotten password reset, and other capabilities to local feathers-authentication
DEPRECATED. Goodbye, adiós, au revoir, auf Wiedersehen, zàijiàn.
Adds user email verification, forgotten password reset, and other capabilities to local
feathers-authentication
.
Deprecated:
feathers-service-verify-reset
has been moved into Feathers core asfeathers-authentication-management
for theAuk
release.
feathers-authentication-management
has important new capabilities. Its API has been rationalized and deprecated features have been removed. Documentation is available at Feathers docs underAuthentication
,Password Management
,Local
. (Also available here for now.)
options.userPropsForShortToken
renamed options.identifyUserProps
.
It contains all fields uniquely identifying the user.
These will mainly be communications (email, cellphone, facebook) but also username.
The prop names in options.identifyUserProps
need to have unique key columns in the DB.
This repo uses DB failures to catch duplicate keys,
because .verifyChange
values makes catching potential duplicates difficult.
user item should have a primaryCommunication
prop for the notifier.
hooks.restrictToVerified
renamed hooks.isVerified
options.userNotifier
renamed options.notifier
notifier must return a promise
notifier(p1, p2, p3)
now, not (p1, p2, p3, newEmail)
. The requested changes are in .verifyChanges
.
notifier type emailChange
is now identityChange
resendVerifySignup
no longer allows a string param to be the email
verifyReset
param actions
removed: unique, resend, verify, forgot, reset, password, email
options.service
added. default '/users'. Read docs on multiple services for further information.
service accessed by require(repo-name)
now, not require(repo-name).service
.
hooks still accessed by require('repo-name').hooks
.
hooks.addVerification
no longer has the previous param. Read docs on multiple services for further information. This allows addVerification
to be used with multiple instances of authManagement configurations.
hooks.addVerification
's options.len
removed. use options.longTokenLen
wrapper sendResetPwd(identifyUser, notifierOptions)
now, not (email, notifierOptions)
wrapper passwordChange(oldPassword, password, identifyUser)
now, not (oldPassword, password, user)
wrapper identityChange(password, changesIdentifyUser, identifyUser)
now, not emailChange(password, email, user)
neither the service nor the wrappers support callbacks. Callbacks for services are being removed from feathers in early 2017 with the Buzzard release.
service changed from
verifyReset.create({
action: 'passwordChange',
value: { oldPassword: plainPassword, password: plainNewPassword },
}, { user: paramsUser }, cb)
to
verifyReset.create({
action: 'passwordChange',
value: { user: { email }, oldPassword: plainPassword, password: plainNewPassword },
})
verifyReset.create({
action: 'emailChange',
value: { password: plainPassword, email: newEmail },
}, { user: paramsUser }, cb)
to
verifyReset.create({
action: 'identityChange',
value: { user: { email }, password: plainPassword, changes: { email, cellphone } },
})
user needs to add these hooks for the verifyReset service, the repo no longer does it automatically:
for feathers-authenticate < v1.0
const isAction = (...args) => hook => args.includes(hook.data.action);
before: {
create: [
hooks.iff(isAction('passwordChange', 'emailChange'), auth.verifyToken()),
hooks.iff(isAction('passwordChange', 'emailChange'), auth.populateUser()),
],
},
or for feathers-authenticate v1.0
const isAction = (...args) => hook => args.includes(hook.data.action);
before: {
create: [
hooks.iff(isAction('passwordChange', 'emailChange'), auth.hooks.authenticate('jwt')),
hooks.iff(isAction('passwordChange', 'emailChange'), auth.populateUser()),
],
},
Capabilities:
users
items.User notifications may be sent for:
May be used with
feathers-client
service calls over websockets or HTTP.feathers-client
service calls.A 30-char token is generated suitable for URL responses. (Configurable length.) This may be embedded in URL links sent by email, SMS or social media so that clicking the link starts the email address verification or the password reset.
A 6-digit token is also generated suitable for notification by SMS or social media. (Configurable length, may be alpha-numeric instead.) This may be manually entered in a UI to start the email address verification or the password reset.
The email verification token has a 5-day expiry (configurable), while the password reset has a 2 hour expiry (configurable).
Typically your user notifier refers to a property like user.preferredCommunication: 'email'
to determine which transport to use for user notification.
However the API allows the UI to be set up to ask the user which transport they prefer this time,
when resending a email address verification and sending a forgotten password reset.
The server does not handle any interactions with the user. Leaving it a pure API server, lets it be used with both native and browser clients.
The folder example/
presents a full featured server/browser implementation
whose UI lets you exercise the API.
app.configure(authentication)
.configure(verifyReset({ options }))
.configure(user);
options
are:
function(type, user, notifierOptions, newEmail, cb)
The service creates and maintains the following properties in the user
item:
The users
service is expected to be already configured.
Its patch
method is used to update the password when needed,
therefore patch
may not have a auth.hashPassword()
hook.
The service may be called on the client using
Method calls return a Promise unless a callback is provided.
const verifyReset = app.service('verifyReset');
// check props are unique in the users items
verifyReset.create({ action: 'checkUnique',
value: uniques, // e.g. {email, username}. Props with null or undefined are ignored.
ownId, // excludes your current user from the search
meta: { noErrMsg }, // if return an error.message if not unique
}, {}, cb)
// resend email verification notification
verifyReset.create({ action: 'resendVerifySignup',
value: emailOrToken, // email, {email}, {token}
notifierOptions: {}, // options passed to options1.userNotifier, e.g. {transport: 'sms'}
}, {}, cb)
// email addr verification with long token
verifyReset.create({ action: 'verifySignupLong',
value: token, // compares to .verifyToken
}, {}, cb)
// email addr verification with short token
verifyReset.create({ action: 'verifySignupShort',
value: {
token, // compares to .verifyTokenShort
user: {} // identify user, e.g. {email: '[email protected]'}. See options1.userPropsForShortToken.
}
}, {}, cb)
// send forgotten password notification
verifyReset.create({ action: 'sendResetPwd',
value: email,
notifierOptions: {}, // options passed to options1.userNotifier, e.g. {transport: 'sms'}
}, {}, cb)
// forgotten password verification with long token
verifyReset.create({ action: 'resetPwdLong',
value: {
token, // compares to .resetToken
password, // new password
},
}, {}, cb)
// forgotten password verification with short token
verifyReset.create({ action: 'resetPwdShort',
value: {
token, // compares to .resetTokenShort
password, // new password
user: {} // identify user, e.g. {email: '[email protected]'}. See options1.userPropsForShortToken.
},
}, {}, cb)
// change password
verifyReset.create({ action: 'passwordChange',
value: {
oldPassword, // old password for verification
password, // new password
},
}, { user }, cb)
// change email
verifyReset.create({ action: 'emailChange',
value: {
password, // current password for verification
email, // new email
},
}, { user }, cb)
// Authenticate user and log on if user is verified.
var cbCalled = false;
app.authenticate({ type: 'local', email, password })
.then((result) => {
const user = result.data;
if (!user || !user.isVerified) {
app.logout();
cb(new Error(user ? 'User\'s email is not verified.' : 'No user returned.'));
return;
}
cbCalled = true;
cb(null, user);
})
.catch((err) => {
if (!cbCalled) { cb(err); } // ignore throws from .then( cb(null, user) )
});
The wrappers return a Promise unless a callback is provided.
See example/
for a working example of wrapper usage.
```javascript`
<script src=".../feathers-service-verify-reset/lib/client.js"></script>or import VerifyRest from 'feathers-service-verify-reset/lib/client';
const app = feathers() ... const verifyReset = new VerifyReset(app);
// check props are unique in the users items verifyReset.checkUnique(uniques, ownId, ifErrMsg, cb)
// resend email verification notification verifyReset.resendVerifySignup(emailOrToken, notifierOptions, cb)
// email addr verification with long token verifyReset.verifySignupLong(token, cb)
// email addr verification with short token verifyReset.verifySignupShort(token, userFind, cb)
// send forgotten password notification verifyReset.sendResetPwd(email, notifierOptions, cb)
// forgotten password verification with long token verifyReset.resetPwdLong(token, password, cb)
// forgotten password verification with short token verifyReset.resetPwdShort(token, userFind, password, cb)
// change password verifyReset.passwordChange(oldPassword, password, user, cb)
// change email verifyReset.emailChange(password, email, user, cb)
// Authenticate user and log on if user is verified. verifyReset.authenticate(email, password, cb)
### <a name="fetch"> HTTP fetch (docs to complete)
```javascript
// check props are unique in the users items
// Set params just like [Feathers method calls.](#methods)
fetch('/verifyReset', {
method: 'POST', headers: { Accept: 'application/json' },
body: JSON.stringify({ action: 'checkUnique', value: uniques, ownId, meta: { noErrMsg } })
})
.then(data => { ... }).catch(err => { ... });
You will want to refer to authenticating over HTTP.
See feathers-reduxify-services
for information about state, etc.
See feathers-starter-react-redux-login-roles
for a working example.
import feathers from 'feathers-client';
import reduxifyServices, { getServicesStatus } from 'feathers-reduxify-services';
const app = feathers().configure(feathers.socketio(socket)).configure(feathers.hooks());
const services = reduxifyServices(app, ['users', 'verifyReset', ...]);
...
// hook up Redux reducers
export default combineReducers({
users: services.users.reducer,
verifyReset: services.verifyReset.reducer,
});
...
// email addr verification with long token
// Feathers is now 100% compatible with Redux. Use just like [Feathers method calls.](#methods)
store.dispatch(services.verifyReset.create({ action: 'verifySignupLong',
value: token, // compares to .verifyToken
}, {})
);
const reduxifyAuthentication = require('feathers-reduxify-authentication');
const signin = reduxifyAuthentication(app, { isUserAuthorized: (user) => user.isVerified });
// Sign in with the JWT currently in localStorage
if (localStorage['feathers-jwt']) {
store.dispatch(signin.authenticate()).catch(err => { ... });
}
// Sign in with credentials
store.dispatch(signin.authenticate({ type: 'local', email, password }))
.then(() => { ... )
.catch(err => { ... });
The service does not itself handle creation of a new user account nor the sending of the initial
email verification request.
Instead hooks are provided for you to use with the users
service create
method.
const verifyHooks = require('feathers-service-verify-reset').verifyResetHooks;
// users service
module.exports.before = {
create: [
auth.hashPassword(),
verifyHooks.addVerification() // adds .isVerified, .verifyExpires, .verifyToken props
]
};
module.exports.after = {
create: [
hooks.remove('password'),
aHookToEmailYourVerification(),
verifyHooks.removeVerification() // removes verification/reset fields other than .isVerified
]
};
A hook is provided to ensure the user's email addr is verified:
const auth = require('feathers-authentication').hooks;
const verify = require('feathers-service-verify-reset').hooks;
export.before = {
create: [
auth.verifyToken(),
auth.populateUser(),
auth.restrictToAuthenticated(),
verify.restrictToVerified()
]
};
An email to verify the user's email addr can be sent when user if created on the server,
e.g. example/src/services/user/hooks/index
:
The service adds the following optional properties to the user item. You should add them to your user model if your database uses models.
{
isVerified: { type: Boolean },
verifyToken: { type: String },
verifyExpires: { type: Date }, // or a long integer
resetToken: { type: String },
resetExpires: { type: Date }, // or a long integer
}
The client handles all interactions with the user. Therefore the server must serve the client app when, for example, a URL link is followed for email addr verification. The client must do some routing based on the path in the link.
Assume you have sent the email link:
http://localhost:3030/socket/verify/12b827994bb59cacce47978567989e
The server serves the client app on /socket
:
// Express-like middleware provided by Feathersjs.
app.use('/', serveStatic(app.get('public')))
.use('/socket', (req, res) => {
res.sendFile(path.resolve(__dirname, '..', 'public', 'socket.html')); // serve the client
})
The client then routes itself based on the URL. You will likely use you favorite client-side router, but a primitive routing would be:
const [leader, provider, action, slug] = window.location.pathname.split('/');
switch (action) {
case 'verify':
verifySignUp(slug);
break;
case 'reset':
resetPassword(slug);
break;
default:
// normal app startup
}
The length of the "30-char" token is configurable. The length of the "6-digit" token is configurable. It may also be configured as alphanumeric.
A few changes are needed to migrate to 1.0.0. Names were standardized throughout the new version and these had to be changed.
options1.userNotifier signature
was (type, user1, params, cb)
now (type, user1, notifierOptions, newEmail, cb)
options1.userNotifier param 'type'
'resend' now 'resendVerifySignup'
'verify' now 'verifySignup'
'forgot' now 'sendResetPwd'
'reset' now 'resetPwd'
'password' now 'passwordChange'
'email' now 'emailChange'
Error messages used to return
new errors.BadRequest('Password is incorrect.',
{ errors: { password: 'Password is incorrect.' } })
This was hacky although convenient if the names matched your UI. Now they return
new errors.BadRequest('Password is incorrect.',
{ errors: { password: 'Password is incorrect.', $className: 'badParams' } })
or even
new errors.BadRequest('Password is incorrect.',
{ errors: { $className: 'badParams' } })
Set your local UI errors based on the $className value.
The following are deprecated but remain working. They will be removed in the future.
options1
emailer uses options1.userNotifier
API param 'action'
'unique' uses 'checkUnique'
'resend' uses 'resendVerifySignup'
'verify' uses 'verifySignupLong'
'forgot' uses 'sendResetPwd'
'reset' uses 'resetPwdLong'
'password' uses 'passwordChange'
'email' uses 'emailChange'
client wrapper
.unique uses .checkUnique
.verifySignUp uses .verifySignupLong
.sendResetPassword uses .sendResetPwd
.saveResetPassword uses .resetPwdLong
.changePassword uses .passwordChange
.changeEmail uses .emailChange
The service now uses the route /verifyreset rather than /verifyReset/:action/:value
Email addr verification and handling forgotten passwords are common features these days. This package adds that functionality to Feathersjs.
Install Nodejs.
Run npm install feathers-service-verify-reset --save
in your project folder.
You can then require the utilities.
/src
on GitHub contains the ES6 source.
It will run on Node 6+ without transpiling.
cd example
npm install
npm start
Point browser to localhost:3030/socket
for the socketio client,
to localhost:3030/rest
for the rest client.
The two clients differ only in their how they configure feathers-client
.
feathers-starter-react-redux-login-roles is a full-featured example of using this repo with React and Redux.
npm test
to transpile to ES5 code, eslint and then run tests on Nodejs 6+.
MIT. See LICENSE.