knoxxnxt-auth

6.1.3 • Public • Published

Auth

Simple authentication module for node.js applications

Installation

$ npm i --save knoxxnxt-auth

Usage

// default options shown here
var options = {
    db: require('./memorydb'),
    mail: {
        templates: { dir: __dirname + '/mail/templates' },
        transport: require('nodemailer-stub-transport')(),
        from: 'Default From <default@example.com>',
        url: {
            invite: {
                accept: 'http://example.com/auth/invite/accept?token=<%- user.activationToken %>&email=<%- user.email %>',
                reject: 'http://example.com/auth/invite/reject?token=<%- user.activationToken %>&email=<%- user.email %>'
            },
            activate: 'http://example.com/auth/activate?token=<%- user.activationToken %>&email=<%- user.email %>',
            reset: 'http://example.com/auth/reset?token=<%- user.resetToken %>&email=<%- user.email %>'
        },
        subject: {
            invite: 'You are invited',
            activate: 'Activate your account',
            reset: 'Reset Your account password'
        }
    },
    password: {
        // bcrypt rounds
        rounds: 10,
        length: { min: 8, max: 100 },
        entropy: { min: 24 }
    },
    // default user fields
    default: {
        properties: {},
        roles: []
    }
};
 
var auth = require('knoxxnxt-auth')(options);
var methods = auth.methods;

User Model

{
    // set by data store
    "_id": "...",
    // status of the user
    "status": "<invited|registered|enabled|disabled>",
    // login username and unique identifier
    "email": "...",
    // password hash - not accessible via api
    "hash": "...",
    // activation token - not accessible via api
    "activationToken": "...",
    // reset token and expiry - not accessible via api
    "resetToken": "...",
    "resetExpiry": "json date",
    // application specific properties
    "properties": { ... },
    // user roles
    "roles": [ ... ]
}

User Password

Password is validated using zxcvbn. It must obey the following rules:

  • Greater than 8 characters
  • Minimum 24 bits of entropy
  • No dictionary words or common passwords

Constants

 
exports.TOKEN_LENGTH = 64;
 
exports.RESET_TOKEN_EXPIRY = 1000 * 60 * 60 * 3; // 3 hours
 
exports.EMPTY_PASSWORD_HASH = '$2a$10$FISKBraDkuYqs5WNoyJiWe4.kgdJC1hHF0toHCzN80ZNLIRDKUBFS'; // password -> ''
 
exports.CURRENT_VERSION = require('../package.json').version;
 
exports.ROLE = {
  USER: 'user',
  USER_ADMIN: 'user admin'
};
exports.ROLES = ['user', 'user admin'];
 
exports.STATUS = {
  INVITED: 'invited',
  REGISTERED: 'registered',
  ENABLED: 'enabled',
  DISABLED: 'disabled'
};
exports.STATUSES = ['registered', 'enabled', 'disabled'];
 
exports.MESSAGE = {
    ...
};
 

API

version

Current library version

isSupported(semver)

For plugins or wrappers, returns if the supported auth versions are compatible with the existing version.

admin(options)

Creates an admin console to call methods. options are passed straight to the repl.start function.

Additional options include:

  • sock: UNIX socket to bind to
  • port: Port to bind to
  • host: Host to bind to

Either sock or port must be provided.

auth.admin({ port: 9091, host: 'localhost' });

Client terminal

$ nc localhost 9091
 
auth > auth.methods.all({})
undefined
auth > [{"_id":0,"email":"test@jksdua.asia","roles":["user"],"status":"enabled","properties":{"name":{"first":"Test","last":"User"},"some":{"other":"property"},"__pass":"I like big hashes and I cannot lie"}}]
 
auth > methods.get({ email: 'test@jksdua.asia' });
undefined
auth > {"_id":0,"email":"test@jksdua.asia","roles":["user"],"status":"enabled","properties":{"name":{"first":"Test","last":"User"},"some":{"other":"property"},"__pass":"I like big hashes and I cannot lie"}}
undefined
auth >

methods.get({ email })

Get a single user from the database.

var user = yield auth.methods.get({ email: 'a@a.com' });

methods.all({ [query], [options] })

Get all the users in the database.

// no filter, no options
var allUsers = yield auth.methods.all();
 
var disabledUsers = yield auth.methods.all({
    query: { status: 'disabled' }
});
 
var fiveDisabledUsers = yield auth.methods.all({
    query: { status: 'disabled' },
    options: { limit: 5 }
});

methods.insert({ email, pass, [roles], [properties] })

Low-level method for bypassing registration and invitation mechanisms.

try {
    var user = yield auth.methods.insert({
        email: 'a@a.com',
        pass: 'a',
        roles: ['developer', 'tester'],
        properties: {
            any: 'thing',
            even: { an: 'object' }
        }
    });
} catch(e) {}

methods.update(user)

Low-level method for updating user record directly.

Note: This completely replaces the user object in the database

try {
    var user = yield auth.methods.update({
        _id: 1,
        email: 'a@a.com',
        hash: '<bcrypt hash>',
        roles: ['developer', 'tester'],
        status: 'enabled',
        properties: {
            any: 'thing',
            even: { an: 'object' }
        }
    });
} catch(e) {}

methods.remove({ email })

Low-level method for removing a user from the database. This is a permanent operation.

try {
    var user = yield auth.methods.remove({ email: 'a@a.com' });
} catch(e) {}

methods.login({ email, pass })

Validates a user's credentials. If correct, returns the user record.

