mirror of
https://github.com/azaion/admin.git
synced 2026-06-21 21:11:08 +00:00
8e7c602f51
AZ-535: POST /logout (caller's session), /logout/all (all sessions for user),
admin POST /sessions/{sid}/revoke, and verifier-only GET /sessions/revoked
snapshot. New Service role gates the snapshot. Idempotent revoke; reason +
revoked_by_user_id audited per row.
AZ-533: POST /sessions/mission mints a long-lived no-refresh ES256 token bound
to one aircraft + one mission. Audience narrowed to satellite-provider, hard
12 h cap, persisted as class='mission' so the existing logout/revoke surface
covers it. Successful CompanionPC /login or /token/refresh auto-revokes that
aircraft's open mission session (post-flight reconnect).
Schema: 09_sessions_logout_and_mission.sql adds revoked_by_user_id, class,
aircraft_id; drops NOT NULL on refresh_hash for mission rows; adds two partial
indexes for the auto-revoke and snapshot hot paths.
Tests: 13 new e2e tests, all green; full suite 75/76 (1 pre-existing flake in
PasswordHashingTests AC5 timing assertion, unrelated to this batch).
Co-authored-by: Cursor <cursoragent@cursor.com>
7.3 KiB
7.3 KiB
Code Review Report
Batch: 3 (cycle 2) — AZ-535 (logout_revocation), AZ-533 (mission_token_uav) Date: 2026-05-14 Verdict: PASS_WITH_WARNINGS
Phases Covered
- Phase 1: Context loading (read AZ-535 + AZ-533 specs)
- Phase 2: Spec compliance (8/8 ACs covered, see below)
- Phase 3: Code quality (SOLID, naming, error handling, complexity)
- Phase 4: Security quick-scan (revocation surface, mission audience pinning, role separation)
- Phase 5: Performance scan (snapshot endpoint bound, partial indexes)
- Phase 6: Cross-task consistency (sessions table reused; revocation reasons pluralized cleanly)
- Phase 7: Architecture compliance (no new cross-component imports; ProjectReference layering respected)
AC Coverage
| Task | AC | Test | Status |
|---|---|---|---|
| AZ-535 | 1 | LogoutRevocationTests.AC1_Logout_revokes_caller_session_and_blocks_refresh |
Covered |
| AZ-535 | 1 | LogoutRevocationTests.AC1_Logout_is_idempotent |
Covered |
| AZ-535 | 2 | LogoutRevocationTests.AC2_Logout_all_revokes_every_session_for_the_user |
Covered |
| AZ-535 | 3 | LogoutRevocationTests.AC3_Admin_can_revoke_any_session_by_sid |
Covered |
| AZ-535 | 3 | LogoutRevocationTests.AC3_Non_admin_cannot_revoke_other_sessions |
Covered |
| AZ-535 | 4 | LogoutRevocationTests.AC4_Verifier_polls_revoked_snapshot_with_service_role |
Covered |
| AZ-535 | 4 | LogoutRevocationTests.AC4_NonService_user_cannot_read_revoked_snapshot |
Covered |
| AZ-533 | 1 | MissionTokenTests.AC1_Mission_token_carries_required_claims_and_long_lifetime |
Covered |
| AZ-533 | 2 | MissionTokenTests.AC2_Mission_id_must_match_pattern |
Covered |
| AZ-533 | 2 | MissionTokenTests.AC2_Planned_duration_must_be_within_bounds(0.05) + (13) |
Covered |
| AZ-533 | 3 | MissionTokenTests.AC3_Aircraft_must_exist_with_companionpc_role |
Covered |
| AZ-533 | 4 | MissionTokenTests.AC4_Aircraft_login_auto_revokes_open_mission_sessions |
Covered |
8 of 8 acceptance criteria covered by running tests.
Findings
| # | Severity | Category | File | Title |
|---|---|---|---|---|
| 1 | Medium | Spec-Gap | Azaion.AdminApi/Program.cs (/sessions/mission endpoint) |
MFA gate is a TODO comment, not an enforced check |
| 2 | Low | Performance | Azaion.Services/SessionService.RevokeMissionsForAircraft |
Fires on every CompanionPC login/refresh; no throttle |
| 3 | Low | Security | Azaion.AdminApi/Program.cs (/sessions/revoked) |
since floor (12 h) is silent — clients can't tell they were clamped |
| 4 | Low | Maintainability | e2e/Azaion.E2E/Tests/MissionTokenTests.GetUserId |
Re-logs in to read nameid — adds 250 ms per use |
Finding Details
F1: MFA gate is a TODO (Medium / Spec-Gap)
- Location:
Azaion.AdminApi/Program.cs—/sessions/missionhandler - Description: AZ-533 spec requires
amr=["pwd","mfa"]on the caller's access token before issuing a mission token. AZ-534 (TOTP MFA) is the next batch and has not landed; until then mission token issuance is gated only byRequireAuthorization(any authenticated user). The TODO comment inProgram.csmakes the dependency explicit so AZ-534's PR will surface this site. - Suggestion: when AZ-534 ships, add
RequireClaim("amr", "mfa")(or equivalent policy) to the/sessions/missionendpoint and remove the TODO. Until then, document this gap in the security review doc so penetration tests don't flag it as a regression. - Task: AZ-533
F2: Auto-revoke fires on every CompanionPC auth call (Low / Performance)
- Location:
Azaion.AdminApi/Program.cs—/loginand/token/refreshhandlers;Azaion.Services/SessionService.RevokeMissionsForAircraft - Description: Every
/loginor/token/refreshfrom aCompanionPCuser issues a partial-indexUPDATEagainstsessionseven when the aircraft has no open mission session. The partial indexsessions_aircraft_active_idx (aircraft_id, class) WHERE revoked_at IS NULL AND aircraft_id IS NOT NULLmakes the no-op case O(0 rows touched) — but it's still a round-trip. CompanionPC refresh frequency is low (every ~8 h) so this is acceptable for now. - Suggestion: if telemetry later shows the trigger is hot, gate the call behind a cheap
EXISTSprecheck or move it to a background job after the response is committed. - Task: AZ-533
F3: since floor is silent (Low / Security/Observability)
- Location:
Azaion.AdminApi/Program.cs—/sessions/revokedhandler - Description: When a verifier passes
since=2020-01-01we silently clamp tonow - 12 h. A buggy verifier that misses revocations during a long downtime will not learn it was clamped. Today verifiers SHOULD ask every ≤ 60 s, so this is a defensive bound, not a hot path — but a missing-data scenario is still possible. - Suggestion: emit a
Warninglog when clamp triggers (since < floor). Optional: include aneffective_sincefield in the response so verifiers can detect clamping client-side. - Task: AZ-535
F4: GetUserId test helper does an extra login (Low / Maintainability)
- Location:
e2e/Azaion.E2E/Tests/MissionTokenTests.GetUserId - Description: The helper logs in twice — once for setup, then again to read the user's
nameidfrom the JWT. Each login costs ~500 ms (Argon2). Across the 6 mission tests this is ~6 extra logins × ~500 ms. - Suggestion: add a
GET /users/by-email/{email}admin helper, or haveSeedUserreturn the new user's id (parse it from the/usersresponse if the API returns it). Defer until the test suite is the bottleneck. - Task: AZ-533
Notes (non-blocking)
- The pre-existing flake in
PasswordHashingTests.AC5_Verify_uses_constant_time_comparator_no_obvious_timing_leakis NOT a batch-3 regression. Verified: the test passes when run in isolation. Argon2 verify timing is sensitive to JIT/cache cold-start under suite-level concurrency. If it keeps flaking, the right fix is to relax the0.5 × overall meanbound or warm-up Argon2 with a non-test login first. RoleEnum.Service = 60is added betweenResourceUploader = 50andApiAdmin = 1000. Existing role-string parsers (the LinqToDB converter onUser.Role) work because the column type istextand the converter isEnum.Parse(typeof(RoleEnum), v).
Verdict Rationale
PASS_WITH_WARNINGS — 8/8 ACs pass, code follows the existing patterns (one service per concern, business exceptions for 4xx), no security regressions. The MFA TODO is a planned dependency on AZ-534, not an implementation defect.