diff --git a/client/src/api/access-tokens.js b/client/src/api/access-tokens.js index 1778618..0a1dcf9 100755 --- a/client/src/api/access-tokens.js +++ b/client/src/api/access-tokens.js @@ -4,6 +4,8 @@ import socket from './socket'; /* Actions */ const createAccessToken = (data, headers) => http.post('/access-tokens', data, headers); +const exchangeOidcToken = (accessToken, headers) => + http.post('/access-tokens/exchange', { token: accessToken }, headers); const deleteCurrentAccessToken = (headers) => socket.delete('/access-tokens/me', undefined, headers); @@ -11,4 +13,5 @@ const deleteCurrentAccessToken = (headers) => export default { createAccessToken, deleteCurrentAccessToken, + exchangeOidcToken, }; diff --git a/client/src/components/OIDC/OidcLogin.jsx b/client/src/components/OIDC/OidcLogin.jsx index 8995042..5cc7d3a 100644 --- a/client/src/components/OIDC/OidcLogin.jsx +++ b/client/src/components/OIDC/OidcLogin.jsx @@ -1,7 +1,19 @@ +import { useAuth } from 'oidc-react'; +import PropTypes from 'prop-types'; import React from 'react'; -const OidcLogin = react.memo(() => { - return
test
; +let isLoggingIn = true; +const OidcLogin = React.memo(({ onAuthenticate }) => { + const auth = useAuth(); + if (isLoggingIn && auth.userData) { + isLoggingIn = false; + const user = auth.userData; + onAuthenticate(user); + } }); +OidcLogin.propTypes = { + onAuthenticate: PropTypes.func.isRequired, +}; + export default OidcLogin; diff --git a/client/src/components/Root.jsx b/client/src/components/Root.jsx index 2e4a931..1348004 100755 --- a/client/src/components/Root.jsx +++ b/client/src/components/Root.jsx @@ -9,7 +9,6 @@ import Paths from '../constants/Paths'; import LoginContainer from '../containers/LoginContainer'; import CoreContainer from '../containers/CoreContainer'; import NotFound from './NotFound'; -import entryActions from '../entry-actions'; import 'react-datepicker/dist/react-datepicker.css'; import 'photoswipe/dist/photoswipe.css'; @@ -19,10 +18,6 @@ import '../styles.module.scss'; import OidcLoginContainer from '../containers/OidcLoginContainer'; const oidcConfig = { - onSignIn: (user) => { - // Redirect? - entryActions.authenticate(user); - }, authority: 'https://auth.jjakt.monster/realms/test-realm/', clientId: 'planka-dev', redirectUri: 'http://localhost:3000/OidcLogin', diff --git a/client/src/sagas/login/services/login.js b/client/src/sagas/login/services/login.js index 1d494aa..75bbd7f 100644 --- a/client/src/sagas/login/services/login.js +++ b/client/src/sagas/login/services/login.js @@ -7,10 +7,10 @@ import { setAccessToken } from '../../../utils/access-token-storage'; export function* authenticate(data) { yield put(actions.authenticate(data)); - let { accessToken } = data; + let accessToken = data.access_token; try { if (accessToken) { - // swap + ({ item: accessToken } = yield call(api.exchangeOidcToken, accessToken)); } else { ({ item: accessToken } = yield call(api.createAccessToken, data)); } diff --git a/server/api/controllers/access-tokens/exchange.js b/server/api/controllers/access-tokens/exchange.js new file mode 100644 index 0000000..2ecce42 --- /dev/null +++ b/server/api/controllers/access-tokens/exchange.js @@ -0,0 +1,138 @@ +const jwt = require('jsonwebtoken'); +const jwksClient = require('jwks-rsa'); +const openidClient = require('openid-client'); +const { getRemoteAddress } = require('../../../utils/remoteAddress'); + +const Errors = { + INVALID_TOKEN: { + invalidToken: 'Invalid email or username', + }, +}; + +const jwks = jwksClient({ + jwksUri: 'https://auth.jjakt.monster/realms/test-realm/protocol/openid-connect/certs', + requestHeaders: {}, // Optional + timeout: 30000, // Defaults to 30s +}); + +const getJwtVerificationOptions = () => { + const options = {}; + if (sails.config.custom.oidcIssuer) { + options.issuer = sails.config.custom.oidcIssuer; + } + if (sails.config.custom.oidcAudience) { + options.audience = sails.config.custom.oidcAudience; + } + return options; +}; + +const validateAndDecodeToken = async (accessToken, options) => { + sails.log.info(accessToken); + const keys = await jwks.getSigningKeys(); + let validToken = {}; + + const isTokenValid = keys.some((signingKey) => { + try { + const key = signingKey.getPublicKey(); + validToken = jwt.verify(accessToken, key, options); + return 'true'; + } catch (error) { + sails.log.error(error); + } + return false; + }); + + if (!isTokenValid) { + const tokenForLogging = jwt.decode(accessToken); + const remoteAddress = getRemoteAddress(this.req); + + sails.log.warn( + `invalid token: sub: "${tokenForLogging.sub}" issuer: "${tokenForLogging.iss}" audience: "${tokenForLogging.aud}" exp: ${tokenForLogging.exp} (IP: ${remoteAddress})`, + ); + throw Errors.INVALID_TOKEN; + } + return validToken; +}; + +const getUserInfo = async (accessToken, options) => { + const issuer = await openidClient.Issuer.discover(options.issuer); + const oidcClient = new issuer.Client({ + client_id: 'irrelevant', + }); + const userInfo = await oidcClient.userinfo(accessToken); + return userInfo; +}; +const mergeUserData = (validToken, userInfo) => { + const oidcUser = { ...validToken, ...userInfo }; + sails.log.info(oidcUser); + return oidcUser; +}; +module.exports = { + inputs: { + token: { + type: 'string', + required: true, + }, + }, + + exits: { + invalidToken: { + responseType: 'unauthorized', + }, + }, + + async fn(inputs) { + const options = getJwtVerificationOptions(); + const validToken = await validateAndDecodeToken(inputs.token, options); + const userInfo = await getUserInfo(inputs.token, options); + const oidcUser = mergeUserData(validToken, userInfo); + + const now = new Date(); + let isAdmin = false; + if (sails.config.custom.oidcAdminRoles.includes('*')) isAdmin = true; + else if (Array.isArray(oidcUser[sails.config.custom.oidcRolesAttribute])) { + const userRoles = new Set(oidcUser[sails.config.custom.oidcRolesAttribute]); + isAdmin = sails.config.custom.oidcAdminRoles.findIndex((role) => userRoles.has(role)) > -1; + } + + const newUser = { + email: oidcUser.email, + password: '$sso$', // Prohibit password login for SSO accounts + isAdmin, + name: oidcUser.name, + username: oidcUser.preferred_username, + subscribeToOwnCards: false, + createdAt: now, + updatedAt: now, + }; + + const user = await User.findOrCreate({ username: userInfo.preferred_username }, newUser); + + const controlledFields = ['email', 'password', 'isAdmin', 'name', 'username']; + const updateFields = {}; + controlledFields.forEach((field) => { + if (user[field] !== newUser[field]) { + updateFields[field] = newUser[field]; + } + }); + + if (Object.keys(updateFields).length > 0) { + updateFields.updatedAt = now; + await User.updateOne({ id: user.id }).set(updateFields); + } + + const plankaToken = sails.helpers.utils.createToken(user.id); + + const remoteAddress = getRemoteAddress(this.req); + await Session.create({ + accessToken: plankaToken, + remoteAddress, + userId: user.id, + userAgent: this.req.headers['user-agent'], + }); + + return { + item: plankaToken, + }; + }, +}; diff --git a/server/api/helpers/utils/oidc-verify-token.js b/server/api/helpers/utils/oidc-verify-token.js new file mode 100644 index 0000000..6d1c680 --- /dev/null +++ b/server/api/helpers/utils/oidc-verify-token.js @@ -0,0 +1,36 @@ +const jwt = require('jsonwebtoken'); +const jwksClient = require('jwks-rsa'); + +const client = jwksClient({ + jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json', + requestHeaders: {}, // Optional + timeout: 30000, // Defaults to 30s +}); + +module.exports = { + inputs: { + token: { + type: 'string', + required: true, + }, + }, + + exits: { + invalidToken: {}, + }, + + async fn(inputs) { + let payload; + const keys = await client.getSigningKeys(); + try { + payload = jwt.verify(inputs.token, keys); + } catch (error) { + throw 'invalidToken'; + } + + return { + subject: payload.sub, + issuedAt: new Date(payload.iat * 1000), + }; + }, +}; diff --git a/server/config/custom.js b/server/config/custom.js index 8d8043c..0f86410 100644 --- a/server/config/custom.js +++ b/server/config/custom.js @@ -30,4 +30,9 @@ module.exports.custom = { attachmentsPath: path.join(sails.config.appPath, 'private', 'attachments'), attachmentsUrl: `${process.env.BASE_URL}/attachments`, + + oidcIssuer: process.env.OIDC_ISSUER, + oidcAudience: process.env.OIDC_AUDIENCE, + oidcRolesAttribute: process.env.OIDC_ROLES_ATTRIBUTE || 'groups', + oidcAdminRoles: process.env.OIDC_ADMIN_ROLES.split(',') || [], }; diff --git a/server/config/policies.js b/server/config/policies.js index 9095e72..afaa3f7 100644 --- a/server/config/policies.js +++ b/server/config/policies.js @@ -24,4 +24,5 @@ module.exports.policies = { 'projects/create': ['is-authenticated', 'is-admin'], 'access-tokens/create': true, + 'access-tokens/exchange': true, }; diff --git a/server/config/routes.js b/server/config/routes.js index 2453f91..81dcaf1 100644 --- a/server/config/routes.js +++ b/server/config/routes.js @@ -10,6 +10,7 @@ module.exports.routes = { 'POST /api/access-tokens': 'access-tokens/create', + 'POST /api/access-tokens/exchange': 'access-tokens/exchange', 'DELETE /api/access-tokens/me': 'access-tokens/delete', 'GET /api/users': 'users/index', diff --git a/server/package-lock.json b/server/package-lock.json index 42301b2..036a9b0 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -11,6 +11,7 @@ "dotenv-cli": "^6.0.0", "filenamify": "^4.3.0", "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.0.1", "knex": "^2.3.0", "lodash": "^4.17.21", "moment": "^2.29.4", @@ -199,12 +200,103 @@ "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==" + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "node_modules/@types/node": { + "version": "20.4.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz", + "integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", + "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -3718,6 +3810,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": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -3848,6 +3956,11 @@ "node": ">= 0.10" } }, + "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/localforage": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.3.0.tgz", @@ -3876,6 +3989,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "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.issafeinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.issafeinteger/-/lodash.issafeinteger-4.0.4.tgz", @@ -3935,6 +4053,29 @@ "node": ">=10" } }, + "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/machine": { "version": "15.2.2", "resolved": "https://registry.npmjs.org/machine/-/machine-15.2.2.tgz", @@ -8078,12 +8219,103 @@ } } }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==" + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==", + "requires": { + "@types/node": "*" + } + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "@types/node": { + "version": "20.4.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz", + "integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==" + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", + "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "requires": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -10815,6 +11047,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": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -10905,6 +11150,11 @@ } } }, + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "localforage": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.3.0.tgz", @@ -10927,6 +11177,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "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.issafeinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.issafeinteger/-/lodash.issafeinteger-4.0.4.tgz", @@ -10977,6 +11232,31 @@ "yallist": "^4.0.0" } }, + "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==" + } + } + }, "machine": { "version": "15.2.2", "resolved": "https://registry.npmjs.org/machine/-/machine-15.2.2.tgz", diff --git a/server/package.json b/server/package.json index bd528be..ff3980d 100644 --- a/server/package.json +++ b/server/package.json @@ -32,6 +32,7 @@ "dotenv-cli": "^6.0.0", "filenamify": "^4.3.0", "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.0.1", "knex": "^2.3.0", "lodash": "^4.17.21", "moment": "^2.29.4",