try {
    var user = yield auth.methods.login({ email: 'a@a.com', pass: 'a' });
} catch(e) {}

methods.register({ email, pass, [roles], [properties], [mailOptions] })

Registers a new user. An email is sent to activate the user account. Until the account is activated, the status of the user is marked as 'registered'. Once activated, the user status changes to 'enabled'.

try {
    var user = yield auth.methods.register({ email: 'a@a.com', pass: 'a' });
} catch(e) {}

methods.activate({ email, [token], [skipTokenVerification=false] })

Activates a user. The status of the user is changed from 'registered' to 'enabled'.

If skipTokenVerification is passed, token is not validated.

try {
    var user = yield auth.methods.activate({ email: 'a@a.com', token: 'a' });
 
    var user = yield auth.methods.activate({ email: 'b@b.com', skipTokenVerification: true });
} catch(e) {}

methods.resendActivationEmail({ email, [mailOptions] })

Creates a new activation token and sends the activation email.

try {
    var status = yield auth.methods.activate({ email: 'a@a.com' });
} catch(e) {}

methods.invite({ email, [roles], [properties], [mailOptions] })

Invites a new user. The user status is marked as invited. The user is sent an email with instructions on how to activate their account.

try {
    var user = yield auth.methods.invite({ email: 'a@a.com' });
} catch(e) {}

methods.resendInvitationEmail({ email, [mailOptions] })

Resends invitation email sent by invite().

try {
    var user = yield auth.methods.resendInvitationEmail({ email: 'a@a.com' });
} catch(e) {}

methods.acceptInvite({ email, pass, [token], [skipTokenVerification=false] })

Accept an invitation. Changes user status from invited to enabled.

If skipTokenVerification is passed, token is not validated.

try {
    var user = yield auth.methods.acceptInvite({ email: 'a@a.com', pass: 'a', token: 'a' });
 
    var user = yield auth.methods.acceptInvite({ email: 'b@b.com', pass: 'b', skipTokenVerification: true });
} catch(e) {}

methods.rejectInvite({ email, [token], [skipTokenVerification=false] })

Reject an invitation. Removes user record from the system - this is permanent.

If skipTokenVerification is passed, token is not validated.

try {
    var user = yield auth.methods.rejectInvite({ email: 'a@a.com', token: 'a' });
 
    var user = yield auth.methods.rejectInvite({ email: 'a@a.com', skipTokenVerification: true });
} catch(e) {}

methods.getProperties({ email })

Get user properties

try {
    var properties = yield auth.methods.getProperties({ email: 'a@a.com' });
} catch(e) {}

methods.setProperties({ email, properties })

Set user properties. User properties are replaced as is so ensure all properties are sent when updating.

try {
    yield auth.methods.properties({
        email: 'a@a.com',
        properties: { a: 'a', b: 2 }
    });
} catch(e) {}

methods.changePassword({ email, oldPass, newPass })

Change a user's password

try {
    yield auth.methods.changePassword({ email: 'a@a.com', oldPass: 'a', newPass: 'b' });
} catch(e) {}

methods.resetRequest({ email, [mailOptions] })

User is requesting a password reset. An email is sent to the user with a reset link.

try {
    yield auth.methods.resetRequest({ email: 'a@a.com' });
} catch(e) {}

methods.resetResponse({ email, token, [pass], [skipTokenVerification=false] })

User is requesting a password reset. An email is sent to the user with a reset link.

If skipTokenVerification is passed, token is not validated.

try {
    yield auth.methods.resetResponse({ email: 'a@a.com', token: 'a', pass: 'a' });
} catch(e) {}

methods.disable({ email })

Mark a user as disabled

try {
    yield auth.methods.disable({ email: 'a@a.com' });
} catch(e) {}

methods.enable({ email })

Mark a user as enabled

try {
    yield auth.methods.enable({ email: 'a@a.com' });
} catch(e) {}

Datastore

Any backend databased can be used such as MongoDB, Postgres, MySQL etc. It must support the following method signatures. All methods must be generator functions.

By default, a memory database is used and a warning is printed to the console.

.find([args])

An arguments hash passed from application level code is passed straight to the function. This could be used for various querying options.

.findById(id)

The user id is passed. It expects the entire user model to be returned.

.findByEmail(email)

Fetch a user by email. It expects the entire user model to be returned.

.insert(user)

A user json object is sent. The user object contains nested properties that must be handled appropriately by the data store. It expects a boolean indicating if the user was successfully inserted into the database.

It is left to the data store to assign the user with an id. This must be set as _id field on the user object

.bulkInsert(users)

An array of user json objects is sent. It expects a boolean indicating if the user was successfully inserted into the database.

.update(user)

The entire user object is passed to the data store. It expects a boolean indicating if the user was successfully inserted into the database.

.removeByEmail(email)

Remove a user from the database using their email address.

.reset()

Remove all the database records

Creating a datastore

See knoxxnxt-auth-mongodb for a sample implementaion.

Step 1

Create a new datastore that conforms to the above API.

Step 2

$ require knoxxnxt-auth --save-dev

Step 3

Run knoxxnxt-auth tests with the correct environment variable pointing to the new file

Debugging

In case of issues, debug by setting the environment variable DEBUG="knoxxnxt-auth:*".

cd node_modules/knoxxnxt-auth
$ DB_MODULE="$(pwd)/../../datastore.js" npm test

Readme

Keywords

Package Sidebar

Install

npm i knoxxnxt-auth

Weekly Downloads

0

Version

6.1.3

License

Last publish

Collaborators

  • jksdua