44
Total
40
Met
4
Partial
0
Missing
0
N/A
2
Deviations
π Delta from Previous Review (84a563f β 06088df)
Resolved This Commit
β
AC-038 MISSINGβMET β main.rs fully implemented (was placeholder)
β
AC-039 MISSINGβMET β All 13 routes registered in main.rs
πΌ AC-037 MISSINGβPARTIAL β /authorize handler implemented (integration test pending)
π’ Deviation closed β HIGH: main.rs placeholder
π’ Deviation closed β HIGH: No /authorize endpoint
π’ Deviation closed β LOW: JWKS at /jwks (now /.well-known/jwks.json)
New Criteria (OID-010a)
π‘ AC-040 PARTIAL β POST /v1/auth/login
π’ AC-041 MET β GET /.well-known/openid-configuration
π’ AC-042 MET β JWT Bearer auth extractor
π’ AC-043 MET β login.succeeded CloudEvent
π’ AC-044 MET β login.failed CloudEvent
β Deviations Resolved This Commit
β
HIGH β main.rs was a placeholder
println!(). Now: production Actix-web server with env config, PostgreSQL pool, RSA key pair, all repos, event producer, health endpoint.β
HIGH β No GET /authorize endpoint existed. Now: handler at /v1/oauth/authorize with Bearer JWT auth, OAuth2-compliant 302 redirects and error redirect.
β
LOW β JWKS served at /jwks (non-standard). Now: exclusively at
/.well-known/jwks.json. OIDC Discovery jwks_uri is consistent.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, login.failed β β token.issued uses Uuid::nil() β |
| 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 | tests/oauth_test.rs β token_endpoint_authorization_code_full_flow |
| 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 | new token != old, replay 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 | src/api/oauth.rs β emitted β β tenantid=Uuid::nil() β |
| 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) | src/api/oidc.rs β jwks() at /.well-known/jwks.json (path fixed this commit) β 3 unit tests |
| AC-036 | MET | JWT claims: sub, iss, aud, exp, iat, tenant_id, roles, permissions | src/domain/token.rs β Claims struct, verified in oauth_test full-flow test |
| AC-037 β | PARTIAL | GET /v1/oauth/authorize β OAuth2 authorization endpoint (user-facing) | src/api/oauth.rs β authorize() handler, 302 redirect with code+state, error redirects, Bearer JWT auth. Query deserialization tests. MISSING: integration test (step 10g) |
| 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. Dockerfile (multi-stage, non-root, HEALTHCHECK). |
| 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 NEW | PARTIAL | POST /v1/auth/login β authenticate (email + password + tenant_id) β JWT | src/api/auth.rs + src/service/auth.rs β is_active check, argon2 verify, roles/permissions fetch, JWT issue. Error: 401 for NotFound/Unauthorized (no email enumeration), 400 for Validation. 3 handler + 5 service unit tests. MISSING: integration test (step 10g) |
| AC-041 NEW | MET | GET /.well-known/openid-configuration β OIDC Discovery (RFC 8414) | src/api/oidc.rs β openid_configuration(), all RFC 8414 required fields including issuer, authorization_endpoint, token_endpoint, jwks_uri, grant_types, S256 challenge methods, claims. 3 unit tests. |
| AC-042 NEW | MET | JWT Bearer token auth extractor (AuthenticatedUser) for route protection | src/api/extractors.rs β FromRequest impl. 5 unit tests: valid JWT (user_id, tenant_id, email, roles verified), missing header (401), non-bearer (401), invalid token (401), expired token (401). |
| AC-043 NEW | MET | login.succeeded CloudEvent with correct tenant_id | src/events/producer.rs β login_succeeded(tenant_id, user_id, email). tenantid populated from arg (not Uuid::nil()). Data: {tenant_id, user_id, email}. 2 unit tests. |
| AC-044 NEW | MET | login.failed CloudEvent with correct tenant_id | src/events/producer.rs β login_failed(tenant_id, email, reason). Emitted on wrong password, unknown user, inactive user. Data: {tenant_id, email, reason}. 2 unit tests. |
Remaining Deviations (2 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. Unchanged from previous review.Ref: ODS Global Rules β Multi-Tenancy: "Include tenant_id in all Redpanda events" Β· Affects AC-022, AC-033
MEDIUM β Integration tests for login + authorize flows pending
OID-010 step 10g (integration tests against real PostgreSQL via testcontainers β login flow, authorize flow, full end-to-end loginβauthorizeβtoken, OIDC discovery ~12 tests) is planned but not yet executed. The
/v1/oauth/authorize handler has only query-deserialization unit tests. The login endpoint has in-memory mock unit tests only. The complete authorization code flow (login β authorize β token exchange) lacks an end-to-end integration test.Ref: OID-010 step 10g Β· Affects AC-037, AC-040