simplymichael / express-user-manager

A user management and authentication library for Express apps. Automatically creates and adds relevant (customizable) API endpoints to an Express app.
MIT License
18 stars 5 forks source link
authentication authorization express expressjs login login-system node nodejs registration user user-management user-management-system

Express User Manager

npm Travis build Codecov npm downloads GitHub License Conventional commits

A user management and authentication library for Express apps.

It automatically creates and adds the following API endpoints to an Express app:

Additional features include:

New in V3.0.0: Support for Hooks

Table of Contents

Installation

npm install --save express-user-manager

Quick start

const express = require('express');
const userManager = require('express-user-manager');
const app = express();

/**
 * Setup the datastore using any of the currently supported database adapters:
 *   - mongoose: for MongoDB
 *   - sequelize: for any of the other supported database engines:
 *     MySQL | MariaDB | SQLite | Microsoft SQL Server | Postgres | In-memory DB
 *     (See the section on "Built-in data stores" for supported database engines)
 */
const dbAdapter = 'mongoose'; // OR 'sequelize'
const store = userManager.getDbAdapter(dbAdapter);

// Bind the routes under [apiMountPoint] (default: ***/api/users***):
userManager.listen(expressApp, apiMountPoint = '/api/users', customRoutes = {});

(async function() {
  const server = http.createServer(app);

  // Establish a connection to the data store
  // Ensure the db is connected before binding the server to the port
  await store.connect({
    host: DB_HOST, // optional, default: 'localhost'
    port: DB_PORT, // optional
    user: DB_USERNAME, // optional
    pass: DB_PASSWORD, // optional
    engine: DB_ENGINE, // optional if the adapter is "mongoose" or if the value is "memory" and the adapter is "sequelize"; required otherwise
    dbName: DB_DBNAME, // optional, default: 'users'
    storagePath: DB_STORAGE_PATH, // optional, required if "engine" is set to "sqlite"
    debug: DB_DEBUG, // optional, default: false
    exitOnFail: EXIT_ON_DB_CONNECT_FAIL // optional, default: true
  });

  // Proceed with normal server initialization tasks
  server.listen(PORT);
  server.on('error', onError);
  server.on('listening', onListening);
 })();

// Optionally listen for and handle events
// (See the Emitted events section for more)
userManager.on(EVENT_NAME, function(data) {
  // do something with data
});

Quick notes

The init method

The init method provides a shortcut way to perform the setup and initialization steps above.

It is an async function that runs setup and initialization tasks, connects to the database, then starts listening for requests, all in a single step: await init(app, options);.

It takes two parameters:

Configuration

express-user-manager can be configured in several ways:

Environment variables

Note: express-user-manager uses the dotenv package, so a quick and easy way to define the above variables is to create a .env file at the root of your project directory, and add them to the file and they will automatically be picked up. Sample .env file

The config method

As stated earlier in the Configuration section, one of the ways you can configure express-user-manager is by using the config method.

This method provides an alternate way to pass configuration values to *express-user-manager** if you haven't done (or are unable to do) so via environment variables.

Below is an example touching on every setting:

const express = require('express');
const userManager = require('express-user-manager');
const app = express();
const dbAdapter = 'mongoose'; // OR 'sequelize'

