Browse Source

support generic oidc

dev-oauth
Bernd Storath 1 week ago
parent
commit
c4524fbd44
  1. 54
      docs/content/advanced/config/external-authentication.md
  2. 38
      src/app/pages/login.vue
  3. 4
      src/server/api/auth/[provider]/callback.get.ts
  4. 9
      src/server/api/auth/methods.get.ts
  5. 2
      src/server/database/migrations/0005_lean_the_executioner.sql
  6. 2
      src/server/database/migrations/meta/0005_snapshot.json
  7. 4
      src/server/database/migrations/meta/_journal.json
  8. 24
      src/server/utils/oauth.ts

54
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 |
<!-- TODO Github Setup -->
### 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://<your-domain>/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

38
src/app/pages/login.vue

@ -15,29 +15,53 @@
<div v-if="authMethods" class="flex flex-col gap-5">
<!-- Google OAuth Button -->
<a
v-if="authMethods.providers?.google"
v-if="authMethods.providers?.google?.enabled"
href="/api/auth/google"
class="flex cursor-pointer items-center justify-center gap-2 rounded border border-gray-300 bg-white py-2 text-sm text-gray-700 shadow-sm transition hover:bg-gray-50 dark:border-neutral-600 dark:bg-neutral-800 dark:text-neutral-200 dark:hover:bg-neutral-700"
>
<IconsBrandsGoogle class="h-4 w-4" />
<span>{{ $t('login.signInWith', ['Google']) }}</span>
<span>
{{
$t('login.signInWith', [
authMethods.providers.google.friendlyName,
])
}}
</span>
</a>
<!-- GitHub OAuth Button -->
<a
v-if="authMethods.providers?.github"
v-if="authMethods.providers?.github?.enabled"
href="/api/auth/github"
class="flex cursor-pointer items-center justify-center gap-2 rounded border border-gray-300 bg-white py-2 text-sm text-gray-700 shadow-sm transition hover:bg-gray-50 dark:border-neutral-600 dark:bg-neutral-800 dark:text-neutral-200 dark:hover:bg-neutral-700"
>
<IconsBrandsGitHub class="h-4 w-4" />
<span>{{ $t('login.signInWith', ['GitHub']) }}</span>
<span>
{{
$t('login.signInWith', [
authMethods.providers.github.friendlyName,
])
}}
</span>
</a>
<!-- OIDC OAuth Button -->
<a
v-if="authMethods.providers?.oidc?.enabled"
href="/api/auth/oidc"
class="flex cursor-pointer items-center justify-center rounded border border-gray-300 bg-white py-2 text-sm text-gray-700 shadow-sm transition hover:bg-gray-50 dark:border-neutral-600 dark:bg-neutral-800 dark:text-neutral-200 dark:hover:bg-neutral-700"
>
<span>
{{
$t('login.signInWith', [authMethods.providers.oidc.friendlyName])
}}
</span>
</a>
<!-- Divider -->
<div v-if="authMethods.oauthEnabled" class="flex items-center gap-2">
<div class="h-px flex-1 bg-gray-300 dark:bg-neutral-600"></div>
<span class="text-xs text-gray-500 dark:text-neutral-400">{{
$t('login.or')
}}</span>
<span class="text-xs text-gray-500 dark:text-neutral-400">
{{ $t('login.or') }}
</span>
<div class="h-px flex-1 bg-gray-300 dark:bg-neutral-600"></div>
</div>
</div>

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

9
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<OAUTH_PROVIDER, boolean>
{} as Partial<
Record<OAUTH_PROVIDER, { enabled: true; friendlyName: string }>
>
),
oauthEnabled:
WG_ENV.OAUTH_PROVIDERS !== undefined && WG_ENV.OAUTH_PROVIDERS.length > 0,

2
src/server/database/migrations/0005_supreme_groot.sql → 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

2
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": {

4
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
}
]

24
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)) {

Loading…
Cancel
Save