PROFORM DIGITAL В пространство

Защита

Как устроена защита

Десять фактов о системе, которую мы отдаём заказчику. Каждый можно проверить: командой в терминале или по файлу и строке в коде — код с первого дня лежит в вашем репозитории.

Тот же код держит клуб PROFORM DINAMO: crm.proformdinamo.com · строки указаны на 14.09.2026

  1. 01

    Пароли хранятся хэшем bcrypt, и ответ занимает одинаковое время

    В базе нет паролей — только bcrypt-хэш, из которого пароль не восстановить. Если такого логина нет, сервер всё равно выполняет сравнение с подставным хэшем: по скорости ответа нельзя узнать, существует ли адрес.

    Проверьтеpsql -c 'SELECT LEFT("passwordHash",7) FROM "User" LIMIT 3'
    → каждая строка начинается с $2b$10$ — это bcrypt, не пароль

    apps/crm/src/auth.ts:11 (DUMMY_HASH) · :107–109 (bcrypt.compare с подставным хэшем)

  2. 02

    Пять неверных паролей — и вход закрыт на пятнадцать минут

    Пять ошибок на один адрес или двадцать с одного IP за 15 минут — вход закрыт. Попытка в закрытом окне не продлевает его: пять неудачных попыток остаются записанными, а через пятнадцать минут вход снова открыт. Экран говорит «вход закрыт», а не «неверный пароль», чтобы сотрудник не продолжал.

    Проверьте в своей системепять раз неверный пароль на один адрес
    → шестая попытка получает код locked, а не «неверный пароль»

    apps/crm/src/auth.ts:90 (окно 15 минут) · :96–104 (счётчики и порог 5 / 20) · :15–17 (код locked)

  3. 03

    Каждый вход записан: кто, когда, с какого адреса

    Успешный вход в CRM — строка в журнале с именем, временем и IP-адресом. Вход с верным паролем, но без права входа в CRM, тоже попадает в журнал. Адрес берёт наш прокси-сервер из самого соединения, а не из заголовка, который прислал клиент.

    Проверьте в своей системеЖурнал → действие login
    → у каждой строки в поле meta стоит ip

    apps/crm/src/auth.ts:139–143 (запись входа с ip) · :124–131 (login.denied: верный пароль без права входа в CRM) · :37–50 (X-Real-IP от прокси)

  4. 04

    Права читаются из базы при каждом запросе

    Роль, права и статус сотрудника сервер читает из базы на каждом запросе, а не из сохранённой сессии. Отключённый сотрудник вылетает на следующем клике — не через час и не завтра.

    Проверьте в своей системеотключите сотрудника в карточке
    → его следующий клик в CRM заканчивается на экране входа

    apps/crm/src/auth.ts:56–58 · :65–76 (findUnique на каждом запросе, null при isActive = false) · apps/crm/src/auth.config.ts:17–19

  5. 05

    Двадцать пять отдельных прав, три роли

    Права — не «админ и остальные», а 25 отдельных ключей: продажа, сторно, правка платежей, выплаты, журнал, тарифы и так далее. Три роли: владелец, менеджер, сотрудник. Новый сотрудник не получает ничего, пока владелец не даст. Проверяет сервер в каждом действии — спрятанная кнопка защитой не считается.

    Проверьтеgrep -c 'key: "' apps/crm/src/lib/permissions.ts
    → 25

    apps/crm/src/lib/permissions.ts:5–79 (25 ключей) · :83 (три роли) · :129–132 (STAFF: []) · :145–148 (hasPerm) · apps/crm/src/app/dashboard/bar/actions.ts:25–30 (проверка в действии)

  6. 06

    Журнал: база данных сама запрещает менять и удалять записи — год

    Записи журнала приложение может только создавать и читать: на уровне СУБД у него отозваны UPDATE и DELETE. Удалить можно только записи старше года, только владелец (проверяет приложение), только через функцию базы — и эта функция сама оставляет запись о том, сколько строк удалено и до какой даты.

    Проверьтеpsql -c '\dp "AuditLog"'
    → у роли proform_app в списке прав нет ни w (update), ни d (delete)

    packages/db/prisma/migrations/20260630045232_audit_append_only/migration.sql:4 · 20260806210000_audit_prune_one_year/migration.sql:37–42, 45–55 · apps/crm/src/app/dashboard/audit/actions.ts:22 (только FOUNDER)

  7. 07

    Четыре пользователя базы, у каждого своя граница

    Владелец схемы, приложение, только чтение — и отдельный пользователь у памяти ИИ, который видит только свою схему. Приложение работает с данными, но не может менять структуру таблиц; аналитика читает и ничего не пишет. У каждого свой пароль; пароли лежат в файле окружения на сервере, не в коде.

    Проверьтеpsql -c '\du'
    → proform_owner · proform_app · proform_readonly · brain_svc

    infra/postgres/init/01-init.sh:11–20 (proform_app: только DML) · :22–27 (proform_readonly: только SELECT) · infra/CRONTAB.md:174 (brain_svc: только схема brain) · infra/docker-compose.yml:6 (пароли из infra/.env)

  8. 08

    HTTPS с HSTS-preload; экран нельзя встроить в чужой сайт

    CRM отдаёт Strict-Transport-Security на два года с preload и Content-Security-Policy frame-ancestors 'none': браузер не откроет систему по http и не покажет её внутри чужой страницы. Плюс nosniff и Referrer-Policy на каждом ответе.

    Проверьтеcurl -sI https://crm.proformdinamo.com/login | grep -iE 'strict|frame'
    → strict-transport-security: max-age=63072000; includeSubDomains; preload
    → content-security-policy: frame-ancestors 'none'

    apps/crm/next.config.ts:21–31 (securityHeaders) · :50–52 (на все пути)

  9. 09

    Токен приложения живёт пятнадцать минут

    Токен доступа клиентского приложения подписан HS256 и действует 15 минут. У токена тренера своя область: он не открывает клиентские методы, и наоборот. Заблокированный аккаунт отвергается на каждом запросе, даже с живым токеном.

    Проверьтедекодируйте payload токена (base64url, вторая часть)
    → exp − iat = 900 секунд

    apps/crm/src/lib/member/jwt.ts:3–5 (ACCESS_TTL 15m) · :25–29 (scope trainer отклоняется) · apps/crm/src/lib/member/guard.ts:30–31 (appEnabled на каждом запросе)

  10. 10

    Сервер в Ташкенте; база закрыта от интернета; копия каждую ночь

    Сервер стоит в сети Uztelecom. База данных и кэш слушают только сам сервер — адрес 127.0.0.1. Резервная копия снимается каждую ночь; подробности и команды для проверки — в блоке ниже.

    Проверьтеwhois 89.126.223.217
    → netname: UZTELECOM · country: UZ · Tashkent

    infra/docker-compose.yml:4 · :20 (127.0.0.1:5433) · :37 (127.0.0.1:6379) · infra/backup.sh:3–4 · :35–37

