UNPKG

@intuitionrobotics/user-account

Version:
137 lines 6.69 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SecretsModule = exports.SecretsModule_Class = exports.TokenExpiredException = void 0; const ts_common_1 = require("@intuitionrobotics/ts-common"); const backend_1 = require("@intuitionrobotics/thunderstorm/backend"); const jws = require("jws"); const JWTBuilder_1 = require("./JWTBuilder"); const __1 = require("../.."); class TokenExpiredException extends backend_1.ApiException { constructor(message, cause) { super(401, message, cause); } } exports.TokenExpiredException = TokenExpiredException; class SecretsModule_Class extends ts_common_1.Module { constructor() { super("SecretsModule"); this.DEFAULT_ISS = "TOOLS"; this.AUTHENTICATION_PREFIX = __1.AUTHENTICATION_PREFIX; this.AUTHENTICATION_KEY = __1.AUTHENTICATION_KEY; this.getAuthSecret = (kid) => { return this.getSecret(kid); }; this.getConfig = () => { if (!this.config) throw new ts_common_1.BadImplementationException(`Missing config, check SecretsModule's config`); if (!this.config.secrets) throw new ts_common_1.BadImplementationException(`Missing 'secrets' key in config, check SecretsModule's config`); return this.config.secrets; }; this.isExpired = (token) => { return this.getExpiration(token) < (0, ts_common_1.currentTimeMillies)(); }; this.generateJwt = (payload, kid, algorithm = "HS256") => { const secret = this.getAuthSecret(kid); return new JWTBuilder_1.JWTBuilder(algorithm) // This is a default that can be overwritten by the claims .setExpiration(Math.floor(((0, ts_common_1.currentTimeMillies)() + ts_common_1.Day) / 1000)) .addClaims(payload) .setIssuer(this.getIss()) .setKeyID(secret.kid) .build(secret.value); }; this.getIss = () => { const issuer = this.config.issuer; if (!issuer) return this.DEFAULT_ISS; return issuer; }; this.decodeJwt = (jwt) => { return jws.decode(jwt); }; this.setDefaultConfig({ validateKeyId: "AUTH_SECRET" }); } getSecret(k) { var _a, _b; const secret = ((_a = process.env) === null || _a === void 0 ? void 0 : _a[k]) || ((_b = this.getConfig()) === null || _b === void 0 ? void 0 : _b[k]); if (!secret) throw new ts_common_1.BadImplementationException(`Missing secret with key ${k} in SecretsModule`); return { kid: k, value: secret }; } validateRequestAndCheckExpiration(request, scopes) { const token = this.validateRequest(request, scopes); if (this.isExpired(token)) { const cause = `The JWT passed is not valid, check. With payload: ${(0, ts_common_1.__stringify)(token.payload)}. The JWT passed is expired`; throw new TokenExpiredException(cause); } return token.payload; } // Specify a kid to force the usage of it validateRequest(request, scopes) { var _a, _b; const authToken = this.extractAuthToken(request); const token = this.decodeJwt(authToken); if (!token) throw new backend_1.ApiException(401, "Could not decode token " + authToken); const kid = token.header.kid || this.config.validateKeyId; if (!kid) throw new backend_1.ApiException(401, "Could not deduce which key to use in order to verify the token, please specify a key ID"); const secret = this.getAuthSecret(kid); const verified = jws.verify(authToken, token.header.alg, secret.value); let cause = `The JWT passed is not valid, check. With payload: ${(0, ts_common_1.__stringify)(token.payload)} and header ${(0, ts_common_1.__stringify)(token.header)}.`; if (!verified) throw new backend_1.ApiException(401, cause); if (!((_a = token.payload) === null || _a === void 0 ? void 0 : _a[JWTBuilder_1.EXPIRES_AT])) { cause += ` The JWT is missing the expiration claim`; throw new backend_1.ApiException(401, cause); } const scopesToValidate = (_b = token.payload) === null || _b === void 0 ? void 0 : _b.scopes; if (scopesToValidate) this.validateScopes(scopesToValidate, scopes); return token; } validateScopes(_scopesToValidate, scopes) { const scopesToValidate = _scopesToValidate.split(","); const isValidScope = scopesToValidate.some(s => scopes.includes(s)); if (!isValidScope) throw new backend_1.ApiException(403, `User doesn't have valid scopes. It needs ${scopes.join(",")} but it has ${_scopesToValidate}`); } extractAuthToken(request) { const authHead = request.header(this.AUTHENTICATION_KEY); if (authHead === undefined) throw new ts_common_1.BadImplementationException("Missing Authorization header"); if (!authHead) throw new ts_common_1.BadImplementationException('The Authorization header is empty'); const parts = authHead.split(" "); if (parts.length !== 2 || parts[0] !== this.AUTHENTICATION_PREFIX) throw new ts_common_1.BadImplementationException(`The Authorization header is malformed` + "\n" + `Value: ${authHead}` + "\n" + `Expected Value: ${this.AUTHENTICATION_PREFIX} [token]`); const authToken = parts[1].trim(); if (!authToken) throw new ts_common_1.BadImplementationException(`The token provided is empty`); return authToken; } getExpiration(token) { let exp = token.payload[JWTBuilder_1.EXPIRES_AT]; if (!exp) return exp; const now = (0, ts_common_1.currentTimeMillies)(); const cutOff = 100000000000; // 3-3-1973 in milliseconds const isInSeconds = exp < cutOff; if (isInSeconds) exp = exp * 1000; const year = 365 * ts_common_1.Day; if (exp > now + year) throw new backend_1.ApiException(401, `The JWT passed is not valid. Payload: ${(0, ts_common_1.__stringify)(token.payload)}.` + `Malformed JWT, expiry date is not valid, check the exp format, assumed to be in ${isInSeconds ? "seconds" : "milliseconds"}`); return exp; } } exports.SecretsModule_Class = SecretsModule_Class; exports.SecretsModule = new SecretsModule_Class(); //# sourceMappingURL=SecretsModule.js.map