/************************************************************************* * * Troven CONFIDENTIAL * __________________ * * (c) 2017-2020 Troven Ventures Pty Ltd * All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of Troven Pty Ltd and its licensors, * if any. The intellectual and technical concepts contained * herein are proprietary to Troven Pty Ltd * and its suppliers and may be covered by International and Regional Patents, * patents in process, and are protected by trade secret or copyright law. * Dissemination of this information or reproduction of this material * is strictly forbidden unless prior written permission is obtained * from Troven Pty Ltd. */ import { IOperation, IChassisContext, IChassisMiddleware, Utils, } from "../index"; import JWT from "../helpers/JWT"; import JWTPlugin from "../plugins/jwt"; import { APIs, Vars } from "../helpers"; import * as _ from "lodash"; //import * as request_uuid from "./request_uuid"; /** * Authorize JWT * ------------- * Ensure the JWT contains specified claims. * * Unless disabled, an audit event is emitted. * */ export default class jwt implements IChassisMiddleware { name = "api.jwt"; title = "ensure a JWT contains specified claims"; constructor(protected plugin: JWTPlugin) {} fn(operation: IOperation, _options: any): Function { let context: IChassisContext = operation.context; let options = _.extend( { debug: true, params: {} }, _options, this.plugin.debug ); let debug = this.plugin.debug || options.debug; // disable - used because `openapi` calls if (options.enabled === false) { debug && context.log({ code: "api:jwt:disabled", message: "JWT disabled", resource: operation.resource, method: operation.actionId, }); return null; } return function (req, res, next) { // avoid running more than once if (req.jwt && req._uuid) { context.log({ code: "api:jwt:revisit", message: "revisit", request_id: req._uuid, }); next(); return; } let jwt_plugin = context.plugins.get("jwt") as JWTPlugin; req._uuid = res.getHeader("X-REQUEST-ID"); let jwt = jwt_plugin.ignoreVerify ? JWT.getJWT(context, req) : JWT.getValidJWT(context, req); let debugged = debug ? { ctx: {}, claims: {}, request_id: req._uuid, resource: operation.resource, method: operation.actionId, expires: jwt ? new Date(jwt.exp * 1000) : null, jwt: jwt ? jwt.sub: false, ignoreVerify: jwt_plugin.ignoreVerify, } : false; if (!jwt) { return Utils.sendError(context, res, { statusCode: 403, code: "api:jwt:missing", message: "token missing", debug: debugged, }); } if (jwt.error) { return Utils.sendError(context, res, { statusCode: 403, code: jwt.error, message: "token error", debug: debugged, }); } let now = Date.now() / 1000; // now in seconds; if (!jwt.jti) { return Utils.sendError(context, res, { statusCode: 403, code: "api:jwt:invalid", message: "token invalid", debug: debugged, }); } // check JWT: has not expired if (now > jwt.exp) { return Utils.sendError(context, res, { statusCode: 403, code: "api:jwt:expired", message: "token expired", debug: debugged, }); } // check JWT: not yet valid if (now < jwt.nbf) { return Utils.sendError(context, res, { statusCode: 403, code: "api:jwt:not-active", message: "token not active", debug: debugged, }); } // interpolate claims let req_ctx = APIs.getOperationContext( operation, req, options.params ); let claims = Vars.$$(options.claims, req_ctx); if (debugged) { debugged.ctx = req_ctx; debugged.claims = claims; } // check our claims in our JWT let access_granted = JWT.hasClaims(jwt, claims, context); if (!access_granted) { return Utils.sendError(context, res, { statusCode: 403, code: "api:jwt:denied", message: "insufficient permissions", debug: debugged, }); } context.audit({ code: "api:jwt:authorized", message: "authorized", debug: debugged, ignoreVerify: jwt_plugin.ignoreVerify, req_ctx: req_ctx, }); req.jwt = jwt; // we're good - continue processing request ... next(); }; } }