import * as jwt from 'jsonwebtoken'; import * as jwkToPem from 'jwk-to-pem'; import * as request from 'request-promise-native'; import { log } from '../system'; import { Middleware } from './middleware'; import { Request, Response } from '../http'; import { Unauthorized, InternalError } from '../http/errors'; /** * Extracts JWT from an Authorization header. * * @param {string} [headerValue] * @returns {string} */ function getBearerToken(headerValue?: string): string { if (!headerValue || !headerValue.includes('Bearer')) { throw new Unauthorized( 'Authorization failed due to missing credentials.' ); } return headerValue.split(' ')[1]; } /** * RBAC middleware via user-provided evaluator functions. * * @export * @class Roles * @extends {Middleware} */ export class Roles extends Middleware { /** * An array of evaluator functions which, given the current request and * response objects, returns either true or false indicating authorization. * * @memberof Roles */ evaluators: Array<(req: Request, res: Response) => Promise>; /** * Creates an instance of Roles. * @param {Array<(req: Request, res: Response) => Promise>} evaluators * @memberof Roles */ constructor( evaluators: Array<(req: Request, res: Response) => Promise> ) { super(); this.evaluators = evaluators; } /** * Executes each evaluator against the current request and response. * * @param {Request} req * @param {Response} res * @throws {Unauthorized} * @returns {Promise} * @memberof Roles */ processRequest = async (req: Request, res: Response) => { for (let evaluator of this.evaluators) { let allowed = await evaluator(req, res); if (!allowed) { throw new Unauthorized( 'You do not have permissions to access this resource.' ); } } } } /** * JWT processing middleware. * * @export * @class JWT * @extends {Middleware} */ export class JWT extends Middleware { /** * The JWT encryption password. * * @type {string} * @memberof JWT */ secret: string; /** * The encryption algorithm to use. * * @type {string} * @memberof JWT */ algorithm: string; /** * Creates an instance of JWT. * @param {string} secret * @param {string} [algorithm='HS256'] * @memberof JWT */ constructor(secret: string, algorithm: string='HS256') { super(); this.secret = secret; this.algorithm = algorithm; } /** * Extracts and verifies the JWT from the Authorization header. * * @param {Request} req * @param {Response} res * @throws {Unauthorized} * @returns {Promise} * @memberof JWT */ processRequest = async (req: Request, res: Response) => { return new Promise((resolve, reject) => { let token = getBearerToken(req.headers.Authorization); let options: jwt.VerifyOptions = { algorithms: [this.algorithm] }; try { req.auth = jwt.verify(token, this.secret, options); resolve(); } catch (exc) { log.error(exc); let err = new Unauthorized( 'Authorization failed: Invalid credentials.' ); reject(err); } }) } } /** * JWK processing middleware. * * @export * @class JWK * @extends {Middleware} */ export class JWK extends Middleware { /** * A list of JWKs in JSON used for token verification. * * @type {object[]} * @memberof JWK */ keys: object[]; /** * Creates an instance of JWK. * @param {object[]} jwks * @throws {InternalError} * @memberof JWK */ constructor(jwks: object[]) { super(); this.keys = jwks; if (this.keys.length < 1) { log.debug('Empty key array provided.'); throw new InternalError('Unable to read JWKS.'); } } /** * Extracts and verifies the JWT from the Authorization header. * * @param {Request} req * @param {Response} res * @throws {Unauthorized} * @returns {Promise} * @memberof JWK */ processRequest = async (req: Request, res: Response) => { // TODO: implement full validation for Cognito // https://amzn.to/2fo77UI let token = getBearerToken(req.headers.Authorization); for (let key of this.keys) { let pem = jwkToPem(key); try { req.auth = jwt.verify(token, pem); return; } catch (exc) { // keep trying; } } log.error('No keys were able to validate the token.'); throw new Unauthorized( 'Authorization failed due to invalid credentials.' ); } }