mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 17:22:07 +00:00
feat(auth/sessions): list-all gate + revoke-all-except-current (MED-1/2/3)
Audit 2026-05-10 Fix 13 Phase A — close MED-1, MED-2, MED-3.
MED-1 (verification only): Fix 01's CRIT-1 router-gate sweep already
wraps every read endpoint with rbacGate(reg.Checker, '<resource>.read',
...). Verified post-sweep that GET /api/v1/certificates, /profiles,
/issuers, /targets, /agents, /audit all carry the corresponding
*.read permission gate.
MED-2: ListSessions now gates ?actor_id=<other> on auth.session.list.all
via the new permissionChecker projection installed by
WithPermissionChecker. cmd/server/main.go threads the existing
authCheckerAdapter into the handler. When caller's actor_id !=
caller.ActorID AND the handler has a checker, an inline
CheckPermission(..., 'auth.session.list.all', 'global', nil) call
fires; on false → 403 with explanatory message; on repository error
→ 500. Defense-in-depth: the router-level rbacGate enforces
auth.session.list as the floor; the .list.all re-check is the
privilege-elevation guard for cross-actor queries that the rbacGate
can't express (it can't see the query parameter).
MED-3: ship DELETE /api/v1/auth/sessions?except=current — the
'sign out all other sessions' flow. Gated by auth.session.revoke;
the handler reads the caller's current session ID from
session.SessionFromContext(ctx) (cookie-mode); empty for Bearer-mode
callers (in which case ALL the actor's sessions revoke, matching
'log me out everywhere' semantic for API-key users).
New repository method SessionRepository.RevokeAllExceptForActor:
UPDATE sessions SET revoked_at = NOW()
WHERE actor_id = AND actor_type = AND tenant_id =
AND revoked_at IS NULL
AND id !=
returning rowcount. Added to the interface in internal/repository/session.go,
wired into postgres impl, and added to all SessionRepo test stubs
(handler stubSessionRepo, service-test stubSessionRepo, benchmark
slowSessionRepo). The session.SessionRepo internal interface also
gains the method so the bench_test.go forwarder compiles.
Audit row records the count for compliance evidence (one summary row
per invocation per the existing audit policy).
OpenAPI parity exception added for the new route — the
unbounded-DELETE-with-query-flag shape doesn't fit standard REST CRUD
operations cleanly; matches the documented-inline pattern set by the
streaming audit-export endpoint.
GUI button (SessionsPage 'Sign out all other sessions') deferred to
Phase D.
Refs: cowork/auth-bundles-audit-2026-05-10.md MED-1, MED-2, MED-3
Spec: cowork/auth-bundles-fixes-2026-05-10/13-med-bundle.md Phase A
This commit is contained in:
@@ -153,6 +153,14 @@ var SpecParityExceptions = map[string]string{
|
||||
// in docs/operator/security.md::audit-export and the handler doc
|
||||
// comment.
|
||||
"GET /api/v1/audit/export": "Audit 2026-05-10 HIGH-11 — streaming NDJSON audit export; gated audit.export. Documented inline at internal/api/handler/audit.go::ExportAudit.",
|
||||
|
||||
// Audit 2026-05-10 MED-3 — `DELETE /api/v1/auth/sessions?except=current`
|
||||
// is the "sign out all other sessions" flow. Distinct from the
|
||||
// per-session DELETE /api/v1/auth/sessions/{id} (already in OpenAPI);
|
||||
// this variant operates on the caller's whole session set minus the
|
||||
// current. Documented inline at
|
||||
// internal/api/handler/auth_session_oidc.go::RevokeAllExceptCurrent.
|
||||
"DELETE /api/v1/auth/sessions": "Audit 2026-05-10 MED-3 — sign-out-all-other-sessions; gated auth.session.revoke. Documented inline at internal/api/handler/auth_session_oidc.go::RevokeAllExceptCurrent.",
|
||||
}
|
||||
|
||||
func TestRouter_OpenAPIParity(t *testing.T) {
|
||||
|
||||
@@ -446,6 +446,12 @@ func (r *Router) RegisterHandlers(reg HandlerRegistry) {
|
||||
// handler layer per Phase 5 spec.
|
||||
r.Register("GET /api/v1/auth/sessions", rbacGate(reg.Checker, "auth.session.list", reg.AuthSessionOIDC.ListSessions))
|
||||
r.Register("DELETE /api/v1/auth/sessions/{id}", rbacGate(reg.Checker, "auth.session.revoke", reg.AuthSessionOIDC.RevokeSession))
|
||||
// Audit 2026-05-10 MED-3 closure — DELETE /api/v1/auth/sessions?except=current
|
||||
// is the "Sign out all other sessions" flow. Gated by
|
||||
// auth.session.revoke (any authenticated caller with the perm
|
||||
// can revoke their OWN remaining sessions; the handler reads
|
||||
// the current session ID from context and excludes it).
|
||||
r.Register("DELETE /api/v1/auth/sessions", rbacGate(reg.Checker, "auth.session.revoke", reg.AuthSessionOIDC.RevokeAllExceptCurrent))
|
||||
|
||||
// OIDC provider CRUD.
|
||||
r.Register("GET /api/v1/auth/oidc/providers", rbacGate(reg.Checker, "auth.oidc.list", reg.AuthSessionOIDC.ListProviders))
|
||||
|
||||
Reference in New Issue
Block a user