UNPKG

@arcblock/did-auth

Version:

Helper function to setup DID authentication support on a node.js web server

169 lines (150 loc) 6.33 kB
/* eslint-disable object-curly-newline */ // eslint-disable-next-line const debug = require('debug')(`${require('../../package.json').name}:handlers:wallet`); const cors = require('cors'); const createHandlers = require('./util'); const BaseHandler = require('./base'); const noop = () => {}; /** * Events that are emitted during an did-auth process * * - scanned: when the qrcode is scanned by wallet * - succeed: when authentication complete * - error: when something goes wrong * * @class WalletHandlers * @extends {EventEmitter} */ class WalletHandlers extends BaseHandler { /** * Creates an instance of DID Auth Handlers. * * @class * @param {object} config * @param {object} config.tokenStorage - function to generate action token * @param {object} config.authenticator - Authenticator instance that can to jwt sign/verify * @param {function} [config.pathTransformer=null] - how should we update pathname * @param {function} [config.onConnect=noop] - function called before each auth request send back to app, used to check for permission, throw error to halt the auth process * @param {object} [config.options={}] - custom options to define all handlers attached * @param {string} [config.options.prefix='/api/did'] - url prefix for this group endpoints * @param {number} [config.options.cleanupDelay=60000] - how long to wait before cleanup finished session * @param {string} [config.options.tokenKey='_t_'] - query param key for `token` * @param {string} [config.options.encKey='_ek_'] - query param key for encryption key * @param {string} [config.options.versionKey='_v_'] - query param key for protocol `version` */ constructor({ pathTransformer, tokenStorage, authenticator, onConnect = noop, options = {} }) { super({ pathTransformer, tokenStorage, authenticator, onConnect }); this.options = { prefix: '/api/did', cleanupDelay: 60000, tokenKey: '_t_', encKey: '_ek_', versionKey: '_v_', ...options, }; } /** * Attach routes and handlers for authenticator * Now express app have route handlers attached to the following url * - `GET /api/did/{action}/token` create new token * - `GET /api/did/{action}/status` check for token status * - `GET /api/did/{action}/timeout` expire a token * - `GET /api/did/{action}/auth` create auth response * - `POST /api/did/{action}/auth` process payment request * * @method * @param {object} config * @param {object} config.app - express instance to attach routes to * @param {object} [config.claims] - claims for this request * @param {string} config.action - action of this group of routes * @param {function} [config.onStart=noop] - callback when a new action start * @param {function} [config.onConnect=noop] - callback when a new action start * @param {function} config.onAuth - callback when user completed auth in DID Wallet, and data posted back * @param {function} [config.onDecline=noop] - callback when user has declined in wallet * @param {function} [config.onComplete=noop] - callback when the whole auth process is done, action token is removed * @param {function} [config.onExpire=noop] - callback when the action token expired * @param {function} [config.onError=console.error] - callback when there are some errors * @param {boolean|string|did} [config.authPrincipal=true] - whether should we do auth principal claim first * @param {boolean} [config.persistentDynamicClaims=false] - whether should we persist dynamic claims * @return void */ attach({ app, action, claims = undefined, onStart = noop, onConnect, onAuth, onDecline = noop, onComplete = noop, onExpire = noop, // eslint-disable-next-line no-console onError = console.error, authPrincipal = true, persistentDynamicClaims = false, }) { if (typeof onAuth !== 'function') { throw new Error('onAuth callback is required to attach did auth handlers'); } if (typeof onDecline !== 'function') { throw new Error('onDecline callback is required to attach did auth handlers'); } if (typeof onComplete !== 'function') { throw new Error('onComplete callback is required to attach did auth handlers'); } const { prefix } = this.options; // pathname for DID Wallet, which will be included for authenticator signing const pathname = `${prefix}/${action}/auth`; debug('attach routes', { action, prefix, pathname }); // eslint-disable-next-line consistent-return const onConnectWrapped = async (...args) => { if (typeof this.onConnect === 'function') { await this.onConnect(...args); } if (typeof onConnect === 'function') { return onConnect(...args); } }; const { generateSession, expireSession, checkSession, onAuthRequest, onAuthResponse, ensureContext, ensureSignedJson, } = createHandlers({ action, pathname, claims, onStart, onConnect: onConnectWrapped, // must be deterministic when returning dynamic claims onAuth, onDecline, onComplete, onExpire, onError, authPrincipal, persistentDynamicClaims, options: this.options, pathTransformer: this.pathTransformer, tokenStorage: this.tokenStorage, authenticator: this.authenticator, }); app.use(`${prefix}/${action}`, cors({ origin: '*', optionsSuccessStatus: 204 })); // 1. WEB|Wallet: to generate new token app.get(`${prefix}/${action}/token`, generateSession); app.post(`${prefix}/${action}/token`, generateSession); // 2. WEB: check for token status app.get(`${prefix}/${action}/status`, ensureContext, checkSession); // 3. WEB: to expire old token app.get(`${prefix}/${action}/timeout`, ensureContext, expireSession); // 4. Wallet: fetch auth request app.get(pathname, ensureContext, ensureSignedJson, onAuthRequest); // 5. Wallet: submit auth response app.post(pathname, ensureContext, ensureSignedJson, onAuthResponse); // 6. Wallet: submit auth response for web wallet app.get(`${pathname}/submit`, ensureContext, ensureSignedJson, onAuthResponse); } } module.exports = WalletHandlers;