ODS OID — Business Analysis Review

commit: 9f512d5  |  2026-03-19  |  APP  |  OID-010b/c
✓ COMPLIANT
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-001METTenant domain model — slug validation, plans, status transitionssrc/domain/tenant.rs — 15 unit tests
AC-002METUser domain model — argon2 hashing, email/password validationsrc/domain/user.rs — 18 unit tests
AC-003METRole domain model — permissions in resource:action formatsrc/domain/role.rs — 14 unit tests
AC-004METOAuth Client domain model — scopes, redirect_uris, argon2 secretsrc/domain/client.rs — 7 unit tests
AC-005METAuthorization Code — PKCE S256 mandatory (plain rejected)src/domain/auth_code.rs — S256 only, 10-min TTL
AC-006METRefresh Token — 30-day expiry, revocation, SHA-256 hashsrc/domain/refresh_token.rs
AC-007METJWT/Claims — RS256 signing, JWKS export (RFC 7517), full claimssrc/domain/token.rs — KeyPair, TokenIssuer, TokenValidator — 10 unit tests
AC-008METPOST /v1/tenants (201, 400, 409)src/api/tenants.rs + tests/tenant_test.rs
AC-009METGET /v1/tenants/{id} (200, 404)src/api/tenants.rs + tests/tenant_test.rs
AC-010METTenant PostgreSQL repository + RLSmigrations/001+002, rls_tenant_isolation_enforced
AC-011METPOST /v1/users (201, 400 email/password, 409)src/api/users.rs + tests/user_role_test.rs — 4 tests
AC-012METGET /v1/users/{id} (200 with roles, 404)src/api/users.rs + tests/user_role_test.rs
AC-013METPATCH /v1/users/{id} (200, 400, 404)src/api/users.rs + tests/user_role_test.rs — 4 tests
AC-014METPUT /v1/users/{id}/roles (200 replaces all, 404)src/api/users.rs — assign_user_roles()
AC-015METPOST /v1/roles (201, 400, 409)src/api/roles.rs + tests/user_role_test.rs
AC-016METGET /v1/roles?tenant_id= (200)src/api/roles.rs + tests/user_role_test.rs
AC-017METUser + Role PostgreSQL repositories + RLSmigrations/003–005, rls_user_tenant_isolation
AC-018METCloudEvents v1.0 format compliance (all required fields)src/events/producer.rs — specversion, id, source, type, subject, time, datacontenttype
AC-019METtenant.created CloudEvent on tenant creationsrc/api/tenants.rs fire-and-forget + events_test.rs
AC-020METuser.created CloudEvent on user creationsrc/api/users.rs fire-and-forget + events_test.rs
AC-021METRedpanda event delivery (rdkafka, testcontainers integration test)redpanda_producer_sends_event — rdkafka 0.36
AC-022PARTIALAll CloudEvents include tenant_idtenant.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-023METPOST /v1/clients (201 + secret, 400)src/api/clients.rs + tests/client_test.rs — 4 tests
AC-024METGET /v1/clients (200, no secret)ClientResponse struct excludes secret field
AC-025METclient_secret shown only at creation, never againPOST → ClientCreatedResponse, GET → ClientResponse (no secret)
AC-026METclient.created CloudEvent on client creationsrc/api/clients.rs fire-and-forget + event_emitted_on_client_create
AC-027METClient PostgreSQL repository + RLSmigrations/006+007, rls_client_tenant_isolation
AC-028METPOST /v1/oauth/token — authorization_code + PKCE S256oauth_test.rs + E2E: auth_flow_test.rs e2e_login_authorize_token_exchange (token exchange step verified)
AC-029METPOST /v1/oauth/token — client_credentials + scope downscoping4 integration tests: success, wrong_secret, scope_downscoping, unsupported_grant
AC-030METPOST /v1/oauth/token — refresh_token with rotationE2E verified: auth_flow_test.rs e2e_login_authorize_token_then_refresh — new != old, old rejected 401
AC-031METAuth code repo — atomic consume preventing replayUPDATE...RETURNING WHERE used=false AND expires_at > now()
AC-032METRefresh token repo — revocation + double-revoke protectionrevoke, revoke_twice_fails, find_nonexistent_fails
AC-033PARTIALtoken.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-034METRLS on auth_codes + refresh_tokens tablesmigrations/009+011, auth_code_rls + refresh_token_rls isolation tests
AC-035METGET /.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-036METJWT claims: sub, iss, aud, exp, iat, tenant_id, roles, permissionsauth_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-038METService binary with Actix-web HTTP server startupsrc/main.rs — HttpServer, env config, PostgreSQL pool, RSA key pair, all repos, EventProducer, /health endpoint.
AC-039METRoute registration for all API endpointssrc/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-041METGET /.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-042METJWT Bearer token auth extractor (AuthenticatedUser) for route protectionsrc/api/extractors.rs — FromRequest impl. End-to-end: all authorize integration tests exercise extractor — missing header→401, invalid token→401, valid token→authorized.
AC-043METlogin.succeeded CloudEvent with correct tenant_idauth_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-044METlogin.failed CloudEvent with correct tenant_idauth_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