open-webui: SSO via authentik (OIDC authorization-code flow)
- quadlet: OAUTH_*/OPENID_* env + EnvironmentFile for the client secret (secret stays off-repo in /home/dank/Containers/open-webui/env, mode 600) - authentik objects created live: application 'open-webui', OAuth2 provider 'Open WebUI' (Strict redirect URI), 'Open WebUI roles' scope mapping, entitlements Open WebUI Users/Admins bound to openwebui_users/admins - e2e: 18-openwebui-sso.spec.ts (silent SSO round-trip) + prefix in config - unit tests: OIDC wiring + secret-hygiene assertions - docs: SSO section + history entry
This commit is contained in:
1 parent
6ffcde0197
commit
b48de913b8
5 files changed
+130
-2
No files matched your search
@@ -28,8 +28,9 @@ export default defineConfig({
|
||||
// whose prefix is not listed in any project silently never runs — a
|
||||
// green suite with a test that does not exist. 17- joined on 2026-09-16
|
||||
// (the hermes vhost, whose upstream is a host address rather than a
|
||||
// container name, so it needs its own pass condition).
|
||||
testMatch: /(10|12|13|16|17)-.*\.spec\.ts/,
|
||||
// container name, so it needs its own pass condition). 18- joined on
|
||||
// 2026-09-24 (OpenWebUI SSO through authentik).
|
||||
testMatch: /(10|12|13|16|17|18)-.*\.spec\.ts/,
|
||||
use: { storageState: ".auth/qa-e2e.json" },
|
||||
dependencies: ["setup"],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
// OpenWebUI (ai.franksdank.cloud) authenticates through authentik via the
|
||||
// OIDC authorization-code flow. With ONLY the standard authentik session,
|
||||
// visiting the vhost must show the "Continue with authentik" button, and
|
||||
// clicking it must complete the round-trip silently (no password prompt on
|
||||
// the IdP) and land in the logged-in app.
|
||||
|
||||
test("SSO: OpenWebUI login completes through authentik silently", async ({ page }) => {
|
||||
test.setTimeout(180_000);
|
||||
await page.goto("https://ai.franksdank.cloud/");
|
||||
|
||||
// The app is a React SPA; the login page (no local session) renders the
|
||||
// OAuth button labelled after the provider name.
|
||||
const ssoButton = page.getByRole("button", { name: /continue with authentik/i });
|
||||
await expect(ssoButton).toBeVisible({ timeout: 90_000 });
|
||||
|
||||
// The round-trip: ai -> auth (silent, the session is already there) ->
|
||||
// back to ai with the code. No authentik password form may appear.
|
||||
await ssoButton.click();
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const body = (await page.locator("body").innerText()).toLowerCase();
|
||||
return !body.includes("continue with authentik");
|
||||
},
|
||||
{ timeout: 90_000 },
|
||||
)
|
||||
.toBe(true);
|
||||
await expect
|
||||
.poll(() => new URL(page.url()).hostname, { timeout: 60_000 })
|
||||
.toBe("ai.franksdank.cloud");
|
||||
const body = await page.locator("body").innerText();
|
||||
expect(body.toLowerCase()).not.toContain("powered by authentik"); // IdP flow shell
|
||||
expect(body.toLowerCase()).not.toContain("password"); // no credential prompt
|
||||
});
|
||||
@@ -89,3 +89,36 @@ QDIR="$REPO_ROOT/dankclaw/open-webui/quadlet"
|
||||
fi
|
||||
has_no_leaked_secret "$QDIR/open-webui.container"
|
||||
}
|
||||
|
||||
@test "open-webui OIDC: provider URL points at the open-webui application on auth.franksdank.cloud" {
|
||||
# The OAuth client lives in authentik (application slug "open-webui");
|
||||
# the container reaches the IdP over the public URL (Caddy publishes
|
||||
# 0.0.0.0:443, Pi-hole resolves the name to the host LAN IP).
|
||||
grep -qE '^Environment=OPENID_PROVIDER_URL=https://auth\.franksdank\.cloud/application/o/open-webui/\.well-known/openid-configuration$' \
|
||||
"$QDIR/open-webui.container" \
|
||||
|| { echo "open-webui.container: OPENID_PROVIDER_URL must point at the open-webui application on auth.franksdank.cloud"; return 1; }
|
||||
}
|
||||
|
||||
@test "open-webui OIDC: redirect URI matches the ai.franksdank.cloud vhost" {
|
||||
# The browser-side callback must land on the published vhost; the
|
||||
# authentik provider allows exactly this URI (Strict).
|
||||
grep -qE '^Environment=OPENID_REDIRECT_URI=https://ai\.franksdank\.cloud/oauth/oidc/callback$' \
|
||||
"$QDIR/open-webui.container" \
|
||||
|| { echo "open-webui.container: OPENID_REDIRECT_URI must be the ai.franksdank.cloud callback"; return 1; }
|
||||
}
|
||||
|
||||
@test "open-webui OIDC: WEBUI_URL, signup, email merge, and role management" {
|
||||
grep -qE '^Environment=WEBUI_URL=https://ai\.franksdank\.cloud$' "$QDIR/open-webui.container"
|
||||
grep -qE '^Environment=ENABLE_OAUTH_SIGNUP=true' "$QDIR/open-webui.container"
|
||||
grep -qE '^Environment=OAUTH_MERGE_ACCOUNTS_BY_EMAIL=true' "$QDIR/open-webui.container"
|
||||
grep -qE '^Environment=ENABLE_OAUTH_ROLE_MANAGEMENT=true' "$QDIR/open-webui.container"
|
||||
grep -qE '^Environment=OAUTH_SCOPES=openid email profile roles$' "$QDIR/open-webui.container"
|
||||
}
|
||||
|
||||
@test "open-webui OIDC: client secret lives in the host env file, not the unit" {
|
||||
# The client ID is a public identifier and may be tracked; the secret
|
||||
# must come from the git-ignored host env file (mode 600).
|
||||
grep -qE '^EnvironmentFile=/home/dank/Containers/open-webui/env$' "$QDIR/open-webui.container" \
|
||||
|| { echo "open-webui.container: must load the client secret from the host env file"; return 1; }
|
||||
! grep -q 'OAUTH_CLIENT_SECRET' "$QDIR/open-webui.container"
|
||||
}
|
||||
Reference in new issue
Block a user