Skip to content

feat(admin-teams): decouple admin auth metadata + last_active_at tracking #229

@sage-ali

Description

@sage-ali

Context

Derived from the audit in #205. Depends on #228 (schema foundation).

Currently AdminAuthService reads and writes lockout state (failed_attempts, locked_until, last_login_at) to the shared auth_metadata table — the same table used by the app platform. This means a lockout on the admin platform locks the user out of the app too, and admin login history is mixed with app login history. last_active_at for admin platform activity does not exist anywhere.

What's needed

1. AdminAuthMetadata entity + model action

2. Migrate lockout logic in AdminAuthService

Move all reads/writes from auth_metadataadmin_auth_metadata:

  • ensureAuthMetadata → uses AdminAuthMetadataModelAction
  • recordFailedLogin → increments admin_auth_metadata.failed_attempts, sets locked_until
  • recordSuccessfulLogin → resets failed_attempts, writes last_login_at
  • throwIfLocked → reads from admin_auth_metadata

auth_metadata must not be touched by any admin auth path after this change.

3. AdminJwtGuard — write last_active_at with Redis debounce

After a request passes auth, fire-and-forget update admin_auth_metadata.last_active_at = now() for the authenticated user. To avoid a DB write on every request:

  • Redis key: admin:active:<userId> with TTL of 5 minutes
  • If key exists → skip DB write
  • If key missing → write to DB, set Redis key

The update must not block the request — wrap in a detached promise with silent error logging.

Acceptance criteria

  • Admin lockout no longer writes to or reads from auth_metadata
  • Locking an admin account does not affect their ability to log into the app platform
  • admin_auth_metadata.last_login_at is updated on every successful admin login
  • admin_auth_metadata.last_active_at is updated at most once per 5 minutes per user via the guard
  • Unit tests updated to mock AdminAuthMetadataModelAction instead of AuthMetadataModelAction

Depends on

#228

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions