increment 2 done #38
No reviewers
Labels
No labels
bug
dependencies
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
rust
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
navicore/anz!38
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "increment-2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
┌─────────────┬─────────────────────────────────┬──────────────────────────────────────────────────────────┐
│ Piece │ File │ Notes │
├─────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────────┤
│ JSON │ src/admin_api.rs (new │ RealmDto, UserDto, ClientDto, SigningKeyDto, SessionDto, │
│ contract │ top-level) │ ListResponse. Pure data — no axum, no rusqlite — so │
│ DTOs │ │ the future CLI imports it unchanged. │
├─────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────────┤
│ Auth module │ src/server/admin/auth.rs │ The extractor + authorize_admin + audit-on-denial, │
│ │ │ relocated from mod.rs; behavior unchanged. │
├─────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────────┤
│ List │ src/server/admin/lists.rs │ Five GET routes; each is a thin domain-model → DTO │
│ handlers │ │ mapper over existing db:: query functions. │
├─────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────────┤
│ Shared test │ src/server/admin/testing.rs │ mint, router_with, status, response, MASTER_ISSUER — │
│ helpers │ (#[cfg(test)]) │ reused across auth and lists test suites. │
├─────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────────┤
│ Lean module │ src/server/admin/mod.rs │ Module declarations + admin_router (now wiring 7 routes) │
│ root │ │ + health/whoami + AdminContext re-export. ~67 lines. │
└─────────────┴─────────────────────────────────┴──────────────────────────────────────────────────────────┘
Routes added:
Security posture verified by tests (every contract failure would be the kind of thing a reviewer would catch):
Same merge recommendation: ship this as its own PR. The CLI doesn't change in this increment — the DTOs are the
contract that Increment 4's CLI backend will consume — so review surface stays tight and the wire format gets
locked in main before write endpoints land in Increment 3.
┌─────────────┬─────────────────────────────────┬──────────────────────────────────────────────────────────┐ │ Piece │ File │ Notes │ ├─────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────────┤ │ JSON │ src/admin_api.rs (new │ RealmDto, UserDto, ClientDto, SigningKeyDto, SessionDto, │ │ contract │ top-level) │ ListResponse<T>. Pure data — no axum, no rusqlite — so │ │ DTOs │ │ the future CLI imports it unchanged. │ ├─────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────────┤ │ Auth module │ src/server/admin/auth.rs │ The extractor + authorize_admin + audit-on-denial, │ │ │ │ relocated from mod.rs; behavior unchanged. │ ├─────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────────┤ │ List │ src/server/admin/lists.rs │ Five GET routes; each is a thin domain-model → DTO │ │ handlers │ │ mapper over existing db:: query functions. │ ├─────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────────┤ │ Shared test │ src/server/admin/testing.rs │ mint, router_with, status, response, MASTER_ISSUER — │ │ helpers │ (#[cfg(test)]) │ reused across auth and lists test suites. │ ├─────────────┼─────────────────────────────────┼──────────────────────────────────────────────────────────┤ │ Lean module │ src/server/admin/mod.rs │ Module declarations + admin_router (now wiring 7 routes) │ │ root │ │ + health/whoami + AdminContext re-export. ~67 lines. │ └─────────────┴─────────────────────────────────┴──────────────────────────────────────────────────────────┘ Routes added: - GET /admin/realms - GET /admin/realms/{realm}/users - GET /admin/realms/{realm}/clients - GET /admin/realms/{realm}/keys - GET /admin/realms/{realm}/users/{username}/sessions Security posture verified by tests (every contract failure would be the kind of thing a reviewer would catch): - password_hash never appears in UserDto - client_secret_hash is replaced by has_secret: bool — the hash is never on the wire - signing-key responses carry only metadata (kid, algorithm, active, created_at); no PEM material - session responses are row-id + timestamps only; session tokens stay SHA-256-hashed in the DB - every list endpoint goes through AdminContext (asserted at the route level by list_realms_requires_admin) - unknown realm → 404; unknown user → 404 Same merge recommendation: ship this as its own PR. The CLI doesn't change in this increment — the DTOs are the contract that Increment 4's CLI backend will consume — so review surface stays tight and the wire format gets locked in main before write endpoints land in Increment 3.Review: Increment 2 — shared DTOs + read-only admin endpoints
Verdict: solid, ready to merge. Clean refactor + five read-only endpoints, with the wire contract locked in
src/admin_api.rsbefore write endpoints land. Verified locally: clippy clean with-D warnings, 134 tests pass. Both notes from the Increment 1 review are addressed (see below).Increment 1 follow-ups — both resolved
authorize_adminnow emitsadmin_access_deniedon every 401/403 with adetailreason (no_bearer/invalid_token/foreign_realm/disabled/no_admin_group/unknown_subject) and the caller IP (viaConnectInfo, gracefullyNoneunderoneshot). Locked bydenials_are_audited_with_reason.tracing::error!, instead of a 403 — no longer conflating server misconfiguration with an authorization denial, and audit logs aren't polluted with bogus "denials".Verified against the code
password_hashis absent fromUserDto;client_secret_hash→has_secret: bool;SigningKeyDtois metadata-only (no PEM);SessionDtois row-id + timestamps (session tokens stay SHA-256-hashed in storage). Each is asserted by a dedicated test that checks the field is absent from the JSON, not merely empty.realm/user/clientmap via.to_rfc3339();signing_key/sessionpass their column through directly, and those columns are themselves written with.to_rfc3339()at insert — so the contract holds across both paths.list_sessions"active sessions" wording is accurate — the underlying query filtersexpires_at > now, so expired rows don't leak into the response.AdminContext, so auth runs before the body.ListResponse<T>envelope is a good forward-compat choice (pagination/totals later without a breaking change).Worth noting (none blocking — forward-looking)
usersor a busy user'ssessionsreturns everything in one response. TheListResponse<T>envelope exists precisely so pagination can be added without breaking the contract; just flagging that the cap isn't there yet.list_realms_requires_adminasserts the 401 path on one endpoint. Since all five share the sameAdminContextextractor that's reasonable, but a one-line unauth assertion per route would make the guarantee explicit and catch a future copy-paste that forgets the extractor.