# Authentication Behaviors ## Overview Authentication in Integreat uses Google OAuth 2.0 as the primary identity provider. Users authenticate via Google, receive a JWT token, and establish a server-side session. The system supports role-based access control (`admin`, `user`, `read-only`), client-scoped permissions, session versioning for SSR rollout, and admin impersonation via signed JWT tokens. **Testing Philosophy** - Prefer unit tests for pure business logic (JWT generation, compression, validation) - Use integration tests for database interactions and cross-system flows (OAuth callback, session management) - Use UI tests only for end-to-end happy paths that touch multiple pages - Every behavior must be user-visible; no tests for implementation details --- ## Testing Patterns ### Pattern: OAuth Login Flow The standard authentication flow follows these steps: 1. Unauthenticated user navigates to a protected page 2. User is redirected to `/login` 3. User clicks "Sign in with Google" 4. Browser redirects to Google OAuth consent screen 5. User consents and Google redirects to `/api/oauth?code=&state=` 6. Server exchanges code for token, fetches profile, finds/creates user 7. Server redirects to original page (or `/`) with `?jwt=` 8. Client reads JWT and establishes session **Test implications:** Integration test the OAuth callback handler. UI test only the happy path once. ### Pattern: Middleware Stack Every protected route passes through authentication middleware: 1. `wrap-session-version` — validates session version, redirects to login if outdated 2. `wrap-secure` — checks for authenticated session, redirects to login if missing 3. `wrap-admin` — checks for admin role, redirects to login if not admin 4. `wrap-client-redirect-unauthenticated` — converts 401 responses to HTMX redirects **Test implications:** Integration test each middleware independently. UI tests only verify redirect behavior. ### Pattern: JWT Claims The JWT token contains user identity and permissions: 1. `:user`, `:exp`, `:db/id`, `:user/role`, `:user/name` for all users 2. `:gz-clients` (compressed) for admin and read-only users 3. `:user/clients` (plain) for regular users **Test implications:** Unit test JWT generation for each role type. --- ## Login ### Display Behaviors | # | Behavior | Test Strategy | Status | |---|----------|---------------|--------| | 1.1 | It should display a "Sign in with Google" button on the login page | UI | [ ] | ### OAuth Behaviors | # | Behavior | Test Strategy | Status | |---|----------|---------------|--------| | 1.2 | It should redirect to Google OAuth when the user clicks "Sign in with Google" | UI | [ ] | | 1.3 | It should exchange the authorization code for an access token on callback | Integration | [ ] | | 1.4 | It should fetch the user's Google profile using the access token | Integration | [ ] | | 1.5 | It should create a new user account when the user logs in for the first time | Integration | [ ] | | 1.6 | It should find the existing user account on subsequent logins | Integration | [ ] | | 1.7 | It should redirect to the original page after successful OAuth | Integration | [ ] | | 1.8 | It should redirect to the root page when no return URL is provided | Integration | [ ] | | 1.9 | It should establish a server-side session with user identity and version | Integration | [ ] | | 1.10 | It should pass the JWT token in the query string after successful OAuth | Integration | [ ] | | 1.11 | It should display the user's clients and data after successful login | UI | [ ] | | 1.12 | It should handle users without email via Google provider ID | Integration | [ ] | | 1.13 | It should return 401 with error message when the OAuth code is missing | Integration | [ ] | | 1.14 | It should return 401 when the OAuth code is invalid or expired | Integration | [ ] | | 1.15 | It should return 401 and log a warning when the Google network request fails | Integration | [ ] | --- ## Logout | # | Behavior | Test Strategy | Status | |---|----------|---------------|--------| | 2.1 | It should clear the session when the user navigates to `/logout` | Integration | [ ] | | 2.2 | It should redirect to the login page after logout | Integration | [ ] | | 2.3 | It should remain idempotent when logging out without an active session | Integration | [ ] | --- ## Impersonation | # | Behavior | Test Strategy | Status | |---|----------|---------------|--------| | 3.1 | It should allow admin users to assume another user's identity via signed JWT | Integration | [ ] | | 3.2 | It should validate the impersonation JWT signature with `:jwt-secret` and `:hs512` | Integration | [ ] | | 3.3 | It should reject expired impersonation JWTs | Integration | [ ] | | 3.4 | It should block non-admin users from accessing `/impersonate` | Integration | [ ] | | 3.5 | It should block unauthenticated users from accessing `/impersonate` | Integration | [ ] | | 3.6 | It should replace the admin's session with the impersonated user's session | Integration | [ ] | --- ## Session Management ### Authentication Gate Behaviors | # | Behavior | Test Strategy | Status | |---|----------|---------------|--------| | 4.1 | It should allow authenticated requests to proceed to protected routes | Integration | [ ] | | 4.2 | It should redirect unauthenticated users to `/login` with a `redirect-to` parameter | Integration | [ ] | | 4.3 | It should return `hx-redirect: /login` for unauthenticated HTMX requests | Integration | [ ] | ### Admin Gate Behaviors | # | Behavior | Test Strategy | Status | |---|----------|---------------|--------| | 5.1 | It should allow admin requests to proceed to admin-only routes | Integration | [ ] | | 5.2 | It should redirect non-admin users to `/login` when accessing admin routes | Integration | [ ] | ### Session Version Behaviors | # | Behavior | Test Strategy | Status | |---|----------|---------------|--------| | 6.1 | It should invalidate sessions with outdated version numbers | Integration | [ ] | | 6.2 | It should redirect to `/login` when an outdated session accesses normal routes | Integration | [ ] | | 6.3 | It should return `hx-redirect: /login` for outdated sessions on HTMX routes | Integration | [ ] | | 6.4 | It should return 401 for outdated sessions on GraphQL routes | Integration | [ ] | | 6.5 | It should treat sessions without a version as outdated | Integration | [ ] | --- ## Cross-Cutting Behaviors ### JWT Behaviors | # | Behavior | Test Strategy | Status | |---|----------|---------------|--------| | 7.1 | It should generate a JWT containing the user's role and client access on login | Unit | [ ] | | 7.2 | It should compress the client list for admin users to fit in the JWT | Unit | [ ] | | 7.3 | It should compress the client list for read-only users to fit in the JWT | Unit | [ ] | | 7.4 | It should include a plain client list for regular users in the JWT | Unit | [ ] | | 7.5 | It should create API tokens with admin role and 1000-day expiration | Unit | [ ] | ### Middleware Behaviors | # | Behavior | Test Strategy | Status | |---|----------|---------------|--------| | 8.1 | It should convert 401 responses to HTMX redirects for unauthenticated users | Integration | [ ] | ### Role-Based Behaviors | # | Behavior | Test Strategy | Status | |---|----------|---------------|--------| | 9.1 | It should allow admin users to access all clients | Integration | [ ] | | 9.2 | It should allow regular users to access only their assigned clients | Integration | [ ] | | 9.3 | It should allow read-only users to access all clients with view-only permissions | Integration | [ ] | | 9.4 | It should handle admin users with no clients by providing an empty compressed list | Unit | [ ] | | 9.5 | It should handle regular users with no clients by providing an empty client vector | Unit | [ ] | ### Security Behaviors | # | Behavior | Test Strategy | Status | |---|----------|---------------|--------| | 10.1 | It should reject tampered JWTs during impersonation | Integration | [ ] | | 10.2 | It should treat sessions with nil identity as unauthenticated | Integration | [ ] | --- ## Test Data Requirements | Entity | Requirements | |--------|-------------| | **Users** | Admin user with multiple clients; regular user with subset of clients; read-only user with multiple clients; new user (not in database) with Google provider details; existing user with Google provider details | | **Clients** | Multiple clients with `:client/code`, `:client/name`, `:client/locations`; client associations on users | | **OAuth Mock** | Mock Google token endpoint responses (success and failure); mock Google userinfo endpoint responses | ## Existing Tests to Preserve - None identified ## Dependencies - Google OAuth 2.0 (authorization code exchange and userinfo retrieval) - Buddy JWT (token signing/unsigning with HS512) - Datomic (user lookup and creation) - Legacy SPA (login and needs-activation pages, no SSR tests)