diff --git a/package-lock.json b/package-lock.json index f333ef80711ff47a9404a6d5ddb09c141c83df62..47e48f9074f4eeefdb2a0460256863defb568c13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12340,9 +12340,9 @@ } }, "node_modules/jose": { - "version": "4.15.9", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", - "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "version": "5.9.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", + "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -12574,17 +12574,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/magic-string": { "version": "0.30.8", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", @@ -12916,6 +12905,14 @@ "node": ">=0.10.0" } }, + "node_modules/oauth4webapi": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.4.tgz", + "integrity": "sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -13036,14 +13033,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/oidc-token-hash": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", - "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", - "engines": { - "node": "^10.13.0 || >=12.0.0" - } - }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -13081,27 +13070,17 @@ } }, "node_modules/openid-client": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", - "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.1.7.tgz", + "integrity": "sha512-JfY/KvQgOutmG2P+oVNKInE7zIh+im1MQOaO7g5CtNnTWMociA563WweiEMKfR9ry9XG3K2HGvj9wEqhCQkPMg==", "dependencies": { - "jose": "^4.15.9", - "lru-cache": "^6.0.0", - "object-hash": "^2.2.0", - "oidc-token-hash": "^5.0.3" + "jose": "^5.9.6", + "oauth4webapi": "^3.1.4" }, "funding": { "url": "https://github.com/sponsors/panva" } }, - "node_modules/openid-client/node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "engines": { - "node": ">= 6" - } - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -16664,7 +16643,7 @@ "express": "^4.21.2", "express-rate-limit": "^7.5.0", "express-session": "^1.18.1", - "openid-client": "^5.7.1", + "openid-client": "^6.1.7", "prom-client": "^15.1.3", "rate-limit-redis": "^4.2.0", "redis": "^4.7.0", diff --git a/packages/client/src/components/AuthErrors.tsx b/packages/client/src/components/AuthErrors.tsx index 934730d96d3f9e26d9eb6b55197ad9d9a02e2d4b..6a9d6ff3b3f1a8548b36b0598b0881dc13b59a81 100644 --- a/packages/client/src/components/AuthErrors.tsx +++ b/packages/client/src/components/AuthErrors.tsx @@ -35,11 +35,6 @@ export const AuthErrors = () => { return ( <> - void; - params: URLSearchParams; -}) => { - return ( - - - {(onClose) => ( - <> - Login Error - - Error: {params.get(Params.ERROR)} -
-
- Error Description: {params.get(Params.ERROR_DESC)} -
- - - - - )} -
-
- ); -}; - /** * This is for OP errors, these might not be retryable * @param param0 diff --git a/packages/server/package.json b/packages/server/package.json index fb8772094ffad79ab8fad879f06c48bf81cc4b6f..c7788be163f6bd6c780a95dd70dbbec47db8b240 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -38,7 +38,7 @@ "express": "^4.21.2", "express-rate-limit": "^7.5.0", "express-session": "^1.18.1", - "openid-client": "^5.7.1", + "openid-client": "^6.1.7", "prom-client": "^15.1.3", "rate-limit-redis": "^4.2.0", "redis": "^4.7.0", diff --git a/packages/server/src/api/client.ts b/packages/server/src/api/client.ts index 97897fe553104e69050fba4c214912f75c7cff07..0fe592c3c23f3d939e8ad4e29e9a02c7331b5bdc 100644 --- a/packages/server/src/api/client.ts +++ b/packages/server/src/api/client.ts @@ -1,7 +1,7 @@ import { Router } from "express"; import { prisma } from "../lib/prisma"; import { OpenID } from "../lib/oidc"; -import { TokenSet, errors as OIDC_Errors } from "openid-client"; +import { ResponseBodyError } from "openid-client"; import { getLogger } from "../lib/Logger"; import Canvas from "../lib/Canvas"; import { RateLimiter } from "../lib/RateLimiter"; @@ -44,12 +44,7 @@ app.get("/login", (req, res) => { return; } - res.redirect( - OpenID.client.authorizationUrl({ - prompt: "consent", - scope: "openid instance", - }) - ); + res.redirect(OpenID.getAuthorizationURL()); }); app.get("/logout", (req, res) => { @@ -78,34 +73,19 @@ app.get("/callback", RateLimiter.HIGH, async (req, res) => { return; } - let exchange: TokenSet; + let exchange: Awaited>; try { - const params = OpenID.client.callbackParams(req); - exchange = await OpenID.client.callback(OpenID.getRedirectUrl(), params); + exchange = await OpenID.exchangeToken(req.originalUrl); } catch (e) { - if (e instanceof OIDC_Errors.RPError) { - // client error - - res.redirect( - "/" + - buildQuery({ - TYPE: "rp", - ERROR: e.name, - ERROR_DESC: e.message, - }) - ); - return; - } - - if (e instanceof OIDC_Errors.OPError) { - // server error + console.error(e); + if (e instanceof ResponseBodyError) { switch (e.error) { case "invalid_client": // this happens when we're configured with invalid credentials Logger.error( - "OpenID is improperly configured. Cannot exchange tokens, do I have valid credetials?" + "OpenID is improperly configured. Cannot exchange tokens, do I have valid credentials?" ); res.redirect( "/" + @@ -127,16 +107,6 @@ app.get("/callback", RateLimiter.HIGH, async (req, res) => { ); return; } - - res.redirect( - "/" + - buildQuery({ - TYPE: "op", - ERROR: e.error, - ERROR_DESC: e.error_description, - }) - ); - return; } res.redirect( @@ -158,7 +128,7 @@ app.get("/callback", RateLimiter.HIGH, async (req, res) => { } try { - const whoami = await OpenID.client.userinfo<{ + const whoami = await OpenID.userInfo<{ instance: { software: { name: string; @@ -173,7 +143,7 @@ app.get("/callback", RateLimiter.HIGH, async (req, res) => { name?: string; }; }; - }>(exchange.access_token); + }>(exchange.access_token, exchange.claims()!.sub); const [username, hostname] = whoami.sub.split("@"); diff --git a/packages/server/src/lib/oidc.ts b/packages/server/src/lib/oidc.ts index 92447d029c2db27bd25f597e0940fa75b8781d93..a29e5b4d3c5a9c62e2884ab4267b22616156b934 100644 --- a/packages/server/src/lib/oidc.ts +++ b/packages/server/src/lib/oidc.ts @@ -1,8 +1,7 @@ -import { BaseClient, Issuer } from "openid-client"; +import * as openid from "openid-client"; class OpenID_ { - issuer: Issuer = {} as any; - client: BaseClient = {} as any; + config: openid.Configuration = {} as any; async setup() { if (process.env.INHIBIT_LOGIN) { @@ -14,18 +13,38 @@ class OpenID_ { const { AUTH_ENDPOINT, AUTH_CLIENT, AUTH_SECRET } = process.env; - this.issuer = await Issuer.discover(AUTH_ENDPOINT); - this.client = new this.issuer.Client({ - client_id: AUTH_CLIENT, + this.config = await openid.discovery(new URL(AUTH_ENDPOINT), AUTH_CLIENT, { client_secret: AUTH_SECRET, - response_types: ["code"], - redirect_uris: [this.getRedirectUrl()], }); } getRedirectUrl() { return process.env.OIDC_CALLBACK_HOST + "/api/callback"; } + + getAuthorizationURL() { + return openid + .buildAuthorizationUrl(this.config, { + redirect_uri: this.getRedirectUrl(), + prompt: "consent", + scope: "openid instance", + }) + .toString(); + } + + exchangeToken(relativePath: string) { + return openid.authorizationCodeGrant( + this.config, + new URL(relativePath, process.env.OIDC_CALLBACK_HOST) + ); + } + + userInfo( + accessToken: string, + expectedSub: string + ): Promise { + return openid.fetchUserInfo(this.config, accessToken, expectedSub) as any; + } } export const OpenID = new OpenID_();