Admin authentication
Overview
Platform admin access is restricted to a single configured operator (Mihaham by default). Authentication uses a one-time password (OTP) delivered via Telegram bot command /admin login. The web admin panel verifies the OTP and issues a short-lived admin JWT.
FR: FR-057, FR-058
Configuration
| Env var | Default | Description |
|---|---|---|
ADMIN_TELEGRAM_ID |
— | Numeric Telegram user ID of the sole admin |
ADMIN_USERNAME |
Mihaham |
Username required on web login (case-insensitive) |
ADMIN_OTP_TTL_SECONDS |
300 |
OTP validity (5 minutes) |
ADMIN_JWT_TTL_HOURS |
8 |
Admin session duration |
Both API and bot services read ADMIN_TELEGRAM_ID from .env.
Flow
sequenceDiagram
participant Admin as Admin
participant Bot as telegram_bot
participant API as API
participant Redis as Redis
participant Web as admin_UI
Admin->>Bot: /admin login
Bot->>Bot: from_user.id == ADMIN_TELEGRAM_ID?
Bot->>API: POST /auth/admin/otp/request {telegram_id}
API->>Redis: SET admin:otp:{id} code TTL
API-->>Bot: {code, expires_in}
Bot->>Admin: DM with 6-digit code
Admin->>Web: /admin/login
Web->>API: POST /auth/admin/otp/verify {username, code}
API->>Redis: validate + delete OTP
API-->>Web: {access_token, token_type: admin}
Web->>API: GET /admin/games (Authorization: Bearer admin JWT)
OTP request (POST /auth/admin/otp/request)
- Caller: Telegram bot only (internal service call).
- Body:
{ "telegram_id": 123456789 } - Validation:
telegram_idmust equalADMIN_TELEGRAM_ID. - Storage: Redis key
admin:otp:{telegram_id}→ 6-digit code; TTL =ADMIN_OTP_TTL_SECONDS. - Rate limit: Max 3 requests per 15 minutes per telegram ID.
- Response:
{ "expires_in": 300 }— code is not returned in HTTP response (bot receives it from a dedicated internal field or the bot generates and stores via API — see implementation).
OTP verify (POST /auth/admin/otp/verify)
- Caller: Web admin login page.
- Body:
{ "username": "Mihaham", "code": "123456" } - Validation:
- Username matches
ADMIN_USERNAME(case-insensitive). - Code matches Redis value for
ADMIN_TELEGRAM_ID. - Max 3 failed attempts per OTP; then OTP invalidated.
- Response:
{ "access_token": "...", "token_type": "admin", "expires_in": 28800 } - Rate limit: Max 10 verify attempts per 15 minutes per IP.
Admin JWT
| Claim | Value |
|---|---|
sub |
"admin" |
type |
"admin" |
username |
Mihaham |
exp |
now + ADMIN_JWT_TTL_HOURS |
Header: Authorization: Bearer <admin_jwt>
Admin tokens are stored separately from player tokens in the web client (tabletime_admin_token).
Bot commands
| Command | Who | Action |
|---|---|---|
/admin |
Admin only | Help text for admin commands |
/admin login |
Admin only | Request OTP and send DM with code |
Non-admin users receive a generic “unknown command” or no response.
Threat model
| Threat | Mitigation |
|---|---|
| OTP brute force | 6-digit code + 3 attempts + TTL 5 min + rate limits |
| OTP interception | Short TTL; single use; delivered only to configured Telegram ID |
| Stolen admin JWT | 8 h TTL; HTTPS only in production |
| Unauthorized audit access | GET /games/{id}/audit requires admin JWT |
| OTP in application logs | Never log the code value |
Prerequisites
- Admin must send
/startto the bot at least once (Telegram DM requirement). ADMIN_TELEGRAM_IDmust match the admin's numeric user ID.BOT_TOKENmust be valid in both API and bot services.