@intuitionrobotics/user-account
Version:
137 lines • 6.69 kB
JavaScript
;
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