UNPKG

@horanet/hauth

Version:

Web authentication and authorization module for humans and devices with PG database

313 lines (194 loc) 11 kB
# hauth: Web authentication and authorization module for humans and devices This `hauth` package provides secured client password-based authentication and URL-based access control features, that express-based web applications can reuse without rewriting most frequently used authentication flows. ## Introduction This module is aimed at authenticating web users as well as devices. The use of this module assumes that: - the app is powered by [express](https://www.npmjs.com/package/express) - the app can connect to a [Postgres](https://www.postgresql.org/about/) database, and has right to create tables on that database (if not, the tables can be created by hand) ## Features ### Password Authentication If a client makes a request to a path for which the app expects authentication, the app sends an `HTTP` response with a `401` status code, a reason phrase indicating an authentication error, and a `WWW-Authenticate` header. The client can then submit login credentials in either request body, or request header using `HTTP Basic Authentication` with `Authorization` header: ```raw Authorization: Basic login:password ``` ### Role-based Access Control Hauth manages access rules based on URLs path (without query string) and on roles. Each user can be assigned one role. Access rules look like: ```cfg '/node_modules': 'skip', // disable access control => no authentication required '\.css$': 'skip', // as same - URLs can be expressed with regex '/app': 'allow', // allowed to any authenticated user '/reserved': ['admin'], // access granted only to users with role 'admin' '/': 'deny', // forbid everything which is not allowed ``` See below [accessRules](#accessrules) to get more informations about access rules format. By default, if no access rule matches, access is allowed to all authenticated users. ### Accessing User Data The app can retrieve user data in `req.user` field. ### Custom Login and Forbidden Pages In case of missing credentials or access denied, Hauth will respond with 401 or 403 ; else, the response is managed by the application. Besides, it is possible to manage custom 401 or 403 response, for example in order to provide an authentication form or a "Forbidden" page. ### Session Storage - User data are stored in a [JSON Web Token](https://en.wikipedia.org/wiki/JSON_Web_Token) (JWT), so the session data is stored only on client's side, and not in the database. - Ability to configure JWT key, algorithm and expiration time - It is advised to set a JWT key: indeed, if not set a random key is computed at each server startup, so after each startup, JWT previously delivered are not valid anymore and users must reauthenticate. ### Passwords Stored Hashed Salted passwords are hashed with [scrypt](https://en.wikipedia.org/wiki/Scrypt) by [scrypt-pwd](https://www.npmjs.com/package/scrypt-pwd). ### Force Password Change at Next Login Intended for devices, in case we need to change password (which the device uses to authenticate itself with the app) for security reasons. The module can generate the `Next-Password`, and the app will include this `Next-Password` in the response header after a sucessfull authentication, from the next time, the device must use this `Next-Password` to authenticate. ### Account Management Users can be added, deleted, modified (updating its role, name, changing password, etc.) easily via the exposed functions. ### Automatic Account Generation User accounts can be generated and their details are filled properly in the `hauth_user` and `hauth_role` by defining in the configuration. ### Default Accounts The default user accounts are created only at the first use of the module, right after the creation of `hauth_user` and `hauth_role` tables, so we always have default accounts ready to log in (to avoid the situation where there is no account). These accounts can be deleted after. ## Usage Create an express app, `require` the module `hauth`, `init` it with `db` and `config` parameters, and enjoy! ```js const express = require('express'); const app = express(); const pg = require('pg'); const db = new pg.Pool( /* PG params */ ); const config = { /* cf doc below */ }; const hauth = require('@horanet/hauth'); hauth.init(config, db); app.use('/hauth/login', hauth.getCookie); app.use('/hauth/logout', hauth.delCookie); app.use('/', hauth.control); app.use( /* app code */ ); app.listen(port, () => { /* ... */ }); ``` For more features, look at the example provided. ### PG tips Assume the database parameters are as follows: ```cfg host: 'localhost', port: 5432, database: 'test', user: 'testuser', password: 'testpass', ``` one can create the database using `psql`: ```psql sudo -u postgres psql postgres=# CREATE DATABASE test; postgres=# CREATE USER testuser WITH ENCRYPTED PASSWORD 'testpass'; postgres=# ALTER DATABASE test OWNER TO testuser; ``` With the correct params to connect to the created PG database, the module will automatically creates two tables (if they do not exist): `hauth_user` and `hauth_role`. ## Config parameters Module `hauth` takes in two sets of parameters: ```javascript const hauth = require('@horanet/hauth'); hauth.init(params, db); ``` where `db` is a database handle, for example ```js const db = new pg.Pool({ host: 'localhost', port: 5432, database: 'test', user: 'testuser', password: 'testpass' }); ``` and `params` is an object containing the configuration parameters (all optional): ### `cookiename` the default value is `hauth` ### JWT parameters as defined in [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) module - `jwt_key`: key used to cipher JSON web tokens, optional ; if not defined,randomly generated at each server startup - `jwt_alg`: algorithm used for signing JWT, default value is `HS256` - `jwt_exp`: expiration time for JWT, default `2h` for 2 hours ### `roles` an array with the name of the roles. Each user can be assigned one role granting access to some URLs according to `accessRules`. For example, `roles = ['admin', 'basic_user']` ### `accessRules` dictate the behavior of the control function, according to the URLs, with the following format: ```config <pattern>: <keyword> or <list of accepted profiles> ``` where `pattern` is: - a path, matching the starting of the request path (hence, starting with `/`), e.g. `'/files/'` - or a [regex](https://en.wikipedia.org/wiki/Regular_expression) string matching a part of the request path, e.g. `'\.css$'` (a pattern is recognized as a regex by the fact that it does not start with `/`) The keywords are: - `allow`: allowed access to any authenticated user - `deny`: access forbidden - `skip`: allowed access without authentication These rules are tested in the order they are listed, until a rule matches the request path. If no access rule matches the request path, the default rule is `allow`. So, in order to forbid anything that is not explicitly allowed, you should end the access rules with the rule `'/': 'deny'` ### `on401` A function to provide custom response if user authentication is required (typically used to send an authentication form) ### `on403` A function to provide custom response in case of access denied ### `onLogout` A function to provide custom response when the user logs out ### `autocreate` A function to make possible for an unregistered client to create its own accounts. If this function is defined, if an unknown client provides username and password, it makes it possible to check the credentials and to run some more code; if this function returns a non-null object describing an account, Hauth will create the account in hauth_user and deliver a new password (in a 'X-Next-Password' Header). For example, ```js autocreate: async function(login, pwd, db, request_headers) { if (pwd === 'secret') { // this magic password allows to create any admin account await db.query('INSERT INTO ...'); // you can add some extra processing return {login: login, name: login, role: 'admin'} } } ``` The autocreate function extra processing should be idempotent. Indeed, a common way to reset client credentials is to remove the corresponding record in haut_user, which induces to re-run the autocreate process. Besides, the client might fail to catch the next password, and thus re-run the autocreate process. Most of the SQL INSERT requests run in that function should finish with `on conflict do nothing`. ### `defaultUsers` An array of users to be created along with tables hauth_users and hauth_roles. This is intended for initing apps; These accounts can then be modified or deleted, and will not be recreated at each server startup. ## APIs This `hauth` module exposes the following functions: ### init Initialize the module. This function takes in the `config` and `db` parameters ; it creates the two tables `hauth_role` and `hauth_user` if necessary (and thus fills the default users), then fill in the `roles` defined in `config`. ### control Runs authentication and access control ### allowed Function allowed(role, url): this function return `True` if a user with rule `<rule>` is authorized to access `<url>`. ### getCookie Sign and verify token using `jsonwebtoken` module. ### delCookie Delete cookie (so as to log out the current user). ### getUser Given a login, returns a user profile, with password and `next_password` - Input parameter `login` (string) - Return an array `[user, password, next_password]` with - `user`: a JSON object containing all user data, except `password` and `next_password` - `password`: a string, or null if not defined - `next_password`: a string, or null if not defined ### addUser Add a single user to table `hauth_user` - Input parameter `user`: a JSON object containing user's information. For example: ```config {"login": "admin", "name": "Administrator", "role": "admin", "password": "admin"} ``` - Not all information are required, if a required field is missing, default information will be filled. ### delUser Delete a user from table `hauth_user`: - Input parameter `user`: a JSON object containing user's information - Require field: `user.login` - User with `login` will be deleted - Return `pass` value: `true` in case the `DELETE` query was succesfull, `false` otherwise. ### modUser Modify existing user in table `hauth_user`: - Input parameter `user`: a JSON object containing user's information - Require field: `user.login` - Information of user with `login` will be updated according to the input fields - Return `pass` value: `true` in case the `UPDATE` query was succesfull, `false` otherwise. ### addRoles ### checkUser ### checkPwd ### updatePwd ### checkToken ### addCookie ### generateCookie ## Example To run the example (the script located in `./example/index.js`): ```shell npm install npm run example ``` To check the currently logged in user, use the path `localhost:3000/whoami` ## Test ## FAQ