From c4524fbd44cd084a07ee3f2fea3e72cf5a90e7eb Mon Sep 17 00:00:00 2001 From: Bernd Storath Date: Wed, 27 May 2026 12:32:35 +0200 Subject: [PATCH] support generic oidc --- .../config/external-authentication.md | 54 +++++++++++++++++-- src/app/pages/login.vue | 38 ++++++++++--- .../api/auth/[provider]/callback.get.ts | 4 +- src/server/api/auth/methods.get.ts | 9 +++- ...root.sql => 0005_lean_the_executioner.sql} | 2 +- .../migrations/meta/0005_snapshot.json | 2 +- .../database/migrations/meta/_journal.json | 4 +- src/server/utils/oauth.ts | 24 +++++++++ 8 files changed, 116 insertions(+), 21 deletions(-) rename src/server/database/migrations/{0005_supreme_groot.sql => 0005_lean_the_executioner.sql} (76%) diff --git a/docs/content/advanced/config/external-authentication.md b/docs/content/advanced/config/external-authentication.md index 2a4dee8b..fb1a9393 100644 --- a/docs/content/advanced/config/external-authentication.md +++ b/docs/content/advanced/config/external-authentication.md @@ -8,10 +8,11 @@ title: External Authentication To enable OAuth set the env var `OAUTH_PROVIDERS` to any of the following providers: -| Provider | Value | -| ----------------- | -------- | -| [Google](#google) | `google` | -| [GitHub](#github) | `github` | +| Provider | Value | +| ----------------------------- | -------- | +| [Google](#google) | `google` | +| [GitHub](#github) | `github` | +| [Generic OIDC](#generic-oidc) | `oidc` | You can enable multiple providers by separating them with a comma: @@ -41,9 +42,52 @@ e.g. `google,github` | `OAUTH_GITHUB_CLIENT_ID` | ✔️ | `xxx` | GitHub Client ID | | `OAUTH_GITHUB_CLIENT_SECRET` | ✔️ | `xxx` | GitHub Client Secret | + + ### Generic OIDC -TODO +This supports generic OIDC providers like Authelia, Authentik, etc. + +The provider needs to support: + +- PKCE +- default scopes: `openid email profile` +- Valid HTTPS +- Client Secret Authentication `client_secret_post` + +| Env | Required | Default | Example | Description | +| -------------------------- | -------- | ------- | -------------------------- | ------------------ | +| `OAUTH_OIDC_SERVER` | ✔️ | - | `https://auth.example.com` | OIDC Server | +| `OAUTH_OIDC_CLIENT_ID` | ✔️ | - | `xxx` | OIDC Client ID | +| `OAUTH_OIDC_CLIENT_SECRET` | ✔️ | - | `xxx` | OIDC Client Secret | +| `OAUTH_OIDC_NAME` | ✖️ | OIDC | `Authelia` | Provider Name | + +#### Authelia Setup + +Generate Client ID and Secret: + +```shell +# Client ID +docker run --rm authelia/authelia:latest authelia crypto rand --length 72 --charset rfc3986 +# Client Secret +docker run --rm authelia/authelia:latest authelia crypto hash generate pbkdf2 --variant sha512 --random --random.length 72 --random.charset rfc3986 +``` + +```yaml +- client_id: '...' + client_name: wg-easy + client_secret: '$pbkdf2-...' + redirect_uris: + - https:///api/auth/oidc/callback + scopes: + - openid + - profile + - email + authorization_policy: one_factor + pre_configured_consent_duration: 1 week + require_pkce: true + token_endpoint_auth_method: client_secret_post +``` ### Generic OAuth diff --git a/src/app/pages/login.vue b/src/app/pages/login.vue index 387943c7..dec0ae32 100644 --- a/src/app/pages/login.vue +++ b/src/app/pages/login.vue @@ -15,29 +15,53 @@ diff --git a/src/server/api/auth/[provider]/callback.get.ts b/src/server/api/auth/[provider]/callback.get.ts index b1fd7105..2062062b 100644 --- a/src/server/api/auth/[provider]/callback.get.ts +++ b/src/server/api/auth/[provider]/callback.get.ts @@ -22,7 +22,7 @@ export default defineEventHandler(async (event) => { expectedNonce: providerConfig.isOIDC === false ? undefined : session.data.oauth_nonce, expectedState: session.data.oauth_state, - idTokenExpected: providerConfig.isOIDC, + idTokenExpected: providerConfig.isOIDC ?? true, }); type SubjectType = string | undefined | typeof client.skipSubjectCheck; @@ -45,8 +45,6 @@ export default defineEventHandler(async (event) => { userInfo = await client.fetchUserInfo(config, tokens.access_token, subject); } - console.log(userInfo); - if (!userInfo.sub) { throw createError({ statusCode: 400, diff --git a/src/server/api/auth/methods.get.ts b/src/server/api/auth/methods.get.ts index 5e71a52e..872072da 100644 --- a/src/server/api/auth/methods.get.ts +++ b/src/server/api/auth/methods.get.ts @@ -2,10 +2,15 @@ export default defineEventHandler(() => { return { providers: WG_ENV.OAUTH_PROVIDERS?.reduce( (acc, curr) => { - acc[curr] = true; + acc[curr] = { + enabled: true, + friendlyName: OAUTH_PROVIDERS[curr].friendlyName, + }; return acc; }, - {} as Record + {} as Partial< + Record + > ), oauthEnabled: WG_ENV.OAUTH_PROVIDERS !== undefined && WG_ENV.OAUTH_PROVIDERS.length > 0, diff --git a/src/server/database/migrations/0005_supreme_groot.sql b/src/server/database/migrations/0005_lean_the_executioner.sql similarity index 76% rename from src/server/database/migrations/0005_supreme_groot.sql rename to src/server/database/migrations/0005_lean_the_executioner.sql index c5265489..8aa3fda2 100644 --- a/src/server/database/migrations/0005_supreme_groot.sql +++ b/src/server/database/migrations/0005_lean_the_executioner.sql @@ -15,7 +15,7 @@ CREATE TABLE `__new_users_table` ( `updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL ); --> statement-breakpoint -INSERT INTO `__new_users_table`("id", "username", "password", "email", "name", "role", "totp_key", "totp_verified", "enabled", "oauth_provider", "oauth_id", "created_at", "updated_at") SELECT "id", "username", "password", "email", "name", "role", "totp_key", "totp_verified", "enabled", "oauth_provider", "oauth_id", "created_at", "updated_at" FROM `users_table`;--> statement-breakpoint +INSERT INTO `__new_users_table`("id", "username", "password", "email", "name", "role", "totp_key", "totp_verified", "enabled", "created_at", "updated_at") SELECT "id", "username", "password", "email", "name", "role", "totp_key", "totp_verified", "enabled", "created_at", "updated_at" FROM `users_table`;--> statement-breakpoint DROP TABLE `users_table`;--> statement-breakpoint ALTER TABLE `__new_users_table` RENAME TO `users_table`;--> statement-breakpoint PRAGMA foreign_keys=ON;--> statement-breakpoint diff --git a/src/server/database/migrations/meta/0005_snapshot.json b/src/server/database/migrations/meta/0005_snapshot.json index 639d8a8f..c8f139e5 100644 --- a/src/server/database/migrations/meta/0005_snapshot.json +++ b/src/server/database/migrations/meta/0005_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "840b00a9-8e9d-4bc9-a0fa-6185ebb01a46", + "id": "8cfd2b7c-5483-4b98-b95e-f9c741f32821", "prevId": "0f072f91-cd10-4702-ae7b-245255d69d1e", "tables": { "clients_table": { diff --git a/src/server/database/migrations/meta/_journal.json b/src/server/database/migrations/meta/_journal.json index a00425fc..991a31f8 100644 --- a/src/server/database/migrations/meta/_journal.json +++ b/src/server/database/migrations/meta/_journal.json @@ -40,8 +40,8 @@ { "idx": 5, "version": "6", - "when": 1779862471761, - "tag": "0005_supreme_groot", + "when": 1779865249257, + "tag": "0005_lean_the_executioner", "breakpoints": true } ] diff --git a/src/server/utils/oauth.ts b/src/server/utils/oauth.ts index a22ab51e..f8dae9a1 100644 --- a/src/server/utils/oauth.ts +++ b/src/server/utils/oauth.ts @@ -2,6 +2,7 @@ import type { H3Event } from 'h3'; import { discovery } from 'openid-client'; type OAuthConfig = { + friendlyName: string; server: string; scope: string; clientId: string | undefined; @@ -12,6 +13,7 @@ type OAuthConfig = { }; const GoogleConfig: OAuthConfig = { + friendlyName: 'Google', server: 'https://accounts.google.com', scope: 'openid email profile', clientId: process.env.OAUTH_GOOGLE_CLIENT_ID, @@ -22,6 +24,7 @@ const GoogleConfig: OAuthConfig = { }, }; const GithubConfig: OAuthConfig = { + friendlyName: 'GitHub', server: 'https://github.com/login/oauth', scope: 'read:user user:email', clientId: process.env.OAUTH_GITHUB_CLIENT_ID, @@ -33,10 +36,19 @@ const GithubConfig: OAuthConfig = { isOIDC: false, userInfoFlow: 'github', }; +const OidcConfig: OAuthConfig = { + friendlyName: process.env.OAUTH_OIDC_NAME ?? 'OIDC', + server: process.env.OAUTH_OIDC_SERVER ?? '', + scope: 'openid email profile', + clientId: process.env.OAUTH_OIDC_CLIENT_ID, + clientSecret: process.env.OAUTH_OIDC_CLIENT_SECRET, + params: {}, +}; export const OAUTH_PROVIDERS = { google: GoogleConfig, github: GithubConfig, + oidc: OidcConfig, }; export type OAUTH_PROVIDER = keyof typeof OAUTH_PROVIDERS; @@ -62,6 +74,11 @@ export function isConfiguredOauthProvider( return true; } +function isEnabledProvider(provider: OAUTH_PROVIDER) { + return WG_ENV.OAUTH_PROVIDERS?.includes(provider); +} + +// TODO: simplify logic between WG_ENV.OAUTH_PROVIDERS and buildOauthConfig export async function buildOauthConfig(event: H3Event) { const provider = getRouterParam(event, 'provider'); if (!provider || !isValidOauthProvider(provider)) { @@ -71,6 +88,13 @@ export async function buildOauthConfig(event: H3Event) { }); } + if (!isEnabledProvider(provider)) { + throw createError({ + statusCode: 400, + statusMessage: 'Disabled provider', + }); + } + const oauthProvider = OAUTH_PROVIDERS[provider]; if (!isConfiguredOauthProvider(oauthProvider)) {