Роли и права
- Ролей две: клиент (
customer) и сотрудник (operator). Внутри выпускного альбома есть организатор и участник. - Права проверяет только сервер. Приложение на экране роль показывает, но границей безопасности не является: на каждом запросе API проверяет роль и принадлежность объекта.
- Чужой объект возвращает
404, а не403: так не раскрывается, существует ли он. Каждый такой отказ пишется в журнал какaccess.denied. - Сотрудник видит заказы, обращения и журнал. Каталога, корзины и AI в его разделе нет.
Проверка: api/test/operator.test.ts, api/test/albums.test.ts, api/test/orders.test.ts.
Вход и пароли
- Код входа хранится только в виде HMAC-хеша и действует ограниченное время. Приглашения в альбом хранятся как хеш токена: утечка таблицы не даёт доступа к альбомам.
- Лимиты: 5 неверных кодов и 3 запроса кода за 10 минут на контакт; 5 неверных паролей альбома за 10 минут на приглашение и пользователя. Дальше
429 TOO_MANY_ATTEMPTS. Телефон или почта в журнал открытым текстом не попадают. - Пароль выпускного альбома (4–64 символа) хранится как
scryptс солью, не логируется и не отдаётся в API. Чтобы вступить в альбом, нужны и ссылка-приглашение, и пароль: утечка ссылки не открывает фото детей.
Проверка: api/test/auth.test.ts («лимиты входа»), api/test/albums.test.ts («пароль альбома»).
Фото и ссылки
- Фото отдаются по подписанной ссылке (HMAC), которая действует один час; испорченная или просроченная ссылка не открывается.
- Участник альбома видит и правит только свои фото; организатор видит все.
- Ответы API не кэшируются (
Cache-Control: no-store), на всех ответахX-Content-Type-Options: nosniffиReferrer-Policy: no-referrer; Caddy добавляетStrict-Transport-Security. Доступ к сайту и API только по HTTPS.
Журнал действий
Таблица audit_log только дополняется: пользователь, время, действие, объект, результат. Сотрудник видит журнал в своём разделе. Журнал переживает удаление пользователя, но содержит только идентификаторы, а не содержимое данных.
AI и согласие
- Фото уходит во внешний сервис обработки (aiai.by) только после явного согласия пользователя и только для той правки, которую он запустил.
- Согласие можно отозвать в профиле («Мои данные и безопасность»): после этого AI снова просит согласие.
- К запросам применяется фильтр недопустимых инструкций; результат обработки не попадает в заказ без действия пользователя. Лимит: не больше 10 обработок в час на пользователя.
Удаление данных
В профиле: «Мои данные и безопасность» показывает, сколько проектов, фото и заказов хранится, и позволяет удалить всё. Удаляются проекты, фото (вместе с файлами), AI-задачи и согласия; заказы остаются в истории без привязки к человеку. Проверка: api/test/privacy.test.ts.
Данные в репозитории
Только синтетические данные. Секреты (JWT_SECRET, FILE_URL_SECRET, ключ aiai.by, пароль базы) лежат в .env на сервере, в репозитории только .env.example без значений.
Ограничения
- Демо-режим (
DEMO_MODE=true): код входа всегда000000. Он включён на демо-сервере, чтобы жюри могло войти без SMS; для настоящих пользователей его нужно выключить. Лимиты попыток при этом работают, а демо-код остаётся рабочим. - Коды входа не отправляются по SMS и почте: до продакшена нужен провайдер рассылки.
- Фото хранятся на локальном диске сервера; для нескольких серверов нужно объектное хранилище.
- Права проверяются в коде API, политик на уровне базы (RLS) нет.
- Платежи имитируются, платёжных данных в системе нет.
Исходник страницы — SECURITY.md в репозитории проекта.
SECURITY.md