Files
workavia-drive/tdrive/backend/node/src/services/console/clients/remote-jwks-verifier.ts
T
Montassar Ghanmy 3a94d7386e 🔐 Fixing SSO bugs (#36)
2023-05-02 10:03:25 +01:00

101 lines
3.2 KiB
TypeScript

import JwksClient from "jwks-rsa";
import nJwt from "njwt";
import { logger } from "../../../core/platform/framework/logger";
function verifyAudience(expected: string, aud: string | Array<string>) {
if (!expected) {
throw new Error("expected audience is required");
}
if (Array.isArray(aud) && !aud.includes(expected)) {
throw new Error(
`audience claim ${expected} does not match one of the expected audiences: ${aud.join(", ")}`,
);
}
if (!Array.isArray(aud) && aud !== expected) {
throw new Error(`audience claim ${aud} does not match expected audience: ${expected}`);
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function verifyIssuer(expected: string, issuer: string) {
if (issuer !== expected) {
throw new Error(`issuer ${issuer} does not match expected issuer: ${expected}`);
}
}
export class OidcJwtVerifier {
public claimsToAssert: any;
private issuer: string;
private jwksUri: string;
private jwksClient: any;
private verifier: any;
constructor(options: { [key: string]: any } = {}) {
// https://github.com/auth0/node-jwks-rsa/blob/master/CHANGELOG.md#request-agent-options
if (options.requestAgentOptions) {
// jwks-rsa no longer accepts 'requestAgentOptions' and instead requires a http(s).Agent be passed directly
const msg = `\`requestAgentOptions\` has been deprecated, use \`requestAgent\` instead.
For more info see https://github.com/auth0/node-jwks-rsa/blob/master/CHANGELOG.md#request-agent-options`;
throw new Error(msg);
}
this.claimsToAssert = options.assertClaims || {};
this.issuer = options.issuer;
this.jwksUri = options.jwksUri;
this.jwksClient = JwksClient({
jwksUri: this.jwksUri,
cache: true,
cacheMaxAge: options.cacheMaxAge || 60 * 60 * 1000,
cacheMaxEntries: 3,
jwksRequestsPerMinute: options.jwksRequestsPerMinute || 10,
rateLimit: true,
requestAgent: options.requestAgent,
});
this.verifier = nJwt
.createVerifier()
.setSigningAlgorithm("RS256")
.withKeyResolver((kid: string, cb: any) => {
if (kid) {
this.jwksClient.getSigningKey(kid, (err: any, key: any) => {
cb(err, key && (key.publicKey || key.rsaPublicKey));
});
} else {
cb("No KID specified", null);
}
return null;
});
}
async verifyAsPromise(tokenString: string): Promise<any> {
return new Promise((resolve, reject) => {
// Convert to a promise
this.verifier.verify(tokenString, (err: any, jwt: any) => {
if (err) {
return reject(err);
}
const jwtBodyProxy = new Proxy(jwt.body, {});
Object.defineProperty(jwt, "claims", {
enumerable: true,
writable: false,
value: jwtBodyProxy,
});
delete jwt.body;
resolve(jwt);
});
});
}
async verifyIdToken(idTokenString: string, expectedClientId: string) {
const jwt = await this.verifyAsPromise(idTokenString);
verifyAudience(expectedClientId, jwt.claims.aud);
logger.info(`issuer is ${this.issuer} -- ${jwt.claims.iss} -- ${JSON.stringify(jwt)}`);
// verifyIssuer(this.issuer, jwt.claims.iss);
return jwt;
}
}