Adds user email verification, forgotten password reset, and other capabilities to local
feathers-authentication.
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:
- userNotifier: function(type, user, notifierOptions, newEmail, cb)
- type: type of notification
- 'resendVerifySignup' from resendVerifySignup API call
- 'verifySignup' from verifySignupLong and verifySignupShort API calls
- 'sendResetPwd' from sendResetPwd API call
- 'resetPwd' from resetPwdLong and resetPwdShort API calls
- 'passwordChange' from passwordChange API call
- 'emailChange' from emailChange API call
- user: user's item, minus password.
- notifierOptions: notifierOptions option from resendVerifySignup and sendResetPwd API calls
- newEmail: the new email address from emailChange API call
- longTokenLen: Half the length of the long token. Default is 15, giving 30-char tokens.
- shortTokenLen: Length of short token. Default is 6.
- shortTokenDigits: Short token is digits if true, else alphanumeric. Default is true.
- delay: Duration for sign up email verification token in ms. Default is 5 days.
- resetDelay: Duration for password reset token in ms. Default is 2 hours.
- userPropsForShortToken: A 6-digit short token is more susceptible to brute force attack than a 30-char token.
Therefore the verifySignupShort and resetPwdShort API calls require the user be identified
using a find-query-like object. To prevent this itself from being an attack vector,
userPropsForShortToken is an array of valid properties allowed in that query object.
The default is ['email']. You may change it to ['email', 'username'] if you want to
identify users by {email} or {username} or {email, username}.
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 - Using Feathers method calls - Provided service wrappers - HTTP fetch - React's Redux - Vue 2.0 (docs todo)
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: 'a@a.com'}. 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: 'a@a.com'}. 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) )
});
````
### <a name="wrappers"> Provided service wrappers
The wrappers return a Promise unless a callback is provided.
See `example/` for a working example of wrapper usage.
```javascript`
<script src="https://github.com/eddyystop/feathers-service-verify-reset/raw/v1.0.1/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)
// 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. Y
$ claude mcp add feathers-service-verify-reset \
-- python -m otcore.mcp_server <graph>