node-devise v0.1.4
node-devise
Another flexible authentication solution for mongoosejs. Inspired by irina.
See complete MVC solution based on Restify engines;
Installation:
npm install node-devise --saveRequires:
Node >8.0.0.
Mongoose >5.0.0.
Summary
Usage
Standard installation of the plugin:
const mongoose = require('mongoose')
const devise = require('node-devise')
const UserSchema = new mongoose.Schema({})
UserSchema.plugin(devise)
mongoose.model('User', UserSchema)Overview
Authenticable
authenticatable Module, responsible for hashing the password and validating the authenticity of a user while signing in.
Options
authenticatable will set the following options to schema:
* +authenticationField+: parameter used to set the name of the authentication field
* +authenticationFieldType+: by default, `node-devise` will set the authentication field a Email type
* +passwordField+: parameter used to set the field that triggers the password hash
* +hashedPasswordField+: parameter used to set the name of the hashed field
* +maximumAllowedFailedAttempts+: number of attempts allowed before lockout of account. By default 3Examples
UserSchema.plugin(devise, {
authenticationField: 'username',
authenticationFieldType: String,
maximumAllowedFailedAttempts: 5
})Custom message options
| Key | Description |
|---|---|
| authenticatorErrorMessage | parameter used to change the authentication error message |
| passwordErrorMessage | when the password is not entered |
| passwordNotMatchErrorMessage | used in the comparePassword method to inform that the password does not match |
| hashedPasswordErrorMessage | used in the comparePassword method to inform that the current instance does not have a password hash |
| authenticatorNotExistErrorMessage | used in the authenticate static method to inform that the string does not have a match in the database |
| credentialsNotExistErrorMessage | used in the authenticate static method to report that the credential is incorrect |
Instance Methods
#encryptPassword(password) :
instance method used to encrypt the current password. This method is called when the instance receives any value in the password field or when it makes a manual call.
Examples
// encypt instance password
await user.encryptPassword('secret')#comparePassword(password) :
an instance method which takes in a plain string password and compare with the instance hashed password to see if they match.
Examples
// after having model instance, returns true or an exception
const res = await user.comparePassword('secret')#changePassword(newPassword) :
an instance method that uses a single string in the newPassword parameter and sets it to the current password of that authenticated instance.
Examples
// after having model instance, returns true or an exception
const res = await user.changePassword('new_secret')#authenticate(password) :
authenticates the user's instance from a password passed by parameter.
Examples
// after having model instance, returns true or an exception
await user.authenticate(credentials.password)Static Methods
#authenticate(credentials, opts) :
a static method which takes in credentials in the format below:
const faker = require('faker')
const credentials = {
email: faker.internet.email(),
password: faker.internet.password()
}where a valid email and password corresponding to a database user must be reported. At the end,
the user of the provided credentials will be returned, otherwise the corresponding errors will be thrown.
Examples
// returns current object or an exception
const user = await User.authenticate(credentials)internally the authenticate static method calls an authenticate instance method that can also be ivoked
by the instance object receiving in the parameter the current password.
Confirmable
confirmable's main responsability is to verify if an account is already confirmed to sign in, and to send emails with confirmation instructions. Confirmation instructions are sent to the user email after creating a record and when manually requested by a new confirmation instruction request.
Confirmable adds the following columns:
confirmationTokenis a unique random tokenconfirmedAtis a timestamp when the user clicked the confirmation linkconfirmationSentAtis a timestamp when the confirmationToken was generated (not sent)confirmationTokenExpiryAtis an attribute that keep tracks of when the confirmation token will expiry. Beyond that, new confirmation token will be generated and notification will be send.
Examples
const user = await User.findOne({ email: credentials.email })
try {
await user.confirm() // returns true or an exception
await user.isConfirmed() // returns true or an exception
await user.sendConfirmation() // manually send instructions
} catch (error) {
console.log(error.stack)
}Options
confirmable will set the following options:
* +confirmable.tokenLifeSpan+: specifies the lifetime of the token, by default 3 days.Custom message options
| Key | Description |
|---|---|
| invalidConfirmationTokenErrorMessage | parameter used to tell that the commit token is invalid |
| confirmationTokenExpiredErrorMessage | informs you that the confirmation token has expired |
| accountNotConfirmedErrorMessage | when the account is not confirmed |
| checkConfirmationTokenExpiredErrorMessage | when the confirmation token has expired and a new email for verification is sent |
Instance Methods
#generateConfirmationToken(opts = { save: true }) :
an attribute that keep tracks of when the confirmation token will expiry. Beyond that, new confirmation token will be generated and notification will be send.
Examples
const res = await user.generateConfirmationToken({ save: false })
// when `save = false`
await res.save()#sendConfirmation(opts = { save: true }) :
This instance method is utilized by model.send and sends the confirmation notification. If sent successfully, it will update confirmationSentAt instance attribute with the current time stamp and persist the instance before return it.
Examples
// sends a confirmation notification and saves the send date in the `ConfirmationSentAt` parameter
const res = await user.sendConfirmation({ save: false })
// when `save = false`
await res.save()#isConfirmed() :
verifies whether a user is confirmed or not.
Examples
// after having model instance, returns true or an exception
const res = await user.isConfirmed()#confirm() :
confirm a user by setting it's confirmedAt to actual time and persist the instance before return it.
Examples
// after having model instance, returns true or an exception
const res = await user.confirm()Static Methods
#confirm(confirmationToken) :
this static method takes the given confirmationToken and confirms un-confirmed registration which matches the given confirmation token.
Examples
// returns current object or an exception
const res = await User.confirm('confirmationToken')Lockable
provide a means of locking an account after a specified number of failed sign-in attempts (defaults to 3 attempts).
user can unlock account through unlock instructions sent. It extend the model with the following.
Lockable adds the following columns:
failedAttemptis an attribute which keeps track of failed login attempts.lockedAtis an attribute which keeps track of the moment account is locked.unlockedAtis an attribute which keeps track of the moment account is unlocked.unlockTokenis an attribute which store the current unlock token of the locked account.unlockTokenSentAtis an attribute which keeps track of when the unlock token notification sent.unlockTokenExpiryAtis an attribute which keep track ofunlockTokenexpiration. IfunlockTokenis expired, a new token will get generated and set.
Examples
const user = await User.findOne({ email: credentials.email })
try {
// block account
await user.lock() // returns true or an exception
await user.isLocked() // returns true or an exception
await user.sendUnlock() // manually send instructions
// unlocks account
await User.unlock(user.unlockToken) // returns current account or an exception
await user.resetFailedAttempts() // reset invalid login attempts
} catch (error) {
console.log(error.stack)
}Options
lockable will set the following options:
* +lockable.tokenLifeSpan+: specifies the lifetime of the token, by default 3 days.Custom message options
| Key | Description |
|---|---|
| accountLockedErrorMessage | parameter used to change the account lockout error message. |
| invalidUnlockTokenErrorMessage | informs that the unlock token is invalid. |
| unlockTokenExpiredErrorMessage | unlocking token already expired. |
Instance Methods
#generateUnlockToken(opts = { save: true }) :
an instance method that generate unlockToken and unlockTokenExpiryAt.
Instance will get persisted before returned otherwise corresponding errors will get returned.
Examples
const res = await user.generateUnlockToken({ save: false })
// when `save = false`
await res.save()#sendUnlock(opts = { save: true }) :
an instance method which send account locked notification to the owner. It will set
unlockTokenSentAt to track when the lock notification is sent.
Instance will get updated before it returns otherwise corresponding errors will get returned.
Examples
// sends a unlock notification and saves the send date in the `unlockTokenSentAt` parameter
const res = await user.sendUnlock({ save: false })
// when `save = false`
await res.save()#isLocked() :
verifies whether a user is locked or not.
Examples
// after having model instance, returns false if not locked or an exception
const res = await user.isLocked()#resetFailedAttempts() :
clear previous failed attempts.
Examples
// set failedAttempts = 0
const res = await user.resetFailedAttempts()#lock(opts) :
an instance method that is used to lock an account. When invoked, it will check if the number of
failedAttempts is greater than the configured maximum allowed login attempts,
if so the account will get locked by setting lockedAt to the current timestamp of
lock invocation. Instance will get persisted before it returns otherwise corresponding errors will get returned.
Examples
// after having model instance, returns true or an exception
const res = await user.lock()Static Methods
#unlock(unlockToken) :
a model static method which unlock a locked account with the provided unlockToken.
If the token is expired the new unlockToken will get generated. If token is valid,
locked account will get unlocked and unlockedAt attribute will be set to current timestamp and
failedAttempts will get set to 0. Instance unlocked will get persisted before it
returns otherwise corrensponding errors will get returned.
Examples
// returns current object or an exception
const res = await User.unlock('unlockToken')Recoverable
lays out infrastructure of resets of the user passwords and sends reset instructions. It extends model with the following.
Recoverable adds the following columns:
recoveryTokenis an attribute that store recovery token.recoveryTokenExpiryAtis an attribute that track when the recoverable token is expiring.recoverySentAtis an attribute that keep track of the moment the recovery notification is sent.recoveredAtis an attribute which keeps track of the moment the password was recovered.
Examples
try {
// creates a new token and send it with instructions about how to reset the password
const user = await User.requestRecover({ email: credentials.email })
// resets the user password and save the record
await User.recover(user.recoveryToken, 'new_secret')
} catch (error) {
console.log(error.stack)
}Options
recoverable will set the following options:
* +recoverable.tokenLifeSpan+: specifies the lifetime of the token, by default 3 days.Custom message options
| Key | Description |
|---|---|
| invalidRecoveryDetailsErrorMessage | parameter used to change the recovery details message is invalid. |
| invalidRecoveryTokenErrorMessage | informs message to invalid recovery token. |
| recoveryTokenExpiredErrorMessage | used to change expired recovery token message. |
Instance Methods
#generateRecoveryToken(opts = { save: true }) :
an instance method which is used to generate recoveryToken and set recoveryTokenExpiryAt timestamp. Instance will get persisted before it returns.
Examples
const res = await user.generateRecoveryToken({ save: false })
// when `save = false`
await res.save()#sendRecovery(opts = { save: true }) :
an instance method which is used to send recovery notification to the user. It will set recoveryTokenSentAt timestamp. Instance will get persisted before it returns.
Examples
const res = await user.sendRecovery({ save: false })
// when `save = false`
await res.save()#recover(recoveryToken, newPassword) :
a model static method which is used to recover an account with the matched recoverToken.
The newPassword provided will get encrypted before set as user password. It will set recoveredAt before it persists the model.
Examples
// returns current object or an exception
const res = await User.recover('recoveryToken', 'new_password')Static Methods
#requestRecover(credentials, opts) :
a model static method which is used to request account password recovery. It utilize
generateRecoveryToken and sendRecovery to generate recovery token and send it.
Examples
// returns current object or an exception
const res = await User.requestRecover({ email })Registerable
handles signing up users through a registration process, also allowing them to edit and destroy their account.
Registerable adds the following columns:
registeredAtis an attribute which keeps track of when an account is registered.unregisteredAtis an attribute which keep tracks of when an account is unregistered.
Examples
const faker = require('faker')
try {
const user = await User.register({
email: faker.internet.email().toLowerCase(),
password: faker.internet.password()
})
const res = await user.unregister()
} catch (error) {
console.log(error.stack)
}Options
recoverable will set the following options:
* +registerable.autoConfirm+: allows registered accounts to be pre-confirmed.Custom message options
| Key | Description |
|---|---|
| credentialsNotExistErrorMessage | parameter used to change the bad credential message. |
| authenticatorAlreadyExistErrorMessage | change the message for existing accounts. |
Instance Methods
#unregister() :
an instance method which allows unregistering (destroy a user). The implementation currently is to set unregiesteredAt
to current timestamp of the invocation. Instance will get persisted before is returned otherwise corresponding errors will be returned.
// returns true or an exception
const res = await user.unregister()Static Methods
#register(credentials, opts) :
a model static method which is used to register provided credentials. It takes care of checking if then email is taken and validating credentials. It will return registered user otherwise corresponding registration errors.
const faker = require('faker')
const credentials = {
email: faker.internet.email(),
password: faker.internet.password()
}
// returns current object or an exception
const user = await User.register(credentials)Trackable
provide a means of tracking user signin activities. It extends provided model with the following.
Trackable adds the following columns:
signInCountincreased every time a sign in is made (by form).currentSignInAta timestamp updated when the user signs in.currentSignInIpAddresskeeps track of the latest IP address a user used to log.lastSignInAtholds the timestamp of the previous sign in.lastSignInIpAddressholds the remote ip of the previous sign in.
Examples
const user = await User.findOne({ email: credentials.email })
try {
await user.track('ipAddress')
} catch (error) {
console.log(error.stack)
}Instance Methods
track(ipAddress) :
this is model instance method, which when called with the IP address, it will update current tracking
details and set the provided IP address as the currentSignInIpAddress.
Examples
const faker = require('faker')
const user = await User.findOne({ email: faker.internet.email() })
// returns true or an exception
await user.track(faker.internet.ip())Sending Notifications
the default implementation of node-devise to send notifications is noop. This is because there are different use case(s) when it comes on sending notifications.
due to that reason, node-devise requires that your model implements send method which accept record, action, done as its argurments.
record :
refer to the current user model instance.
action :
refer to the type of notifcation to be sent. There are just three types which
are account_confirmation, account_recovery and password_recovery which are sent
when new account is registered, when an account is locked and need to be unlocked and when account is requesting
to recover the password respectively.
done :
is the callback that you must call after finishing sending the notification. By default this callback will update the notification send details based on the usage.
How to implement a send
simple add send into your model as instance methods.
Examples
const UserSchema = new Schema({})
// override send method i.e your notification sending: email, sms, etc
schema.methods.send = function (record, action, done) {
// below are the methods that dispatch the data to send via method `done`
//
// await User.requestRecover({ email }, { req })
// await User.register({ email, password }, { req })
// await User.unlock(req.user.unlockToken, { req })
// await User.authenticate({ email, password }, { req })
done((opts) => {
// const host = isObject(opts) && (isObject(opts.req) && opts.req.isSecure())
// ? 'https'
// : 'http' + '://' + uri
switch (action) {
// if we send `confirmation email`
case 'account_confirmation':
console.log('Action type: %s.\nRecord: %s \n', action, JSON.stringify(record))
break
// if we send `account recovery`
case 'password_recovery':
console.log('Action type: %s.\nRecord: %s \n', action, JSON.stringify(record))
break
// if we send `account locked` information
case 'account_recovery':
console.log('Action type: %s.\nRecord: %s \n', action, JSON.stringify(record))
break
default:
console.log('mailer', 'Template not found')
}
})
}Sending issues
It is recommended to use job queue like kue when implementing your send to reduce your API response time.
i18n
it is recommended to use an internalizer such as the i18nex or node-i18n when implementing the foreign language support layer.
the node-devise uses messages with the i18n. To customize your application, you can configure your locale file and use the i18n key in the installation plug-in:
{
'en-US': {
// authenticable
authenticatorErrorMessage: 'No {{field}} provided',
passwordErrorMessage: 'No password provided',
passwordNotMatchErrorMessage: 'Incorrect password',
hashedPasswordErrorMessage: 'Hashed password not found',
authenticatorNotExistErrorMessage: 'Incorrect {{field}}',
credentialsNotExistErrorMessage: 'Incorrect credentials',
// confirmable
invalidConfirmationTokenErrorMessage: 'Invalid confirmation token',
confirmationTokenExpiredErrorMessage: 'Confirmation token expired',
accountNotConfirmedErrorMessage: 'Account not confirmed',
checkConfirmationTokenExpiredErrorMessage: 'Confirmation token expired. Check your email for confirmation instructions.',
// lockable
accountLockedErrorMessage: 'Account locked. Check unlock instructions sent to you.',
invalidUnlockTokenErrorMessage: 'Invalid unlock token',
unlockTokenExpiredErrorMessage: 'Unlock token expired',
// recoverable
invalidRecoveryDetailsErrorMessage: 'Invalid recovery details',
invalidRecoveryTokenErrorMessage: 'Invalid recovery token',
recoveryTokenExpiredErrorMessage: 'Recovery token expired',
// registerable
authenticatorAlreadyExistErrorMessage: 'Account of {{field}} "{{value}}" already exist'
}
}Examples
UserSchema.plugin(devise, { i18n: i18n })Features & Roadmap
- TODO v0.1.4
- DONE Authenticatable
DONEOmniauthable [doc]- DONE Confirmable
- DONE Recoverable
- DONE Registerable
DONERememberable [doc]- DONE Trackable
DONETimeoutable [doc]DONEValidatable [doc]- DONE Lockable
Test
- Install all development dependencies
npm install- Then run test
npm testLicense
Copyright (c) 2018-present
