feat(audit): emit auth_refresh_reuse_detected on refresh-token replay #109

Merged
YousifShkara merged 1 commit from feat/audit-refresh-reuse-event into main 2026-06-11 05:45:34 +02:00
Owner

Closes BUNYIP-88.

The refresh-token reuse detection branch in crates/bunyip-oidc/src/services/oidc_provider.rs correctly revokes the whole token family on replay (refresh_token_families.revoke_reason = 'reuse_detected') but the actual security event was silent: a TODO: emit audit event auth.refresh_reuse_detected sat at the spot where the audit-log row should have been written. On a live OP that is the difference between "we noticed a replay attack and shut it down" and "nothing happened, as far as ops can tell."

Changes:

  • crates/bunyip-domain/src/models/audit.rs: add AuditAction::AuthRefreshReuseDetected and the matching "auth_refresh_reuse_detected" arm in as_str. Not an admin action (system-detected security event), so is_admin_action stays untouched.

  • crates/bunyip-oidc/src/services/oidc_provider.rs: at the TODO site, after the family-revoke commit and before returning Err(OidcInvalidGrant), build a CreateAuditLog with action AuthRefreshReuseDetected, severity Critical, actor_id = old.user_id (so /admin/audit-logs?actor_id=... surfaces the affected user without needing extra context), resource = refresh_token_family/<family_id>, and metadata carrying client_id + the two timestamps that drove the detection (jti_used_at, jti_revoked_at). The actor_id is set directly rather than via with_actor to avoid a DB lookup for email + role in a security-event hot path; metadata carries the rest.

Write is best-effort: an audit-log insert failure logs a tracing::warn! line and does NOT change the Err(OidcInvalidGrant) returned to the caller. The security event is enforced (family revoked) regardless of whether the log line lands.

just check-container clean (modulo the pre-existing test_config_defaults parallel-env-var flake on main; 188 other tests pass; fmt + clippy + build clean).

#BUNYIP-88

Closes BUNYIP-88. The refresh-token reuse detection branch in `crates/bunyip-oidc/src/services/oidc_provider.rs` correctly revokes the whole token family on replay (`refresh_token_families.revoke_reason = 'reuse_detected'`) but the actual security event was silent: a `TODO: emit audit event auth.refresh_reuse_detected` sat at the spot where the audit-log row should have been written. On a live OP that is the difference between "we noticed a replay attack and shut it down" and "nothing happened, as far as ops can tell." Changes: - `crates/bunyip-domain/src/models/audit.rs`: add `AuditAction::AuthRefreshReuseDetected` and the matching `"auth_refresh_reuse_detected"` arm in `as_str`. Not an admin action (system-detected security event), so `is_admin_action` stays untouched. - `crates/bunyip-oidc/src/services/oidc_provider.rs`: at the TODO site, after the family-revoke commit and before returning `Err(OidcInvalidGrant)`, build a `CreateAuditLog` with action `AuthRefreshReuseDetected`, severity Critical, `actor_id = old.user_id` (so /admin/audit-logs?actor_id=... surfaces the affected user without needing extra context), `resource = refresh_token_family/<family_id>`, and metadata carrying client_id + the two timestamps that drove the detection (`jti_used_at`, `jti_revoked_at`). The actor_id is set directly rather than via `with_actor` to avoid a DB lookup for email + role in a security-event hot path; metadata carries the rest. Write is best-effort: an audit-log insert failure logs a `tracing::warn!` line and does NOT change the `Err(OidcInvalidGrant)` returned to the caller. The security event is enforced (family revoked) regardless of whether the log line lands. `just check-container` clean (modulo the pre-existing `test_config_defaults` parallel-env-var flake on `main`; 188 other tests pass; fmt + clippy + build clean). #BUNYIP-88
feat(audit): emit auth_refresh_reuse_detected on refresh-token replay
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt / clippy / build / test (pull_request) Successful in 1m24s
112f7ce059
Closes BUNYIP-88.

The refresh-token reuse detection branch in `crates/bunyip-oidc/src/services/oidc_provider.rs` correctly revokes the whole token family on replay (`refresh_token_families.revoke_reason = 'reuse_detected'`) but the actual security event was silent: a `TODO: emit audit event auth.refresh_reuse_detected` sat at the spot where the audit-log row should have been written. On a live OP that is the difference between "we noticed a replay attack and shut it down" and "nothing happened, as far as ops can tell."

Changes:

- `crates/bunyip-domain/src/models/audit.rs`: add `AuditAction::AuthRefreshReuseDetected` and the matching `"auth_refresh_reuse_detected"` arm in `as_str`. Not an admin action (system-detected security event), so `is_admin_action` stays untouched.

- `crates/bunyip-oidc/src/services/oidc_provider.rs`: at the TODO site, after the family-revoke commit and before returning `Err(OidcInvalidGrant)`, build a `CreateAuditLog` with action `AuthRefreshReuseDetected`, severity Critical, `actor_id = old.user_id` (so /admin/audit-logs?actor_id=... surfaces the affected user without needing extra context), `resource = refresh_token_family/<family_id>`, and metadata carrying client_id + the two timestamps that drove the detection (`jti_used_at`, `jti_revoked_at`). The actor_id is set directly rather than via `with_actor` to avoid a DB lookup for email + role in a security-event hot path; metadata carries the rest.

Write is best-effort: an audit-log insert failure logs a `tracing::warn!` line and does NOT change the `Err(OidcInvalidGrant)` returned to the caller. The security event is enforced (family revoked) regardless of whether the log line lands.

`just check-container` clean (modulo the pre-existing `test_config_defaults` parallel-env-var flake on `main`; 188 other tests pass; fmt + clippy + build clean).

#BUNYIP-88
YousifShkara deleted branch feat/audit-refresh-reuse-event 2026-06-11 05:45:34 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
psa-systems/bunyip!109
No description provided.