Loading package-lock.json +17 −38 Original line number Diff line number Diff line Loading @@ -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" } Loading Loading @@ -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", Loading Loading @@ -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", Loading Loading @@ -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", Loading Loading @@ -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", Loading Loading @@ -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", packages/client/src/components/AuthErrors.tsx +0 −46 Original line number Diff line number Diff line Loading @@ -35,11 +35,6 @@ export const AuthErrors = () => { return ( <> <RPError isOpen={params.get(Params.TYPE) === "rp"} onClose={onClose} params={params} /> <OPError isOpen={params.get(Params.TYPE) === "op"} onClose={onClose} Loading Loading @@ -86,47 +81,6 @@ const BannedError = ({ ); }; /** * This is for RP errors, which can be triggered by modifying data sent in callbacks * * These errors can typically be retried * * @param param0 * @returns */ const RPError = ({ isOpen, onClose, params, }: { isOpen: boolean; onClose: () => void; params: URLSearchParams; }) => { return ( <Modal isOpen={isOpen} onClose={onClose} isDismissable={false}> <ModalContent> {(onClose) => ( <> <ModalHeader>Login Error</ModalHeader> <ModalBody> <b>Error:</b> {params.get(Params.ERROR)} <br /> <br /> <b>Error Description:</b> {params.get(Params.ERROR_DESC)} </ModalBody> <ModalFooter> <Button color="primary" href="/api/login" as={Link}> Login </Button> </ModalFooter> </> )} </ModalContent> </Modal> ); }; /** * This is for OP errors, these might not be retryable * @param param0 Loading packages/server/package.json +1 −1 Original line number Diff line number Diff line Loading @@ -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", Loading packages/server/src/api/client.ts +9 −39 Original line number Diff line number Diff line 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"; Loading Loading @@ -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) => { Loading Loading @@ -78,34 +73,19 @@ app.get("/callback", RateLimiter.HIGH, async (req, res) => { return; } let exchange: TokenSet; let exchange: Awaited<ReturnType<typeof OpenID.exchangeToken>>; 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( "/" + Loading @@ -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( Loading @@ -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; Loading @@ -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("@"); Loading packages/server/src/lib/oidc.ts +27 −8 Original line number Diff line number Diff line import { BaseClient, Issuer } from "openid-client"; import * as openid from "openid-client"; class OpenID_ { issuer: Issuer<BaseClient> = {} as any; client: BaseClient = {} as any; config: openid.Configuration = {} as any; async setup() { if (process.env.INHIBIT_LOGIN) { Loading @@ -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<Data extends {} = {}>( accessToken: string, expectedSub: string ): Promise<openid.UserInfoResponse & Data> { return openid.fetchUserInfo(this.config, accessToken, expectedSub) as any; } } export const OpenID = new OpenID_(); Loading
package-lock.json +17 −38 Original line number Diff line number Diff line Loading @@ -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" } Loading Loading @@ -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", Loading Loading @@ -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", Loading Loading @@ -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", Loading Loading @@ -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", Loading Loading @@ -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",
packages/client/src/components/AuthErrors.tsx +0 −46 Original line number Diff line number Diff line Loading @@ -35,11 +35,6 @@ export const AuthErrors = () => { return ( <> <RPError isOpen={params.get(Params.TYPE) === "rp"} onClose={onClose} params={params} /> <OPError isOpen={params.get(Params.TYPE) === "op"} onClose={onClose} Loading Loading @@ -86,47 +81,6 @@ const BannedError = ({ ); }; /** * This is for RP errors, which can be triggered by modifying data sent in callbacks * * These errors can typically be retried * * @param param0 * @returns */ const RPError = ({ isOpen, onClose, params, }: { isOpen: boolean; onClose: () => void; params: URLSearchParams; }) => { return ( <Modal isOpen={isOpen} onClose={onClose} isDismissable={false}> <ModalContent> {(onClose) => ( <> <ModalHeader>Login Error</ModalHeader> <ModalBody> <b>Error:</b> {params.get(Params.ERROR)} <br /> <br /> <b>Error Description:</b> {params.get(Params.ERROR_DESC)} </ModalBody> <ModalFooter> <Button color="primary" href="/api/login" as={Link}> Login </Button> </ModalFooter> </> )} </ModalContent> </Modal> ); }; /** * This is for OP errors, these might not be retryable * @param param0 Loading
packages/server/package.json +1 −1 Original line number Diff line number Diff line Loading @@ -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", Loading
packages/server/src/api/client.ts +9 −39 Original line number Diff line number Diff line 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"; Loading Loading @@ -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) => { Loading Loading @@ -78,34 +73,19 @@ app.get("/callback", RateLimiter.HIGH, async (req, res) => { return; } let exchange: TokenSet; let exchange: Awaited<ReturnType<typeof OpenID.exchangeToken>>; 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( "/" + Loading @@ -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( Loading @@ -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; Loading @@ -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("@"); Loading
packages/server/src/lib/oidc.ts +27 −8 Original line number Diff line number Diff line import { BaseClient, Issuer } from "openid-client"; import * as openid from "openid-client"; class OpenID_ { issuer: Issuer<BaseClient> = {} as any; client: BaseClient = {} as any; config: openid.Configuration = {} as any; async setup() { if (process.env.INHIBIT_LOGIN) { Loading @@ -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<Data extends {} = {}>( accessToken: string, expectedSub: string ): Promise<openid.UserInfoResponse & Data> { return openid.fetchUserInfo(this.config, accessToken, expectedSub) as any; } } export const OpenID = new OpenID_();