fix(security): refuse the access token of a blocked or deleted user

activeUsers cache, 30 s. Core still accepts the token until it expires; X4 (batch 4) tells Core.
This commit is contained in:
lobo committed 2026-09-30 13:41:57 +02:00
1 parent 509280da14
commit 1f94c87a20
10 files changed
+109 -12

No files matched your search

+10 -7
View File
@@ -63,17 +63,17 @@ To Be Played is split into three repositories:
guard. ArchUnit fails the build for an unmarked method, two markers, a controller calling an `@Async` method, and a
service method calling another service's method that needs more.
- **Admin users page** (`/api/admin/**`, active role PLATFORM_ADMIN): search and page all users, block and unblock
(`users.enabled`; blocking ends every login, the login then answers `403 ACCOUNT_BLOCKED`, but only after the
right password), delete (the same flag and nightly cleanup as "Delete my account"). No action on the own account
(`users.enabled`; blocking ends every login and, within 30 s, the access token at Auth; Core accepts the token
until it expires, max 15 min; the login then answers `403 ACCOUNT_BLOCKED`, but only after the right password), delete (the same flag and nightly cleanup as "Delete my account"). No action on the own account
or on another admin. Every action and every "view as" is a row in `admin_audit`.
- **Dashboard hints**: a PLATFORM_ADMIN writes a hint for all users (plain text in English, German optional, start
and end time, type `DISMISS` or `ACCEPT`). The dashboard shows the running hints one at a time, newest first.
"Do not show again" on a `DISMISS` hint or "OK" on an `ACCEPT` hint is a row in `hint_done`, so the hint never
comes back for this user on any device. Creating and ending a hint are rows in `admin_audit`.
- **Delete my account** with the password (5 wrong tries per 15 minutes, then `429`). It only sets
`users.deleted_at` and revokes every refresh token: from then on no login, no refresh, no public profile, no Steam
link, not in the landing stats. Email and display name stay taken; the Steam ID is freed, so an undo needs a
new Steam link. A nightly job (04:15, Europe/Berlin) removes
`users.deleted_at` and revokes every refresh token: from then on no login, no refresh, no access token, no public
profile, no Steam link, not in the landing stats. Email and display name stay taken; the Steam ID is freed, so an
undo needs a new Steam link. A nightly job (04:15, Europe/Berlin) removes
users flagged 30 days ago, in Core first and then in Auth. It calls Core's `/internal/accounts/*` with a
**service token** (EdDSA JWT, 60 s, `aud=tbp-internal`). `app.account-cleanup.dry-run` (default `true`) only logs.
@@ -136,8 +136,10 @@ layers:
may log in and is shown to others" uses `shared/account/ActiveUser`.
A settings change is one `UserRepository.update(userId, change)`: load the user, change the model, write back the
display name and the settings. A new setting needs no new repository method.
Two Caffeine caches in `CacheConfig`: `users` (by userId) and `publicStats`. Every write evicts what it changes;
entries also expire 10 minutes after the write.
Three Caffeine caches in `CacheConfig`: `users` (by userId) and `publicStats` expire 10 minutes after the write,
`activeUsers` (the filter's "blocked or deleted?" check per request) after 30 seconds. Every write evicts what it
changes. A request that read the database just before a block or delete can put the old answer back, so the short
TTL is the real limit: a blocked or deleted user keeps a valid access token for up to 30 s.
`profile` and `stats` only read data. They have no entities and use `JdbcTemplate` queries in a `repo` package.
`version` has only an `api` package: `GET /api/version` answers the commit and build time of the running image.
`account` (delete and nightly cleanup) sets the flag with `JdbcTemplate` in a `repo` package too, and calls Core
@@ -214,6 +216,7 @@ sequenceDiagram
A->>A: users.deleted_at = now, revoke refresh tokens
A->>C: POST /internal/accounts/deleted (service token)
A-->>B: 204, both cookies cleared
Note over A: an old access token gets 401 within 30 s (activeUsers cache)
Note over A,C: nightly 04:15
A->>C: PUT /internal/accounts/deleted (all flagged users)
A->>C: POST /internal/accounts/purge (flagged 30+ days)