Public Access
feat(registration): registration waits for the admin, no invite code
This commit is contained in:
1 parent
1008034e82
commit
39a4d5ae49
26 files changed
+261
-147
No files matched your search
@@ -13,10 +13,10 @@ To Be Played is split into three repositories:
|
||||
|
||||
## Features
|
||||
|
||||
- Registration with an invitation code, login with email and password (BCrypt). Emails are stored trimmed and in
|
||||
- Registration by admin approval, login with email and password (BCrypt). Emails are stored trimmed and in
|
||||
lower case (V19, unique on `lower(email)`); a password has 8 characters and at most 72 bytes (BCrypt's limit).
|
||||
- Brute-force protection in memory: 5 failed logins per email or 20 per IP, and 10 failed registrations per IP,
|
||||
block for 15 minutes with `429 TOO_MANY_ATTEMPTS` and `Retry-After`. Unknown emails cost the same BCrypt time as a
|
||||
- Brute-force protection in memory: 5 failed logins per email or 20 per IP, and 10 failed or 3 taken registrations
|
||||
per IP, block for 15 minutes with `429 TOO_MANY_ATTEMPTS` and `Retry-After`. Unknown emails cost the same BCrypt time as a
|
||||
wrong password, and every failed login gets the same error.
|
||||
- JWT signed with **EdDSA (Ed25519)**. Auth holds the private key, Core only the public key, so Core can verify
|
||||
tokens but never create them. Tokens carry a `kid` header, and `GET /.well-known/jwks.json` publishes the
|
||||
@@ -74,9 +74,9 @@ To Be Played is split into three repositories:
|
||||
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`.
|
||||
- **Mails** from the platform mailbox: "new user registered" to the admin (`APP_MAIL_ADMIN_TO`), and to the user
|
||||
"your account is active" at the registration, "your account is blocked" and "your account will be deleted from
|
||||
<date>". The registration sends the browser language (`de` for a German browser, else `en`), which also becomes
|
||||
- **Mails** from the platform mailbox: "a new registration waits" to the admin (`APP_MAIL_ADMIN_TO`, with the link
|
||||
to the admin page), and to the user "we got your registration", then "your account is active" or "your
|
||||
registration was denied", later "your account is blocked" and "your account will be deleted from <date>". The registration sends the browser language (`de` for a German browser, else `en`), which also becomes
|
||||
the user's app language. Plain text only. See "Mail flow".
|
||||
- **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 access token, no public
|
||||
@@ -260,7 +260,7 @@ sequenceDiagram
|
||||
participant B as Browser (Frontend)
|
||||
participant A as Auth
|
||||
participant M as SMTP (Hostinger)
|
||||
B->>A: register / admin blocks / delete account
|
||||
B->>A: register / admin decides / admin blocks / delete account
|
||||
A->>A: event + mail_outbox row (PENDING), one transaction
|
||||
loop every minute (MailOutboxService)
|
||||
A->>M: up to 20 due rows
|
||||
@@ -269,6 +269,31 @@ sequenceDiagram
|
||||
Note over A: after 10 tries FAILED and a warning in the log; SENT rows are removed after 30 days
|
||||
```
|
||||
|
||||
### Registration flow
|
||||
|
||||
A registration is a request: the account is PENDING until a PLATFORM_ADMIN decides. While 20 wait, the
|
||||
registration answers `409 PENDING_LIMIT_REACHED` and stores nothing. Two requests at the same moment can make it 21.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as New user
|
||||
participant A as Auth
|
||||
participant D as Admin
|
||||
U->>A: POST /api/registration/new
|
||||
A->>A: users.status = PENDING, two mail rows
|
||||
A-->>D: mail "a new registration waits"
|
||||
A-->>U: mail "we got your registration"
|
||||
U->>A: login: 403 ACCOUNT_PENDING
|
||||
D->>A: PUT /api/admin/users/{id}/decision {accepted}
|
||||
alt accepted
|
||||
A->>A: status = ACTIVE
|
||||
A-->>U: mail "your account is active"
|
||||
else denied
|
||||
A->>A: status = DENIED, deleted_at = now (nightly cleanup after 30 days)
|
||||
A-->>U: mail "your registration was denied"
|
||||
end
|
||||
```
|
||||
|
||||
### Steam ID and block sync to Core
|
||||
|
||||
Core keeps its own copy of the Steam ID and of the block. After a verified Steam link (also for the account that
|
||||
@@ -307,8 +332,8 @@ other, then start them in this order (Core also starts without Auth and finds it
|
||||
In Auth, `local` makes a new signing key at each start (`LocalKeysEnvironmentPostProcessor`, only in this
|
||||
profile, and never together with `prod`), so no key is in the repo; after an Auth restart (also a devtools
|
||||
restart), log in again. Never use `local` on a server: the dev user's password is public. The
|
||||
Flyway callback in `db/local` adds the dev user (`PLATFORM_ADMIN`, linked Steam ID). The invite code for more
|
||||
users is `local`. What Core mocks and how to change the recorded answers: Core readme, "Run locally".
|
||||
Flyway callback in `db/local` adds the dev user (`PLATFORM_ADMIN`, linked Steam ID). A new local user waits until
|
||||
the dev user accepts it on the admin page. What Core mocks and how to change the recorded answers: Core readme, "Run locally".
|
||||
|
||||
### With your own keys (profile `dev`)
|
||||
|
||||
@@ -332,14 +357,13 @@ Configuration comes from environment variables:
|
||||
|---|---|
|
||||
| `JWT_PRIVATE_KEY`, `JWT_PUBLIC_KEY` | Ed25519 keypair, Base64 of the DER bytes (PKCS#8 / X.509) |
|
||||
| `JWT_KID` | Key id written into the token header |
|
||||
| `AUTH_INVITATION_CODE` | Code required for registration |
|
||||
| `DB_PASSWORD` | Local PostgreSQL password |
|
||||
| `APP_PUBLIC_URL` | Auth's public address, the Steam OpenID realm (default `http://localhost:8080`) |
|
||||
| `APP_FRONTEND_URL` | Where the Steam link ends (default `http://localhost:5173`) |
|
||||
| `APP_CORE_INTERNAL_URL` | Core for the nightly account cleanup, required (local `http://localhost:8090`, Docker `http://tbp-core-service:8090`) |
|
||||
| `SPRING_MAIL_HOST`, `SPRING_MAIL_USERNAME`, `SPRING_MAIL_PASSWORD` | SMTP server and its login (port 465, SSL). No host = mail off |
|
||||
| `APP_MAIL_FROM` | Sender address, the mailbox of the SMTP login. Needed with a host |
|
||||
| `APP_MAIL_ADMIN_TO` | Gets "new user registered". Needed with a host |
|
||||
| `APP_MAIL_ADMIN_TO` | Gets "a new registration waits". Needed with a host |
|
||||
|
||||
Generate a keypair:
|
||||
|
||||
|
||||
Reference in new issue
Block a user