diff --git a/web/package-lock.json b/web/package-lock.json index 08a019f30..6621a19ad 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -37,6 +37,7 @@ "fuse.js": "^7.0.0", "lit": "^2.8.0", "mermaid": "^10.6.1", + "oidc-client-ts": "^2.4.0", "rapidoc": "^9.3.4", "style-mod": "^4.1.0", "webcomponent-qr-code": "^1.2.0", @@ -9292,6 +9293,11 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -13226,6 +13232,11 @@ "node": "*" } }, + "node_modules/jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -14789,6 +14800,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oidc-client-ts": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.4.0.tgz", + "integrity": "sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w==", + "dependencies": { + "crypto-js": "^4.2.0", + "jwt-decode": "^3.1.2" + }, + "engines": { + "node": ">=12.13.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", diff --git a/web/src/admin/AdminInterface/AdminInterface.ts b/web/src/admin/AdminInterface/AdminInterface.ts index 4485a5b2a..1afac9d0c 100644 --- a/web/src/admin/AdminInterface/AdminInterface.ts +++ b/web/src/admin/AdminInterface/AdminInterface.ts @@ -1,5 +1,6 @@ import { ROUTES } from "@goauthentik/admin/Routes"; import { OAuthInterface } from "@goauthentik/app/common/oauth/interface"; +import { adminSettings } from "@goauthentik/app/common/oauth/settings"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_API_DRAWER_TOGGLE, @@ -18,6 +19,7 @@ import { getURLParam, updateURLParams } from "@goauthentik/elements/router/Route import "@goauthentik/elements/router/RouterOutlet"; import "@goauthentik/elements/sidebar/Sidebar"; import "@goauthentik/elements/sidebar/SidebarItem"; +import { UserManagerSettings } from "oidc-client-ts"; import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; @@ -48,6 +50,10 @@ export class AdminInterface extends OAuthInterface { @state() user?: SessionUser; + get oauthSettings(): UserManagerSettings { + return adminSettings; + } + static get styles(): CSSResult[] { return [ PFBase, diff --git a/web/src/admin/Routes.ts b/web/src/admin/Routes.ts index 9ee971ad5..0119faaf0 100644 --- a/web/src/admin/Routes.ts +++ b/web/src/admin/Routes.ts @@ -1,5 +1,6 @@ import "@goauthentik/admin/admin-overview/AdminOverviewPage"; import "@goauthentik/app/common/oauth/callback"; +import { adminSettings } from "@goauthentik/app/common/oauth/settings"; import "@goauthentik/app/common/oauth/signout"; import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route"; @@ -11,10 +12,13 @@ export const ROUTES: Route[] = [ new Route(new RegExp("^#.*")).redirect("/administration/overview"), new Route(new RegExp("^/library$")).redirect("/if/user/", true), new Route(new RegExp("^/oauth-callback/(?.*)$"), async (args) => { - return html``; + return html``; }), new Route(new RegExp("^/oauth-signout$"), async () => { - return html``; + return html``; }), // statically imported since this is the default route new Route(new RegExp("^/administration/overview$"), async () => { diff --git a/web/src/common/oauth/callback.ts b/web/src/common/oauth/callback.ts index 830d4af76..b52122918 100644 --- a/web/src/common/oauth/callback.ts +++ b/web/src/common/oauth/callback.ts @@ -1,7 +1,6 @@ import { state } from "@goauthentik/app/common/oauth/constants"; -import { settings } from "@goauthentik/app/common/oauth/settings"; import { refreshMe } from "@goauthentik/app/common/users"; -import { User, UserManager } from "oidc-client-ts"; +import { User, UserManager, UserManagerSettings } from "oidc-client-ts"; import { LitElement } from "lit"; import { customElement, property } from "lit/decorators.js"; @@ -10,8 +9,13 @@ import { customElement, property } from "lit/decorators.js"; export class OAuthCallback extends LitElement { @property() params?: string; + @property({ attribute: false }) + settings?: UserManagerSettings; async firstUpdated(): Promise { - const client = new UserManager(settings); + if (!this.settings) { + return; + } + const client = new UserManager(this.settings); const user = (await client.signinCallback(`#${this.params}`)) as User; const st = user.state as state; window.location.assign(st.url); diff --git a/web/src/common/oauth/interface.ts b/web/src/common/oauth/interface.ts index 1b4248017..9eb97a917 100644 --- a/web/src/common/oauth/interface.ts +++ b/web/src/common/oauth/interface.ts @@ -1,16 +1,17 @@ import { state } from "@goauthentik/app/common/oauth/constants"; -import { settings } from "@goauthentik/app/common/oauth/settings"; import { Interface } from "@goauthentik/app/elements/Base"; -import { UserManager } from "oidc-client-ts"; +import { UserManager, UserManagerSettings } from "oidc-client-ts"; + +export abstract class OAuthInterface extends Interface { + abstract get oauthSettings(): UserManagerSettings; -export class OAuthInterface extends Interface { private async ensureLoggedIn() { - const client = new UserManager(settings); + const client = new UserManager(this.oauthSettings); const user = await client.getUser(); if (user !== null) { return; } - if (window.location.href.startsWith(settings.redirect_uri)) { + if (window.location.href.startsWith(this.oauthSettings.redirect_uri)) { return; } const s = new state(); diff --git a/web/src/common/oauth/middleware.ts b/web/src/common/oauth/middleware.ts index 70f7454f9..43c62b1be 100644 --- a/web/src/common/oauth/middleware.ts +++ b/web/src/common/oauth/middleware.ts @@ -1,11 +1,11 @@ -import { settings } from "@goauthentik/app/common/oauth/settings"; +import { adminSettings } from "@goauthentik/app/common/oauth/settings"; import { UserManager } from "oidc-client-ts"; import { FetchParams, Middleware, RequestContext } from "@goauthentik/api"; export class TokenMiddleware implements Middleware { async pre?(context: RequestContext): Promise { - const user = await new UserManager(settings).getUser(); + const user = await new UserManager(adminSettings).getUser(); if (user !== null) { // @ts-ignore context.init.headers["Authorization"] = `Bearer ${user.access_token}`; diff --git a/web/src/common/oauth/settings.ts b/web/src/common/oauth/settings.ts index 6924a1de2..03ed0c6a4 100644 --- a/web/src/common/oauth/settings.ts +++ b/web/src/common/oauth/settings.ts @@ -1,13 +1,25 @@ +import { MemoryStore } from "@goauthentik/app/common/oauth/storage"; import { Log, OidcClientSettings, UserManagerSettings } from "oidc-client-ts"; Log.setLogger(console); Log.setLevel(Log.DEBUG); -export const settings: OidcClientSettings & UserManagerSettings = { +export const userSettings: OidcClientSettings & UserManagerSettings = { + authority: `${window.location.origin}/application/o/authentik-user-interface/`, + redirect_uri: `${window.location.origin}/if/user/#/oauth-callback/`, + client_id: "authentik-user-interface", + scope: "openid profile email goauthentik.io/api", + response_mode: "fragment", + automaticSilentRenew: true, + userStore: new MemoryStore(), +}; + +export const adminSettings: OidcClientSettings & UserManagerSettings = { authority: `${window.location.origin}/application/o/authentik-admin-interface/`, redirect_uri: `${window.location.origin}/if/admin/#/oauth-callback/`, client_id: "authentik-admin-interface", scope: "openid profile email goauthentik.io/api", response_mode: "fragment", automaticSilentRenew: true, + userStore: new MemoryStore(), }; diff --git a/web/src/common/oauth/signout.ts b/web/src/common/oauth/signout.ts index 9bd06060a..8208c568a 100644 --- a/web/src/common/oauth/signout.ts +++ b/web/src/common/oauth/signout.ts @@ -1,13 +1,17 @@ -import { settings } from "@goauthentik/app/common/oauth/settings"; -import { UserManager } from "oidc-client-ts"; +import { UserManager, UserManagerSettings } from "oidc-client-ts"; import { LitElement } from "lit"; -import { customElement } from "lit/decorators.js"; +import { customElement, property } from "lit/decorators.js"; @customElement("ak-oauth-signout") export class OAuthSignout extends LitElement { + @property({ attribute: false }) + settings?: UserManagerSettings; async firstUpdated(): Promise { - const client = new UserManager(settings); + if (!this.settings) { + return; + } + const client = new UserManager(this.settings); await client.signoutRedirect(); } } diff --git a/web/src/common/oauth/storage.ts b/web/src/common/oauth/storage.ts new file mode 100644 index 000000000..8a5a5cd56 --- /dev/null +++ b/web/src/common/oauth/storage.ts @@ -0,0 +1,20 @@ +import { WebStorageStateStore } from "oidc-client-ts"; + +export class MemoryStore extends WebStorageStateStore { + private map: Map = new Map(); + async set(key: string, value: string): Promise { + this.map.set(key, value); + } + async get(key: string): Promise { + const value = this.map.get(key); + return value ? value : null; + } + async remove(key: string): Promise { + const value = await this.get(key); + this.map.delete(key); + return value; + } + async getAllKeys(): Promise { + return Array.from(this.map.keys()); + } +} diff --git a/web/src/user/Routes.ts b/web/src/user/Routes.ts index cb4f5f7f6..fa3609654 100644 --- a/web/src/user/Routes.ts +++ b/web/src/user/Routes.ts @@ -1,4 +1,5 @@ import "@goauthentik/app/common/oauth/callback"; +import { userSettings } from "@goauthentik/app/common/oauth/settings"; import "@goauthentik/app/common/oauth/signout"; import { Route } from "@goauthentik/elements/router/Route"; import "@goauthentik/user/LibraryPage/LibraryPage"; @@ -10,10 +11,13 @@ export const ROUTES: Route[] = [ new Route(new RegExp("^/$")).redirect("/library"), new Route(new RegExp("^#.*")).redirect("/library"), new Route(new RegExp("^/oauth-callback/(?.*)$"), async (args) => { - return html``; + return html``; }), new Route(new RegExp("^/oauth-signout$"), async () => { - return html``; + return html``; }), new Route(new RegExp("^/library$"), async () => html``), new Route(new RegExp("^/settings$"), async () => { diff --git a/web/src/user/UserInterface.ts b/web/src/user/UserInterface.ts index 1ebc12ced..023defe2e 100644 --- a/web/src/user/UserInterface.ts +++ b/web/src/user/UserInterface.ts @@ -1,4 +1,5 @@ import { OAuthInterface } from "@goauthentik/app/common/oauth/interface"; +import { userSettings } from "@goauthentik/app/common/oauth/settings"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_API_DRAWER_TOGGLE, @@ -23,6 +24,7 @@ import { DefaultTenant } from "@goauthentik/elements/sidebar/SidebarBrand"; import "@goauthentik/elements/sidebar/SidebarItem"; import { ROUTES } from "@goauthentik/user/Routes"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; +import { UserManagerSettings } from "oidc-client-ts"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; @@ -56,6 +58,10 @@ export class UserInterface extends OAuthInterface { @state() me?: SessionUser; + get oauthSettings(): UserManagerSettings { + return userSettings; + } + static get styles(): CSSResult[] { return [ PFBase,