Skip to content

feat: Tier 0 server auth - cookie lifecycle, CSRF, device tokens (backwards-compatible)#1007

Open
pyramation wants to merge 1 commit intomainfrom
devin/1776566631-tier0-auth-fresh
Open

feat: Tier 0 server auth - cookie lifecycle, CSRF, device tokens (backwards-compatible)#1007
pyramation wants to merge 1 commit intomainfrom
devin/1776566631-tier0-auth-fresh

Conversation

@pyramation
Copy link
Copy Markdown
Contributor

Summary

Adds cookie-based session management and CSRF protection to the GraphQL server, fully gated behind per-tenant app_auth_settings flags that default to OFF. When disabled, the server behaves identically to today.

Cookie lifecycle (cookie-plugin.ts): A grafserv processRequest plugin that intercepts GraphQL responses for auth mutations (signIn, signUp, signOut, etc.) and injects Set-Cookie headers to set/clear HttpOnly session cookies and device token cookies. Uses the official grafserv middleware hook — no monkey-patching of res.writeHead/res.end.

CSRF protection (csrf.ts): Double-submit-cookie pattern. Sets a non-HttpOnly CSRF token cookie; validates X-CSRF-Token header on POST requests from cookie-authenticated clients. Bearer-authenticated requests are exempt.

Feature flags added to AuthSettings / api.ts:

  • enable_cookie_auth — gates cookie lifecycle + CSRF
  • require_csrf_for_auth — allows tenants to opt out of CSRF if needed

tokenSource tracking: auth.ts now sets req.tokenSource to 'bearer' | 'cookie' | 'none' so downstream middleware (CSRF) knows whether the request is CSRF-vulnerable.

Review & Testing Checklist for Human

  • DB migration dependency: api.ts now SELECTs enable_cookie_auth and require_csrf_for_auth from app_auth_settings. Verify the corresponding columns exist (or will be added) in constructive-db before deploying — the server will fail at runtime if they're missing.
  • Set-Cookie header type cast (cookie-plugin.ts:276): result.headers is cast from Record<string, string> to Record<string, string | string[]> to support multiple Set-Cookie values. Verify grafserv's Node adapter passes headers straight to writeHead without intermediate validation that would reject arrays.
  • Response body extraction (extractAccessToken / extractDeviceId): Assumes { data: { operationName: { result?: { accessToken } } } } shape. Verify this matches PostGraphile's actual output for all 10 sign-in mutations listed in AUTH_MUTATIONS_SIGN_IN.
  • CSRF error responses: protect middleware returns status(200) with GraphQL-shaped errors inline, while errorHandler returns status(403). Confirm this is the intended pattern (the errorHandler appears to only catch errors thrown by other middleware, not from protect itself).
  • Backwards compatibility: Deploy with both flags OFF and verify zero behavioral change — no cookies set, no CSRF checks, bearer auth works as before.

Suggested test plan: Enable enable_cookie_auth on a test tenant, run a signIn mutation, and verify the Set-Cookie header appears in the response with the correct accessToken. Then verify signOut clears the cookie. For CSRF, verify that a cookie-authenticated POST without X-CSRF-Token returns the expected error, and that bearer-authenticated POSTs are unaffected.

Notes

  • Flush endpoint is intentionally excluded from this PR (separate PR).
  • No new dependencies added — CSRF uses node:crypto, cookie serialization is hand-rolled to avoid pulling in cookie-parser as a server dependency.
  • The parseCookieValue helper in csrf.ts uses split('=')[1] which would truncate values containing =. This is safe for hex CSRF tokens but would need revisiting if reused for other cookie types.

Link to Devin session: https://app.devin.ai/sessions/12acfda2a5434d2686c63515cfeb2610
Requested by: @pyramation

…kwards-compatible)

- Cookie lifecycle via grafserv processRequest plugin (no monkey-patching)
  - CookiePlugin intercepts auth mutation responses to set/clear HttpOnly session cookies
  - Extracts accessToken and deviceId from GraphQL response payloads
  - Gated by enable_cookie_auth flag (complete no-op when disabled)

- CSRF double-submit-cookie protection
  - Sets CSRF token cookie on every response (when cookie auth enabled)
  - Validates X-CSRF-Token header on POST for cookie-authenticated requests
  - Bearer-authenticated requests are exempt (not vulnerable to CSRF)
  - Gated by enable_cookie_auth + require_csrf_for_auth flags

- Feature flags in AuthSettings (all default to false)
  - enableCookieAuth: enables cookie-based session management
  - requireCsrfForAuth: enables CSRF validation for cookie-authenticated mutations

- tokenSource tracking on Express Request
  - auth.ts now sets req.tokenSource to 'bearer', 'cookie', or 'none'
  - CSRF middleware uses this to exempt bearer-authenticated requests

- All features gated behind app_auth_settings flags that default to OFF
  - Server works identically to today when features are disabled
  - Zero middleware overhead when disabled (early return on flag check)
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

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.

1 participant