ODS OID β€” Business Analysis Review

commit: 06088df  |  2026-03-18  |  APP  |  OID-010a
βœ“ COMPLIANT
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-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, login.failed βœ“ β€” token.issued uses Uuid::nil() βœ—
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 S256tests/oauth_test.rs β€” token_endpoint_authorization_code_full_flow
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 rotationnew token != old, replay 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 issuancesrc/api/oauth.rs β€” emitted βœ“ β€” tenantid=Uuid::nil() βœ—
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)src/api/oidc.rs β€” jwks() at /.well-known/jwks.json (path fixed this commit) β€” 3 unit tests
AC-036METJWT claims: sub, iss, aud, exp, iat, tenant_id, roles, permissionssrc/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