Где живут ваши данные

Сервер стоит в Ташкенте, в сети Uztelecom: адрес 89.126.223.217, на него указывают и digital.proformdinamo.com, и crm.proformdinamo.com. База данных PostgreSQL и кэш Redis слушают только сам сервер. Снаружи открыты три порта: 22 — SSH, вход только по ключу; 80 и 443 — сайт. Всё остальное закрыто.

Резервная копия базы снимается каждую ночь в 03:30: тридцать суточных копий по кругу, первая копия каждого месяца хранится бессрочно. Команда для восстановления записана в самом скрипте копирования — копия существует не как файл, а как способ вернуться.

$ whois 89.126.223.217
netname:  UZTELECOM
country:  UZ
address:  Tashkent, Uzbekistan

$ dig +short crm.proformdinamo.com
89.126.223.217

$ nc -zv 89.126.223.217 443
→ succeeded
$ nc -zv 89.126.223.217 5432
→ Operation timed out — сервер не отвечает
$ nc -zv 89.126.223.217 6379
→ Operation timed out — сервер не отвечает
$ ssh root@89.126.223.217
→ Permission denied (publickey)

infra/docker-compose.yml:20 · :37 · infra/backup.sh:3–4 · :35–37 (команда восстановления) · infra/CRONTAB.md:17–18

Защита и доступы как услуга

То, что держит клуб, — и то, что мы добавляем для вас. Начинается не с договора, а с одного часа в вашей смене.

Проверка доступов Час в вашей смене, акт на следующий день: кто знает какие пароли, какие аккаунты общие, где лежит база клиентов, кто из уволенных ещё может войти, где лежат копии. Одна страница: что закрыть сегодня, что перенести в систему, что не трогать.
Роли и журнал Кто что может — отдельными правами, а не «админ и остальные». Каждое действие с суммой, именем и временем в журнале, который база не даёт править.
Вход по коду Одноразовый код вместо общего пароля на стойке: живёт пять минут, срабатывает один раз, в базе хранится только его хэш, выдача записана в журнал.
Ночная копия с проверкой восстановления Копия каждую ночь, тридцать суток по кругу, месячные — бессрочно. И восстановление на тестовую базу: копия, которую никто не разворачивал, — это надежда, а не копия.
Сервер: порты, ключи, HSTS Снаружи открыто только нужное, вход по ключу, база и кэш слушают только сам сервер, HTTPS с HSTS и запретом встраивания.
Рассчитать проект →

Состав и цену собирает калькулятор за три минуты. Точную сумму называет Тимур после разговора, когда видит объём. Телефон +998 94 045 54 48.