Security
Учётная запись администратора
Одна учётная запись — один человек. Общие пароли команды не поддерживаются: журнал фиксирует учётную запись, а не личность пользователя общей записи. Приглашения и новая модель ролей не входят в текущую реализацию. После обновления с реестром сессий потребуется войти заново. Выход немедленно отзывает текущую сессию; смена пароля — все сессии этой учётной записи. Админка хранит авторизацию только в HttpOnly-cookie. Срок по умолчанию — один час; оператор может задать 300–86400 секунд через ADMIN_SESSION_TTL_SECONDS.
RetentionPlay treats the browser as untrusted. These are the guarantees you can rely on and the responsibilities on your side.
Principles
- The widget/browser is never the source of truth.
- All reward decisions are made server-side (eligibility, caps, cooldowns, fraud flags).
- Project/customer tokens are scoped; API keys carry explicit scopes (for example
server:events). - Webhooks are HMAC-signed with replay protection.
- Public endpoints are rate limited; reward callbacks support idempotency keys.
What you must do
| Responsibility | How |
|---|---|
| Keep secrets server-side | Issue session tokens / verify webhooks on your backend only |
Set allowed_origins | Exact scheme + host + port for every embedding site; a reward-bearing game_mode refuses to save with this list empty |
| Verify webhook signatures | Use SDK verify helpers; enforce skew window |
| Make reward grants idempotent | Key on claim/outcome id — the server's own deterministic key decides uniqueness, not your Idempotency-Key header |
| Confirm material rewards server-side | POST /v1/server/events with API key |
FraudGate
Every event ingress runs through ordered rules; the first Block wins, Flag results accumulate, otherwise Allow:
| Rule | Protects against |
|---|---|
| Origin | Embedding from unapproved sites (403 origin_forbidden) |
| Plan limit | Usage beyond plan (skip-paths for safe routes) |
| Play / preview rate limit | Burst abuse on public endpoints |
| Score velocity | Implausible score gains |
| Idempotency | Duplicate processing / double claims |
| Replay | Re-sent confirmations (enforce vs monitor modes) |
| Address rate signal | Flags (never blocks) bursts from one network address across identities |
| Custom rules | Project-specific fraud_config.custom_rules |
The client's own IP address is never trusted directly: it is resolved from the real connection, honoring X-Forwarded-For only up to a configured trusted reverse-proxy boundary, so a client cannot pick its own rate-limit key by forging that header.
A play session's outcome event (spin.reveal, scratch.reveal, level.complete, …) can be consumed exactly once: the fraud gate claims it atomically as part of evaluating the request, not by reading a flag and marking it afterward, so two concurrent requests replaying the same nonce cannot both pass. Reward-claim eligibility (per-user cap, campaign total, cooldown) is checked and the claim inserted inside one transaction that locks the campaign row, so concurrent claims for the same campaign are serialized rather than all reading a stale count before any of them commits.
Score reconciliation: on level.complete the server reconciles the client score against the server-accumulated session score. Inflation beyond tolerance is rejected (validation_error) and, for promo_rewards / in_game_economy, blocked (403 fraud_blocked).
Reward gate. A project set to protect mode (the default is observe, which never delays anything) can hold an otherwise-eligible claim when its risk score crosses a configured threshold, or send it to manual review at a higher one. Your reward.eligible webhook is then not queued at the moment of eligibility — it fires later, either automatically once the hold's TTL passes with a lower recomputed risk, or from a manual release. The webhook's own idempotency key does not change between the held and released state, so your existing idempotent handling needs no special case for this — just be aware the delivery can arrive noticeably later than the triggering event under protect mode. Daily reward caps (per player and per project) apply independently of this and reject a claim outright rather than holding it.
Rate limits
Public endpoints are rate limited; server API keys are additionally limited (for example 600/min per key on /v1/server/events). Handle 429 with backoff.
Audit
Admin and reward-related actions are written to an audit log. Fraud decisions emit structured fraud_decision logs.