diff --git a/apps/api/src/routes/oidc.ts b/apps/api/src/routes/oidc.ts index 1386165..9594adb 100644 --- a/apps/api/src/routes/oidc.ts +++ b/apps/api/src/routes/oidc.ts @@ -27,7 +27,16 @@ export function registerOidcRoutes( return reply.code(400).send({ error: "no OIDC login in progress" }); } try { - const currentUrl = new URL(req.url, `http://${req.headers.host}`); + // Fastify only ever sees plain HTTP here -- TLS terminates at NPM/Cloudflare + // before reaching this process (see docs/oidc-setup.md). Building this URL + // from req.headers.host with a hardcoded "http://" sent the wrong scheme in + // the token exchange's redirect_uri, which Authentik rejects as a mismatch + // against the https:// URL registered for this provider. Since redirectUri + // is guaranteed correct (it's the exact value used to build the original + // authorize request), reuse its origin and only take the query string from + // the actual incoming request. + const currentUrl = new URL(redirectUri); + currentUrl.search = new URL(req.url, "http://placeholder").search; const { username } = await handleCallback(oidcConfig, currentUrl, oidcState, oidcCodeVerifier); req.session.username = username; req.session.oidcState = undefined; diff --git a/e2e/tests/oidc-login.spec.ts b/e2e/tests/oidc-login.spec.ts index 6b7c8d5..1594cea 100644 --- a/e2e/tests/oidc-login.spec.ts +++ b/e2e/tests/oidc-login.spec.ts @@ -20,7 +20,9 @@ test("OIDC login via Authentik stays on public domains throughout", async ({ pag await page.getByPlaceholder(/email or username/i).fill(USERNAME); await page.getByRole("button", { name: /log in|continue|next/i }).click(); - await page.getByLabel(/password/i).fill(PASSWORD); + // Authentik's password field has no