44
Total
42
Met
2
Partial
0
Missing
0
N/A
1
Deviations
📊 Delta from Previous Review (06088df → 9f512d5)
Criteria Upgraded This Review
✅ AC-037 PARTIAL→MET — /authorize: full integration tests added (302 redirect, 401, 400, error redirect). tests/auth_test.rs + tests/auth_flow_test.rs
✅ AC-040 PARTIAL→MET — /auth/login: full integration tests added (200+JWT, 401×4, 400, events). tests/auth_test.rs + tests/auth_flow_test.rs
🟢 Deviation resolved — MEDIUM: integration tests for login + authorize pending (10g complete)
New Test Coverage Added
🧪 tests/auth_test.rs — 14 integration tests: login (6), authorize (4), E2E login→authorize→token (1), OIDC discovery+JWKS (3)
🧪 tests/auth_flow_test.rs — 11 extended tests: OIDC (2), login (5), authorize (4), E2E full+refresh (2), JWKS cross-validate (1)
⚠️ AC-022/AC-033 unchanged — token.issued Uuid::nil() not fixed in production code
✅ Deviations Resolved This Review
✅ MEDIUM — Integration tests for POST /v1/auth/login and GET /v1/oauth/authorize against real PostgreSQL (testcontainers) are now complete. OID-010 step 10g executed across two test files (25 total integration tests). Full E2E login→authorize→token exchange verified. Deviation closed.
Acceptance Criteria (44 total)
| ID | Status | Criterion | Evidence |
|---|---|---|---|
| AC-001 | MET | Tenant domain model — slug validation, plans, status transitions | src/domain/tenant.rs — 15 unit tests |
| AC-002 | MET | User domain model — argon2 hashing, email/password validation | src/domain/user.rs — 18 unit tests |
| AC-003 | MET | Role domain model — permissions in resource:action format | src/domain/role.rs — 14 unit tests |
| AC-004 | MET | OAuth Client domain model — scopes, redirect_uris, argon2 secret | src/domain/client.rs — 7 unit tests |
| AC-005 | MET | Authorization Code — PKCE S256 mandatory (plain rejected) | src/domain/auth_code.rs — S256 only, 10-min TTL |
| AC-006 | MET | Refresh Token — 30-day expiry, revocation, SHA-256 hash | src/domain/refresh_token.rs |
| AC-007 | MET | JWT/Claims — RS256 signing, JWKS export (RFC 7517), full claims | src/domain/token.rs — KeyPair, TokenIssuer, TokenValidator — 10 unit tests |
| AC-008 | MET | POST /v1/tenants (201, 400, 409) | src/api/tenants.rs + tests/tenant_test.rs |
| AC-009 | MET | GET /v1/tenants/{id} (200, 404) | src/api/tenants.rs + tests/tenant_test.rs |
| AC-010 | MET | Tenant PostgreSQL repository + RLS | migrations/001+002, rls_tenant_isolation_enforced |
| AC-011 | MET | POST /v1/users (201, 400 email/password, 409) | src/api/users.rs + tests/user_role_test.rs — 4 tests |
| AC-012 | MET | GET /v1/users/{id} (200 with roles, 404) | src/api/users.rs + tests/user_role_test.rs |
| AC-013 | MET | PATCH /v1/users/{id} (200, 400, 404) | src/api/users.rs + tests/user_role_test.rs — 4 tests |
| AC-014 | MET | PUT /v1/users/{id}/roles (200 replaces all, 404) | src/api/users.rs — assign_user_roles() |
| AC-015 | MET | POST /v1/roles (201, 400, 409) | src/api/roles.rs + tests/user_role_test.rs |
| AC-016 | MET | GET /v1/roles?tenant_id= (200) | src/api/roles.rs + tests/user_role_test.rs |
| AC-017 | MET | User + Role PostgreSQL repositories + RLS | migrations/003–005, rls_user_tenant_isolation |
| AC-018 | MET | CloudEvents v1.0 format compliance (all required fields) | src/events/producer.rs — specversion, id, source, type, subject, time, datacontenttype |
| AC-019 | MET | tenant.created CloudEvent on tenant creation | src/api/tenants.rs fire-and-forget + events_test.rs |
| AC-020 | MET | user.created CloudEvent on user creation | src/api/users.rs fire-and-forget + events_test.rs |
| AC-021 | MET | Redpanda event delivery (rdkafka, testcontainers integration test) | redpanda_producer_sends_event — rdkafka 0.36 |
| AC-022 | PARTIAL | All CloudEvents include tenant_id | tenant.created, user.created, client.created, login.succeeded (verified via integration test: events[0].tenantid==tenant_id), login.failed ✓ — token.issued uses Uuid::nil() ✗ — no production code change in 5f7f890..9f512d5 |
| AC-023 | MET | POST /v1/clients (201 + secret, 400) | src/api/clients.rs + tests/client_test.rs — 4 tests |
| AC-024 | MET | GET /v1/clients (200, no secret) | ClientResponse struct excludes secret field |
| AC-025 | MET | client_secret shown only at creation, never again | POST → ClientCreatedResponse, GET → ClientResponse (no secret) |
| AC-026 | MET | client.created CloudEvent on client creation | src/api/clients.rs fire-and-forget + event_emitted_on_client_create |
| AC-027 | MET | Client PostgreSQL repository + RLS | migrations/006+007, rls_client_tenant_isolation |
| AC-028 | MET | POST /v1/oauth/token — authorization_code + PKCE S256 | oauth_test.rs + E2E: auth_flow_test.rs e2e_login_authorize_token_exchange (token exchange step verified) |
| AC-029 | MET | POST /v1/oauth/token — client_credentials + scope downscoping | 4 integration tests: success, wrong_secret, scope_downscoping, unsupported_grant |
| AC-030 | MET | POST /v1/oauth/token — refresh_token with rotation | E2E verified: auth_flow_test.rs e2e_login_authorize_token_then_refresh — new != old, old rejected 401 |
| AC-031 | MET | Auth code repo — atomic consume preventing replay | UPDATE...RETURNING WHERE used=false AND expires_at > now() |
| AC-032 | MET | Refresh token repo — revocation + double-revoke protection | revoke, revoke_twice_fails, find_nonexistent_fails |
| AC-033 | PARTIAL | token.issued CloudEvent on every successful token issuance (with tenant_id) | Emitted ✓ (E2E tests assert events[1].event_type=='token.issued'). tenantid=Uuid::nil() ✗ — neither E2E test asserts tenantid on token.issued event. Production code unchanged. |
| AC-034 | MET | RLS on auth_codes + refresh_tokens tables | migrations/009+011, auth_code_rls + refresh_token_rls isolation tests |
| AC-035 | MET | GET /.well-known/jwks.json — RFC 7517 JWKS (RS256) | auth_test.rs: jwks_endpoint_serves_valid_public_key (kty=RSA, alg=RS256, kid), jwks_public_key_validates_issued_jwt. auth_flow_test.rs: jwks_endpoint_returns_valid_key (e='AQAB'), e2e_jwks_can_validate_login_token (full JWKS→JWT validation) |
| AC-036 | MET | JWT claims: sub, iss, aud, exp, iat, tenant_id, roles, permissions | auth_flow_test.rs login_success_returns_jwt (roles=['admin'], permissions verified) + e2e_login_authorize_token_exchange (final_claims.sub, tenant_id, roles, permissions all asserted) |
| AC-037 ↑ MET | MET | GET /v1/oauth/authorize — OAuth2 authorization endpoint (user-facing) | Integration tests (real PostgreSQL testcontainers): auth_test.rs — authorize_success_returns_302_redirect_with_code, authorize_missing_jwt_returns_401, authorize_invalid_client_returns_400 (error='invalid_client'), authorize_invalid_response_type_returns_302_error_redirect. auth_flow_test.rs — authorize_success_redirects_with_code, authorize_without_jwt_returns_401, authorize_unknown_client_returns_400, authorize_invalid_redirect_uri_returns_error_redirect. UPGRADED from PARTIAL (06088df had only query-deserialization unit tests). |
| AC-038 | MET | Service binary with Actix-web HTTP server startup | src/main.rs — HttpServer, env config, PostgreSQL pool, RSA key pair, all repos, EventProducer, /health endpoint. |
| AC-039 | MET | Route registration for all API endpoints | src/main.rs — 13 routes: /health, /v1/tenants, /v1/users, /v1/roles, /v1/clients, /v1/auth/login, /v1/oauth/authorize, /v1/oauth/token, /.well-known/openid-configuration, /.well-known/jwks.json |
| AC-040 ↑ MET | MET | POST /v1/auth/login — authenticate (email + password + tenant_id) → JWT | Integration tests (real PostgreSQL testcontainers): auth_test.rs — login_success_returns_200_with_jwt (status, token_type, user_id, tenant_id, JWT claims validated), login_wrong_password_returns_401 (error='invalid_credentials'), login_nonexistent_user_returns_401, login_inactive_user_returns_401, login_success_emits_login_succeeded_event (tenantid verified), login_failure_emits_login_failed_event. auth_flow_test.rs — adds wrong_tenant_returns_401, missing_fields_returns_400, roles+permissions verified. UPGRADED from PARTIAL (06088df had only in-memory mock unit tests). |
| AC-041 | MET | GET /.well-known/openid-configuration — OIDC Discovery (RFC 8414) | auth_test.rs: oidc_discovery_returns_valid_metadata (issuer, jwks_uri, authorization_endpoint, token_endpoint, code_challenge_methods_supported=['S256']). auth_flow_test.rs: additionally verifies login_endpoint, grant_types_supported (authorization_code, client_credentials, refresh_token). |
| AC-042 | MET | JWT Bearer token auth extractor (AuthenticatedUser) for route protection | src/api/extractors.rs — FromRequest impl. End-to-end: all authorize integration tests exercise extractor — missing header→401, invalid token→401, valid token→authorized. |
| AC-043 | MET | login.succeeded CloudEvent with correct tenant_id | auth_test.rs login_success_emits_login_succeeded_event (line 383-385): event_type='login.succeeded', tenantid==tenant_id, data.email==user.email. auth_flow_test.rs login_success_returns_jwt (line 359-361): same assertions. Tenant_id correctly populated. |
| AC-044 | MET | login.failed CloudEvent with correct tenant_id | auth_test.rs login_failure_emits_login_failed_event (line 415-419): event_type='login.failed', tenantid==tenant_id, data.email==user.email. auth_flow_test.rs login_wrong_password_returns_401 (line 389-391): event_type verified. Tenant_id correctly populated. |
Remaining Deviations (1 MEDIUM)
MEDIUM — token.issued CloudEvent uses Uuid::nil() as tenantid
CloudEvent::token_issued() in src/events/producer.rs hardcodes Uuid::nil() (00000000-0000-0000-0000-000000000000) as tenantid. All three grant types have the real tenant_id available during issuance: authorization_code and refresh_token via user lookup, client_credentials via ClientStore. The E2E tests in this commit verify token.issued is emitted (events[1].event_type == "token.issued") but do not assert the tenantid field — leaving the nil UUID undetected at test level. No production code changes in commits 5f7f890..9f512d5. Unchanged from review at 06088df.Ref: ODS Global Rules — Multi-Tenancy: "Include tenant_id in all Redpanda events" · Affects AC-022, AC-033