Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/oidc-failure-cleanup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@evidence-browser/api": patch
---

Harden OIDC verification for issue #164 by asserting failed callbacks clear the signed transaction cookie and documenting the Authentik/Dex sign-off path.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ eb upload dist/evidence.zip --workspace ci-results --bundle-id "pr-42-run-1"

### Authentik OIDC setup

Manual sign-off path recorded against Authentik `2026.8.0`.
Manual sign-off path recorded for issue `#164` against Authentik `2026.8.0`.
The automated CI-compatible OIDC flow uses Dex in
`packages/web/e2e-oidc/oidc-flow.spec.ts`; Authentik remains the first target
IdP for operator setup and manual provider sign-off.

1. In Authentik, create a Property Mapping for the admin group claim if the default `groups` claim is not already suitable:
- Scope name: `groups`
Expand Down Expand Up @@ -164,6 +167,11 @@ Manual sign-off path recorded against Authentik `2026.8.0`.
```
6. Verify that an `evidence-browser-admins` member lands in `/admin`, and that a user outside `OIDC_ALLOWED_GROUPS` is rejected with the safe login error.

The regression suite also covers the local-login off switch: with
`AUTH_LOCAL_ENABLED=false`, `POST /api/auth/login` returns `403`, the login page
hides username/password fields, OIDC can still issue the normal
`evidence_session` cookie, and API keys remain valid.

Upgrade note: migration v1 rebuilds the `users` table. Back up `evidence.db` before upgrading an existing instance.

### Storage
Expand Down
2 changes: 1 addition & 1 deletion docs/ACCEPTANCE_CRITERIA.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ fixture-unicode.zip
- **Given** AUTH_BYPASS=false이고 OIDC provider가 설정됨
- **When** `/login` 페이지에서 로그인 버튼을 클릭한다
- **Then** OIDC provider의 인증 페이지로 리다이렉트되고, 인증 후 원래 페이지로 돌아온다
- **검증**: Verified — `packages/web/e2e-oidc/oidc-flow.spec.ts`가 `docker-compose.test.yml`의 Dex provider로 authorization-code flow를 실행한다.
- **검증**: Verified for issue #164 — `packages/web/e2e-oidc/oidc-flow.spec.ts`가 `docker-compose.test.yml`의 Dex provider로 authorization-code flow를 실행한다. API route tests additionally verify `state`/`nonce` failure handling and transaction-cookie clearing.
- **Evidence**: `.evidence/20260821-0216-feat-168-oidc-idp-stack-attempt1/` (PR #178 QA bundle; uploaded locally as `/w/default/b/20260821-0216-feat-168-oidc-idp-stack-attempt1`)
- **참고**: 실제 Authentik sign-off 절차는 `README.md`의 "Authentik OIDC setup"에 기록됨.

Expand Down
4 changes: 4 additions & 0 deletions packages/api/src/routes/auth-oidc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ describe("OIDC auth routes", () => {

expect(callback.status).toBe(302);
expect(callback.headers.get("location")).toBe("/login?error=oidc_failed");
expect(findSetCookie(callback.headers, "eb_oidc_tx")).toContain("Max-Age=0");
});

it("rejects a wrong or absent nonce", async () => {
Expand All @@ -293,6 +294,7 @@ describe("OIDC auth routes", () => {
);
expect(wrongNonce.status).toBe(302);
expect(wrongNonce.headers.get("location")).toBe("/login?error=oidc_failed");
expect(findSetCookie(wrongNonce.headers, "eb_oidc_tx")).toContain("Max-Age=0");

resetOidcDiscoveryCache();
mockIssuerFetch({ omitNonce: true });
Expand All @@ -302,6 +304,7 @@ describe("OIDC auth routes", () => {
);
expect(absentNonce.status).toBe(302);
expect(absentNonce.headers.get("location")).toBe("/login?error=oidc_failed");
expect(findSetCookie(absentNonce.headers, "eb_oidc_tx")).toContain("Max-Age=0");
});

it("rejects an expired transaction cookie before token exchange", async () => {
Expand All @@ -324,6 +327,7 @@ describe("OIDC auth routes", () => {

expect(callback.status).toBe(302);
expect(callback.headers.get("location")).toBe("/login?error=oidc_failed");
expect(findSetCookie(callback.headers, "eb_oidc_tx")).toContain("Max-Age=0");
expect(fetchMock).not.toHaveBeenCalledWith(tokenEndpoint, expect.anything());
});

Expand Down
Loading