The admin key used to stay in the URL fragment and was sent with every
request. Now the key is exchanged once for a server-side session:
- POST /api/admin/login {key} sets bingo_admin=<random token>
(HttpOnly, Secure, SameSite=Strict, no Path so it is scoped to the
admin API directory, also behind a subpath proxy)
- POST /api/admin/logout ends the session
- sessions live in memory as sha256(token) -> expiry, absolute lifetime
ADMIN_SESSION_HOURS (default 8); restart logs everyone out
- /admin#k=KEY still works as a login link; the page strips the fragment
from the URL and history before logging in, then shows plain /admin
- wrong key answers 401 after 500 ms; Bearer header still works for scripts
- COOKIE_SECURE=0 for plain-http testing only
Tests: 11 passing (login, cookie flags, logout, expiry, admin disabled).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhHiznVwtQ55ZSguTBWrxn
24 lines
1.4 KiB
Bash
24 lines
1.4 KiB
Bash
# Скопируйте в .env и заполните. cp .env.example .env
|
|
|
|
# Ключ для страницы /admin (от 16 символов). Сгенерировать: openssl rand -base64 24
|
|
# Пустой ключ = админка выключена.
|
|
ADMIN_KEY=
|
|
|
|
# Сколько часов действует вход в /admin (с момента входа, без продления). Перезапуск контейнера тоже завершает вход.
|
|
ADMIN_SESSION_HOURS=8
|
|
|
|
# Сезон
|
|
TIMEZONE=Europe/Moscow # в каком поясе карточка обнуляется в понедельник 00:00
|
|
SEASON_START=2026-11-02 # первый день, когда отметки идут в батарейку
|
|
SEASON_END=2026-12-21 # последний такой день (включительно)
|
|
GOAL=500 # цель сезона в шагах
|
|
|
|
# Резервные копии базы в томе (раз в сутки), сколько последних хранить. 0 = выключить.
|
|
BACKUP_KEEP=14
|
|
|
|
# Куда пробросить порт на хосте. 127.0.0.1 — доступ только для nginx на этом же хосте.
|
|
# Если nginx на другой машине или в другом контейнере — см. README, раздел «nginx».
|
|
HOST_BIND=127.0.0.1
|
|
HOST_PORT=8080
|
|
|