diff --git a/backend/config/custom-environment-variables.json b/backend/config/custom-environment-variables.json index 7fe50e8789..da1646d7aa 100644 --- a/backend/config/custom-environment-variables.json +++ b/backend/config/custom-environment-variables.json @@ -181,8 +181,7 @@ "secretAccessKey": "CROWD_OPENSEARCH_AWS_SECRET_ACCESS_KEY" }, "auth0": { - "domain": "CROWD_AUTH0_DOMAIN", "clientId": "CROWD_AUTH0_CLIENT_ID", - "cert": "CROWD_AUTH0_CERT" + "jwks": "CROWD_AUTH0_JWKS" } } diff --git a/backend/package-lock.json b/backend/package-lock.json index 6f1aeae992..876cd6719a 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -65,6 +65,7 @@ "html-to-text": "^8.2.1", "json2csv": "^5.0.7", "jsonwebtoken": "8.5.1", + "jwks-rsa": "^3.0.1", "lodash": "4.17.21", "moment": "2.29.4", "moment-timezone": "^0.5.34", @@ -22201,6 +22202,7 @@ }, "node_modules/erlpack": { "version": "0.1.4", + "hasInstallScript": true, "license": "MIT", "dependencies": { "bindings": "^1.5.0", @@ -26518,6 +26520,14 @@ "version": "1.1.0", "license": "MIT" }, + "node_modules/jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-beautify": { "version": "1.14.8", "license": "MIT", @@ -26768,6 +26778,22 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwks-rsa": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", + "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", + "dependencies": { + "@types/express": "^4.17.14", + "@types/jsonwebtoken": "^9.0.0", + "debug": "^4.3.4", + "jose": "^4.10.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/jws": { "version": "4.0.0", "license": "MIT", @@ -26848,6 +26874,11 @@ "node": ">= 0.8.0" } }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "dev": true, @@ -26905,6 +26936,11 @@ "version": "4.3.0", "license": "MIT" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "dev": true, @@ -27057,6 +27093,29 @@ "yallist": "^3.0.2" } }, + "node_modules/lru-memoizer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz", + "integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + }, "node_modules/lru-queue": { "version": "0.1.0", "license": "MIT", @@ -33427,6 +33486,7 @@ }, "node_modules/zlib-sync": { "version": "0.1.8", + "hasInstallScript": true, "license": "MIT", "dependencies": { "nan": "^2.17.0" @@ -62464,6 +62524,11 @@ "join-component": { "version": "1.1.0" }, + "jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==" + }, "js-beautify": { "version": "1.14.8", "requires": { @@ -62630,6 +62695,19 @@ "safe-buffer": "^5.0.1" } }, + "jwks-rsa": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", + "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", + "requires": { + "@types/express": "^4.17.14", + "@types/jsonwebtoken": "^9.0.0", + "debug": "^4.3.4", + "jose": "^4.10.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + } + }, "jws": { "version": "4.0.0", "requires": { @@ -62680,6 +62758,11 @@ "type-check": "~0.4.0" } }, + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "lines-and-columns": { "version": "1.2.4", "dev": true @@ -62717,6 +62800,11 @@ "lodash.camelcase": { "version": "4.3.0" }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "lodash.debounce": { "version": "4.0.8", "dev": true @@ -62816,6 +62904,31 @@ "yallist": "^3.0.2" } }, + "lru-memoizer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz", + "integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==", + "requires": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "requires": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + } + } + }, "lru-queue": { "version": "0.1.0", "requires": { diff --git a/backend/package.json b/backend/package.json index 02c6e09ba6..542765a516 100644 --- a/backend/package.json +++ b/backend/package.json @@ -99,6 +99,7 @@ "html-to-text": "^8.2.1", "json2csv": "^5.0.7", "jsonwebtoken": "8.5.1", + "jwks-rsa": "^3.0.1", "lodash": "4.17.21", "moment": "2.29.4", "moment-timezone": "^0.5.34", diff --git a/backend/src/api/auth/ssoCallback.ts b/backend/src/api/auth/ssoCallback.ts index b3ab5c3a71..a8b68e5172 100644 --- a/backend/src/api/auth/ssoCallback.ts +++ b/backend/src/api/auth/ssoCallback.ts @@ -1,31 +1,44 @@ import jwt from 'jsonwebtoken' +import jwksClient from 'jwks-rsa' import AuthService from '../../services/auth/authService' import { AUTH0_CONFIG } from '../../conf' import Error401 from '../../errors/Error401' +const jwks = jwksClient({ + jwksUri: AUTH0_CONFIG.jwks, + cache: true, + cacheMaxEntries: 5, + cacheMaxAge: 86400000, +}) + +async function getKey(header, callback) { + jwks.getSigningKey(header.kid, (err, key: any) => { + const signingKey = key.publicKey || key.rsaPublicKey + callback(null, signingKey) + }) +} + export default async (req, res) => { const { idToken, invitationToken, tenantId } = req.body try { const verifyToken = new Promise((resolve, reject) => { - const publicKey = AUTH0_CONFIG.cert.replaceAll('"', '').replace(/\\n/g, '\n') - jwt.verify(idToken, publicKey, { algorithms: ['RS256'] }, (err, decoded) => { - // If error verifying token + jwt.verify(idToken, getKey, { algorithms: ['RS256'] }, (err, decoded) => { if (err) { reject(new Error401()) } - // If token matches auth0 validation criteria - const { aud, iss } = decoded as any - if (aud !== AUTH0_CONFIG.clientId || !iss.includes(AUTH0_CONFIG.domain)) { + const { aud } = decoded as any + + if (aud !== AUTH0_CONFIG.clientId) { reject(new Error401()) } - // If token validation passed resolve(decoded) }) }) const data: any = await verifyToken + // Signin with data const token: string = await AuthService.signinFromSSO( 'auth0', diff --git a/backend/src/conf/configTypes.ts b/backend/src/conf/configTypes.ts index ea49e7f8cc..b9e0417feb 100644 --- a/backend/src/conf/configTypes.ts +++ b/backend/src/conf/configTypes.ts @@ -79,9 +79,8 @@ export interface ApiConfiguration { } export interface Auth0Configuration { - domain: string clientId: string - cert: string + jwks: string } export interface PlansConfiguration {