// Call config(options) to configure the app
userManager.config({
  apiMountPoint: {string}, // The base route under which to listen for API requests

  password: { // {object} for password configuration
    minLength: {number}, // minimum length of user passwords, default: 6,
    maxLength: {number}, // maximum length of user passwords, default: 20
    disallowed: {string | array}, // comma-separated string or array of strings considered weak/non-secure passwords
  },

  routes: { // {object} for configuring custom routes, with members
    list: {string}, // specifies the path for getting users listing
    search: {string}, // specifies the path for searching users
    getUser: {string}, // specifies the path for getting a user's details via their username, a /:{username} is appended to this path
    signup: {string}, // specifies the user registration path
    login: {string}, // specifies user authentication path,
    logout: {string}, // defines the logout path
    updateUser: {string}, // specifies the path for updating a user's data
    deleteUser: {string} // specifies the path for deleting a user, a /:{userId} is appended to this path
  },

  db: { // {object} for configuring the database connection
    adapter: {string}, // the adapter to use. valid values include 'mongoose', 'sequelize'
    host: {mixed}, // database host
    port: {number}, // database port
    user: {string}, // database user
    pass: {string}, // database user's password
    engine: {string}, // the database engine, when the adapter is set to "sequelize". values: 'memory', 'mariadb', 'mssql', 'mysql', 'postgres', 'sqlite'
    dbName: {string}, // name of the database to connect to
    storagePath: {string}, // the database storage path, only valid when "engine" is "sqlite". combined with `dbName`: `${storagePath}/${dbName}.sqlite`
    debug: {boolean}, // a value of true outputs database debug info
    exitOnFail: {boolean}, // set to true to kill the Node process if database connection fails
  },

  security: { // {object} for configuring security
    sessionSecret: {string}, // a key for encrypting the session
    authTokenSecret: {string}, // a key for signing the authorization token
    authTokenExpiry: {number}, // the expiry time of the authorization token (in seconds), example: 60 * 60 * 24
  }
});

async(() => {
  /**
   * The dbAdapter argument is not required if it is either:  
   *   - specified in the db section of the call to config or as
   *   - set using the DB_ADAPTER environment variable
   */
  const store = userManager.getDbAdapter([dbAdapter]);

  /**
   * The connectionOptions are not required if the values:
   *   - are already specified in the db section of the call to config
   *   - are set using the DB_* environment variables
   */
  await store.connect([connectionOptions]);

  // If the dbAdapter and connection values are already specified
  // via config or via environment variables,
  // then the above two calls can be tersely combined in a single call:
  // await userManager.getDbAdapter().connect();
});

// Bind request listeners
userManager.listen(expressApp);

Notes on configuration settings:

Specifying custom API endpoints

To customize the request paths, either:

Below is the default definition of the API Endpoints, which can be modified for your custom routes:

const customApiEndpoints = {
  list       : '/',       // Resolves to [apiMountPoint]/
  search     : '/search', // Resolves to [apiMountPoint]/search
  getUser    : '/user',   // Resolves to [apiMountPoint]/user/:username
  signup     : '/',       // Resolves to [apiMountPoint]/
  login      : '/login',  // Resolves to [apiMountPoint]/login
  logout     : '/logout', // Resolves to [apiMountPoint]/logout
  updateUser : '/',       // Resolves to [apiMountPoint]/
  deleteUser : '/user',   // Resolves to [apiMountPoint]/user/:userId
};

API endpoints object properties

As seen above, the default object has a number of properties, each corresponding to a request path:

Built-in middlewares

The userManager module provides some middlewares. You can get them by calling: userManager.get('middlewares');. This will return an object with the following middlewares:

Hooks

Hooks are a mechanism to allow you hook into different parts of the application's lifecycle.

Available hooks

You can register a request or response hook for a single route, for multiple routes, or for all routes.

Registering request and response hooks

To register a request or response hook:

Registering hooks: examples

Unregistering request and response hooks

You also have the ability to unregister a hook when the hook is no longer needed. And you can unregister hooks globally, for only some routes, or for a single route.

Unregistering hooks: examples

Built-in data stores (database adapters and engines)

Emitted events

Events emitted by the database

Events emitted by request handlers

Events emitted by middlewares

Password constraints

Usage as a stand-alone server

The package comes with a built-in express server that allows you run it as a stand-alone server.

To run it as a stand-alone server, do the following:

Note: The built-in server runs using the default route/path settings. That means:

Requests and responses

Every route below is assumed to begin (i.e., prefixed) with the base API route (or mount point). The default base API route is /api/users.

Contributing

CHANGELOG

See CHANGELOG

License

MIT License

Author

Simplymichael (simplymichaelorji@gmail.com)