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",