Skip to content

feat(auth): add OIDC sign-out#44

Merged
rowan-stein merged 4 commits into
mainfrom
noa/issue-41
Apr 11, 2026
Merged

feat(auth): add OIDC sign-out#44
rowan-stein merged 4 commits into
mainfrom
noa/issue-41

Conversation

@casey-brooks

Copy link
Copy Markdown
Contributor

Summary

  • add end_session_endpoint metadata for OIDC sign-out
  • add mock OIDC + ConnectRPC server to support local e2e flows
  • add Playwright sign-out coverage (user menu + settings) and stabilize mock auth seeding

Testing

Fixes #41

Add end_session_endpoint metadata and E2E coverage for sign-out via the mock OIDC server.
@casey-brooks

Copy link
Copy Markdown
Contributor Author

Test & Lint Summary

  • npm run lint (no errors)
  • npm run typecheck (no errors)
  • npm test (vitest): 8 files, 35 tests passed
  • E2E_BASE_URL=http://127.0.0.1:5000 E2E_OIDC_AUTHORITY=http://127.0.0.1:5000 E2E_OIDC_CLIENT_ID=console-app E2E_OIDC_SCOPE='openid profile email' npm run test:e2e: 17 passed

@casey-brooks

Copy link
Copy Markdown
Contributor Author

Summary

  • extend sign-out polling timeout to 20s to cover slower OIDC redirects in CI

Test & Lint Summary

  • npm run lint (no errors)
  • npm run typecheck (no errors)
  • npm test (vitest): 8 files, 35 tests passed
  • E2E_BASE_URL=http://127.0.0.1:5000 E2E_OIDC_AUTHORITY=http://127.0.0.1:5000 E2E_OIDC_CLIENT_ID=console-app E2E_OIDC_SCOPE='openid profile email' npm run test:e2e: 17 passed

@casey-brooks

Copy link
Copy Markdown
Contributor Author

Summary

  • switch sign-out tests to wait on in-page sessionStorage clearing (avoids cross-origin polling issues)

Test & Lint Summary

  • npm run lint (no errors)
  • npm run typecheck (no errors)
  • npm test (vitest): 8 files, 35 tests passed
  • E2E_BASE_URL=http://127.0.0.1:5000 E2E_OIDC_AUTHORITY=http://127.0.0.1:5000 E2E_OIDC_CLIENT_ID=console-app E2E_OIDC_SCOPE='openid profile email' npm run test:e2e: 17 passed

Restrict OIDC seed injection to the app origin and redirect end-session to the post-logout URI.
@casey-brooks

Copy link
Copy Markdown
Contributor Author

Summary

  • restrict OIDC seed injection to the console origin to avoid re-seeding on mockauth redirects
  • update mock end-session handler to redirect to post_logout_redirect_uri

Test & Lint Summary

  • npm run lint (no errors)
  • npm run typecheck (no errors)
  • npm test (vitest): 8 files, 35 tests passed
  • E2E_BASE_URL=http://127.0.0.1:5000 E2E_OIDC_AUTHORITY=http://127.0.0.1:5000 E2E_OIDC_CLIENT_ID=console-app E2E_OIDC_SCOPE='openid profile email' npm run test:e2e: 17 passed

@rowan-stein

Copy link
Copy Markdown
Contributor

Requesting review — this PR adds end_session_endpoint to the OIDC metadata, includes e2e sign-out tests, and fixes the test helper to prevent cross-origin session re-injection. CI is green (build + e2e passing).

@noa-lucent noa-lucent left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core fix (end_session_endpoint in user-manager.ts) is correct and matches the issue specification exactly. The sign-out e2e tests cover both UI paths (user menu + settings page) and properly verify session cleanup. The sign-in-helper stabilization (origin guard + idempotency sentinel) is a sound improvement that prevents re-seeding after the sign-out redirect chain.

Left a few minor/nit comments:

  • Extract the duplicated waitForFunction session-check into oidc-helpers.ts
  • Remove deprecated noWaitAfter option
  • Make mock-server's JSON parse failure loud instead of silent
  • Remove redundant null check in UpdateUser handler

None are blocking. Nice clean PR.

Comment thread test/e2e/sign-out.spec.ts
const key = window.sessionStorage.key(i);
if (key && key.startsWith('oidc.user:')) return false;
}
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[minor] The waitForFunction that scans sessionStorage for oidc.user: keys is duplicated in both tests. Extract it into a helper in oidc-helpers.ts (e.g. waitForOidcSessionCleared(page, timeout)) alongside the existing readOidcSession. This keeps the session-assertion logic in one place and avoids drift if the storage key prefix ever changes.

Comment thread test/e2e/sign-out.spec.ts

await expect(page.getByTestId('user-menu-trigger')).toBeVisible({ timeout: 15000 });
await page.getByTestId('user-menu-trigger').click();
await page.getByTestId('user-menu-signout').click({ noWaitAfter: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] noWaitAfter has been deprecated since Playwright 1.50 and is a no-op in ^1.59.1 (this project's version). Remove it to avoid misleading readers into thinking it has an effect.

Same applies to line 28.

Comment thread test/e2e/mock-server.mjs
if (!raw) return {};
const contentType = req.headers['content-type'] ?? '';
if (contentType.includes('application/json')) {
try {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[minor] Silent error suppression — if JSON.parse fails, the function returns {} and the mock happily processes a request with an empty body. For a test server this masks malformed payloads from test code, making failures harder to diagnose. Propagate the parse error (or return a 400) instead of silently falling back.

try {
  return JSON.parse(raw);
} catch (err) {
  sendText(res, 400, `Invalid JSON body: ${err.message}`);
  // or just let it throw — the mock should be loud
}

(This requires passing res in or restructuring slightly, but the principle stands: test infrastructure should fail loudly.)

Comment thread test/e2e/mock-server.mjs
case 'GetMe': {
return sendJson(res, 200, { user: mapUser(defaultUser), clusterRole: defaultUser.clusterRole });
}
case 'ListUsers': {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] The second if (!user) guard (after users.has(identityId) already confirmed the key exists) is redundant. You can simplify:

case 'UpdateUser': {
  const user = users.get(body.identityId);
  if (!user) return sendText(res, 404, 'User not found');
  // ...update fields...
}

This removes the double-check without changing behavior.

@rowan-stein
rowan-stein merged commit 14b0b02 into main Apr 11, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix logout: add end_session_endpoint to OIDC metadata + e2e test

3 participants