Public Access
feat(admin): add roles, view as and the admin users API
users.role (USER < AUDITOR < PLATFORM_ADMIN) goes into the access token as the active role. POST /api/auth/view-as lowers the own login and is kept per refresh token, never above the stored role. /api/admin/users lists, blocks and deletes users; every action is written to admin_audit. A blocked login answers 403 ACCOUNT_BLOCKED, only after the right password.
This commit is contained in:
1 parent
7dc2fb613e
commit
2ba0d99148
55 files changed
+1184
-43
No files matched your search
@@ -38,6 +38,15 @@ To Be Played is split into three repositories:
|
||||
10 minutes, `typ=public-profile`) that Core accepts for the public library data only. Its `profileStats` claim
|
||||
names the stat groups the user chose to show there (none by default). The ticket is not a login:
|
||||
both services reject every token with a `typ` claim on normal endpoints.
|
||||
- **Roles**: one per user, a ladder `USER < AUDITOR < PLATFORM_ADMIN` (`users.role`, the first admin is set by
|
||||
SQL). The access token carries the active role in the `role` claim. **View as**: an admin switches the own login
|
||||
down to USER or AUDITOR and back (`POST /api/auth/view-as`); the choice is stored per login in
|
||||
`refresh_tokens.active_role` and never goes above the stored role, so a demoted admin loses the rights at the next
|
||||
refresh. Core does not check roles yet.
|
||||
- **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
|
||||
or on another admin. Every action and every "view as" is a row 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
|
||||
@@ -57,6 +66,10 @@ To Be Played is split into three repositories:
|
||||
| `PUT` | `/api/users/settings` | access token |
|
||||
| `POST` | `/api/users/steam-link` | access token, returns Steam's login URL and sets the binding cookie |
|
||||
| `DELETE` | `/api/users/steam-link` | access token, removes the Steam ID and sets a new access cookie |
|
||||
| `POST` | `/api/auth/view-as` (`{role}`, null = own role; new cookies; `403 ROLE_NOT_ALLOWED` above the own role) | refresh cookie |
|
||||
| `GET` | `/api/admin/users?query=&page=&size=` (`{content, page, size, totalElements, totalPages}`) | PLATFORM_ADMIN |
|
||||
| `PUT` | `/api/admin/users/{id}/blocked` (`{blocked}`; `409 SELF_ACTION`, `TARGET_IS_ADMIN`, `ALREADY_DELETED`) | PLATFORM_ADMIN |
|
||||
| `DELETE` | `/api/admin/users/{id}` (`409 SELF_ACTION`, `TARGET_IS_ADMIN`) | PLATFORM_ADMIN |
|
||||
| `DELETE` | `/api/users/me` (`{password}`; `204` and both cookies cleared, `403 INVALID_PASSWORD`, `429`) | access token |
|
||||
| `GET` | `/api/steam-link/callback` | public, Steam's redirect back; binding cookie; redirects to the frontend settings |
|
||||
| `PUT` | `/api/users/settings/appearance` | access token |
|
||||
@@ -91,6 +104,9 @@ layers:
|
||||
`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
|
||||
through `CoreAccountService`.
|
||||
`admin` (users page, audit) works the same way. It deletes through the port `shared/account/AccountDeleter`, and
|
||||
the login module writes "view as" to the audit table through `shared/audit/AdminAuditLog`, because modules never
|
||||
use each other.
|
||||
|
||||
ArchUnit enforces these rules in `src/test/java/auth_service/architecture/ArchitectureTest.java`, so a layer
|
||||
violation fails the build. It also keeps the modules independent of each other and free of cycles. Test base
|
||||
@@ -113,6 +129,9 @@ sequenceDiagram
|
||||
B->>A: POST /api/auth/refresh with tbp_refresh
|
||||
A-->>B: New tbp_access and tbp_refresh, old refresh token rotated
|
||||
B->>C: Same API call again
|
||||
Note over B,A: View as (admin only)
|
||||
B->>A: POST /api/auth/view-as {role} with tbp_refresh
|
||||
A-->>B: New tbp_access (role claim) and tbp_refresh, active role stored for this login
|
||||
```
|
||||
|
||||
1. `LoginService` checks the password. `LoginController` sets both HttpOnly cookies (`AuthCookies`), so no script
|
||||
|
||||
Reference in new issue
Block a user