Commit Graph

20 Commits

Author SHA1 Message Date
grendervill e4ffbb05a5 feat(messages): сообщения, реакции, пины, typing, read states и поиск
Бэкенд текстовой связи (AGENT.md 7.6, 7.15, 7.16, 8.6):

- POST/GET/PATCH/DELETE /channels/{id}/messages, ответы и упоминания,
  окно правки 24 часа, права автора или MANAGE_MESSAGES;
- реакции (PUT/DELETE .../reactions/{emoji}) с агрегацией и отметкой «моя»;
- закрепления (GET/PUT/DELETE .../pins) под MANAGE_MESSAGES;
- typing с лимитом 1/3 с и адресной рассылкой всем, кроме автора;
- read states: POST /channels/{id}/ack, синхронизация READ_STATE_UPDATE между
  устройствами пользователя;
- поиск FTS5 по комнате (10/мин) с экранированием запроса;
- лимиты отправки 5/5 с (burst 10) и slowmode комнаты; администратор инстанса
  обходит и то, и другое (AGENT.md 7.19);
- Gateway: DispatchToChannel/DispatchToChannelExcept доставляют события комнат
  только тем, кто видит комнату (VIEW_CHANNEL), права считает общий движок;
- store: messages, message_reactions, channel_read_states, files (загрузка
  файлов появится вместе с вложениями).

Тесты: жизненный цикл сообщения, видимость скрытой комнаты (404 участнику,
200 админу), slowmode, упоминания и ответы, typing и read state, фильтрация
событий комнаты между двумя WS-клиентами.
2026-09-19 23:32:11 +03:00
grendervill 4550bea6cc feat(db): схема Фазы 2 — сообщения, реакции, read states, файлы, инвайты, FTS5
Миграция 00004: таблицы `messages`, `message_reactions`,
`channel_read_states`, `files`, `invites` и виртуальная таблица `messages_fts`
с триггерами синхронизации (AGENT.md 6.1, 7.6, 7.7, 7.9, 7.15, 7.16).

Полнотекстовый поиск требует сборки с тегом `sqlite_fts5`:
- `GO_TAGS ?= sqlite_fts5` в Makefile (build/test/cover) и в Dockerfile;
- build-tags в golangci-lint;
- страж `internal/database/fts5_guard.go`: сборка без тега падает с понятным
  текстом, а не ломается на миграции в рантайме.
2026-09-19 23:29:39 +03:00
grendervill 0a82dd3a02 feat(api,install): исключения fail2ban и смена своего никнейма
- установщик: `[DEFAULT] ignoreip` в jail.local — локальные сети и WireGuard
  исключены всегда, свой адрес добавляется флагом `--ignore-ip <ip|cidr>`;
  админ, часто заходящий в панель, больше не банит сам себя;
- участник может менять свой никнейм на сервере без MANAGE_NICKNAMES и без
  проверки иерархии (тайм-аут на себя по-прежнему запрещён);
- тест на смену своего ника и запрет self-тайм-аута.
2026-09-19 23:27:26 +03:00
grendervill 9c001fedfe feat(web): базовый клиент Фазы 1 — вход, серверы, комнаты, настройки
Клиент React 19 + TanStack Query + Zustand:

- вход (поле TOTP появляется на `auth.2fa_required`), регистрация с проверками
  и учётом `registration_enabled`, онбординг первого входа с возможностью
  пропустить, редирект `/` и страница `/status`;
- защита маршрутов: неавторизованных — на `/login`, без онбординга — на
  `/onboarding`;
- оболочка `/app`: рейка серверов, сайдбар категорий и комнат, шапка сервера,
  панель пользователя, модалки создания сервера и комнаты, экран «нет серверов»
  со вступлением в главный сервер, заглушка комнаты до Фазы 2;
- настройки: профиль, безопасность (step-up, сессии, logout-all, 2FA с
  локальным QR и кодами восстановления), внешний вид, админ-раздел инстанса;
- Gateway-клиент: HELLO/IDENTIFY/RESUME, heartbeat с ожиданием ACK,
  экспоненциальное переподключение, разбор `INVALID_SESSION {reason,resumable}`;
- диспетчер событий: READY/RESUMED/USER_UPDATE/GUILD_UPDATE/GUILD_DELETE/
  CHANNEL_* применяются к стору, GUILD_CREATE догружается по REST,
  MEMBER_*/ROLE_* инвалидируют запросы участников, ролей и карточки сервера;
- i18n ru/en, 66 тестов Vitest, `check`/`lint`/`test`/`build` зелёные.

Серверные правки под клиент:

- `totp_enabled` в профиле (`GET /users/@me`, вход, регистрация) — клиенту
  нужно знать, требовать ли код при step-up;
- `POST /auth/2fa/setup` отдаёт `otpauth_url` и `qr_png` (data-URI): QR-код
  рисуется локально, внешние сервисы генерации QR не используются;
- права в ответах — имена (`VIEW_CHANNEL|SEND_MESSAGES`), клиент сверяет имена.
2026-09-19 22:03:46 +03:00
grendervill d6ca8e420f fix(gateway): имена событий и resumable-флаг по спецификации §8.3
- ROLE_CREATE/ROLE_UPDATE/ROLE_DELETE вместо GUILD_ROLE_*;
- выдача/снятие роли приходит как MEMBER_UPDATE с полным списком role_ids:
  клиент пересчитывает доступные действия сразу после события;
- промах RESUME отдаёт INVALID_SESSION с {"resumable": true} и следом полный
  READY — сессия сохраняется, повторный вход не нужен;
- тесты обновлены под новый контракт.
2026-09-19 21:57:46 +03:00
grendervill b189b3592b feat(api): список событий безопасности аккаунта
GET /users/@me/security-events отдаёт последние входы, неудачные попытки,
смены пароля и изменения 2FA (AGENT.md 7.1: уведомления о новых входах —
основа для настроек безопасности в клиенте).
2026-09-19 21:55:52 +03:00
grendervill bf0d130ee8 feat(cli): включение 2FA инстанс-админа командой totp-setup
2FA обязательна для инстанс-администраторов, но до её включения вход закрыт —
получался замкнутый круг. Добавлены команды обслуживания:

- `glchat totp-setup --email <admin>`: создаёт секрет, подтверждает его кодом,
  печатает секрет, otpauth-ссылку и 8 резервных кодов (каждый одноразовый);
- `glchat totp-reset --email <admin>`: удаляет секрет при потере устройства;
- код `auth.2fa_enrollment_required` с подсказкой, какую команду выполнить;
- установщик: флаг `--admin-2fa` для автоматического включения (по умолчанию
  печатает подсказку, чтобы секреты не оседали в логах установки).

Проверено сквозным прогоном локально: bootstrap → 403 на входе без 2FA →
totp-setup → вход с TOTP-кодом → профиль, серверы, комнаты, роли, участники,
аудит, создание сервера админом, 429 на шестой попытке входа, секретов в логах
нет.
2026-09-19 21:53:35 +03:00
grendervill c059cd9f51 feat(api): лимиты частоты запросов (AGENT.md 8.6)
- httpx.RateLimiter: token bucket в памяти с уборкой неактивных ключей и
  подменяемым источником времени;
- login/register — 5 запросов в минуту на IP, весь API — 120 в минуту на
  пользователя (по хэшу токена сессии) или на IP для анонимных запросов;
- превышение отдаёт 429 с Retry-After и retry_after_ms в конверте ошибки;
- тесты: наполнение и пополнение ведра, независимость ключей, 429 на ручке
  входа.
2026-09-19 21:51:47 +03:00
grendervill 6d7062dc2f test(api): сценарии §11.5 и §11.6 — админ инстанса и realtime-события
- администратор инстанса вне сервера видит и меняет чужой сервер: комнаты,
  роли, участники, аудит; его действия помечены actor_instance_admin;
- администратора инстанса нельзя исключить или замутить с сервера
  (instance.admin_protected) — права глобальны и не отзываются владельцем;
- realtime-проверка: два WS-клиента (владелец и участник) получают события
  CHANNEL_CREATE, GUILD_UPDATE, GUILD_ROLE_CREATE, MEMBER_ROLES_UPDATE от
  REST-ручек, а USER_UPDATE приходит только владельцу сессии.
2026-09-19 21:50:54 +03:00
grendervill 1b1a679827 feat(api): ручки Фаз(ы) 1 — профиль, серверы, роли, админ инстанса
REST-слой Фазы 1 на huma (OpenAPI 3.1 генерируется из кода):

- профиль: GET/PATCH /users/@me, смена пароля со step-up, публичный профиль,
  завершение онбординга (новая миграция 00003 с onboarding_completed_at);
- серверы: создание/изменение/удаление, join/leave, список серверов
  пользователя, журнал действий;
- комнаты: список с учётом прав, создание/изменение/удаление;
- участники: список с профилями и ролями, никнейм, тайм-аут, исключение;
- роли: CRUD, выдача/снятие с проверкой иерархии и запретом выдачи прав выше
  собственных;
- админ инстанса: публичная информация, настройки, серверы, пользователи,
  аудит, выдача прав администратора со step-up; обход лимитов фиксируется в
  аудите отдельной записью limits.bypass;
- движок прав: участие в сервере стало обязательным условием (IsMember),
  не участник не получает прав роли @user; калькулятор прав общий для API и
  Gateway, инвалидация кэша после изменений;
- Gateway: браузерный клиент аутентифицируется cookie на рукопожатии, IDENTIFY
  без токена использует её; события GUILD/CHANNEL/MEMBER/ROLE рассылаются из
  ручек, USER_UPDATE — адресно;
- ошибки huma отдаются в едином конверте {"error":{"code","message"}}.

Тесты: 8 сценариев API (профиль, жизненный цикл сервера и права, лимиты и
обход админом, иерархия ролей, тайм-аут, скрытие комнаты оверрайдом,
членство в движке прав, cookie-идентификация Gateway).
2026-09-19 21:50:06 +03:00
grendervill 86dff94a02 feat(gateway): WebSocket Gateway с HELLO, IDENTIFY и READY
Реализован Gateway по AGENT.md §8.3:

- оп-коды 0/1/2/3/4/7/10/11, единый конверт {op,t,d,s};
- HELLO с интервалом сердцебиения (45 с) и идентификатором сессии;
- IDENTIFY/RESUME с проверкой токена через auth.ResolveSession;
- READY: пользователь, серверы, видимые комнаты, роли, участники и права,
  собранные через движок прав (VIEW_CHANNEL-фильтрация);
- HEARTBEAT → HEARTBEAT_ACK, лимит частоты IDENTIFY (5 с);
- RESUME: буфер на 1000 событий с TTL 5 минут, догон пропущенных событий и
  подтверждение RESUMED, полный READY при расхождении состояния;
- рассылка Dispatch/SendToUser с буферизацией для отключённых сессий;
- проверка Origin по allowlist доменов инстанса (AGENT.md §9.7);
- httpx.statusRecorder пробрасывает Hijack, иначе апгрейд ломается.

Тесты: 6 сценариев Gateway (READY, скрытые комнаты, неверный токен,
heartbeat, dispatch, RESUME) и 2 теста маршрута /gateway через полный стек
middleware, включая отклонение чужого Origin.
2026-09-19 21:42:41 +03:00
grendervill c0537d3c17 feat(bootstrap): первичная настройка инстанса и CLI-команды
- internal/bootstrap: создание инстанс-админа, главного сервера, ролей
  «Администратор» и «Пользователь» (all permissions / дефолтный набор §6.3),
  общей текстовой комнаты; повторный запуск идемпотентен
- админ получает флаг is_instance_admin и системный бейдж instance_admin
- CLI: glchat bootstrap-admin, reset-password, make-admin, remove-admin
  (работают внутри контейнера через DATA_DIR)
- auth: UserByEmail, SetPassword (отзывает сессии), RegisterAdmin (в обход
  флага регистрации — bootstrap выполняет установщик)
- тесты: создание админа и главного сервера, идемпотентность, автовступление
  нового пользователя с ролью @user
2026-09-19 21:37:13 +03:00
grendervill f61751ed26 feat(api): REST на chi + huma с auth-ручками и OpenAPI 3.1
- переход на chi + huma (решение D-006): huma отдаёт типизированные ручки и
  генерирует документ, auth-ручки живут на chi (cookie и заголовки напрямую)
- единый формат ошибок: код (auth.invalid_credentials, auth.2fa_required,
  perm.denied и т.д.) + человекочитаемое сообщение (AGENT.md 8.5)
- cookie сессии __Host-session: HttpOnly, SameSite=Lax, Secure при TLS;
  альтернатива — Bearer-токен для desktop/CLI (AGENT.md 8.1)
- ручки: register, login, logout, logout-all, sessions, step-up, 2fa/setup,
  2fa/enable, users/@me; IP и User-Agent прокидываются из запроса в контекст
- /api/v1/openapi.json: объединённый документ (схемы huma + контракт auth)
- тесты: регистрация через API с cookie, ошибки входа, обязательная сессия,
  валидация, наличие всех путей в OpenAPI
2026-09-19 21:36:00 +03:00
grendervill 16218ee045 feat(auth): сервис аккаунтов и аутентификации Фазы 1
- регистрация: политика пароля (≥10 + локальный словарь 10k утечек, SecLists MIT),
  username и email без учёта регистра, шифрование email с привязкой к аккаунту,
  blind index для поиска, автовступление в главный сервер с ролью @user
- вход: Argon2id+pepper, TOTP при включённом 2FA, блокировка после серии неудач,
  события безопасности; единый ответ на неверный пароль и неизвестный аккаунт
- сессии: opaque-токены (в БД только SHA-256), ротация токена, logout/logout-all,
  отзыв остальных сессий при смене пароля, step-up с окном 10 минут
- 2FA: настройка секрета с QR-URL, подтверждение кодом, 8 резервных кодов
  (хранятся хэшами), обязательность для инстанс-админов, запрет отключения
- тесты на реальной SQLite: регистрация/вход/дубликаты, слабые и утёкшие пароли,
  ротация и logout-all, TOTP и резервные коды, step-up, смена пароля,
  выключенная регистрация, отсутствие секретов в логах
2026-09-19 21:29:07 +03:00
grendervill a5205cc054 feat(store,permissions): схема Фазы 1, доступ к данным и движок прав
- миграция 00002: users, sessions, totp_secrets, webauthn_credentials,
  security_events, guilds, guild_members, roles, member_roles, channels,
  channel_overrides, audit_log + дефолтные instance_settings (AGENT.md 6.1)
- internal/store: Snowflake-идентификаторы, CRUD пользователей и сессий
  (ротация, step-up, logout-all), TOTP и события безопасности, серверы,
  участники, роли, комнаты и оверрайды, настройки инстанса и аудит
- internal/permissions: 37 прав битмаской, вычисление по правилам §6.2
  (баз role @user → оверрайды ролей → оверрайд пользователя → ADMINISTRATOR),
  иерархия ролей и участников, тайм-ауты, обход для инстанс-админа, LRU-кэш
  с инвалидацией
- internal/source: адаптер permissions.Source поверх store
- тесты: 18 unit-тестов прав + интеграционный набор на реальной SQLite
  (приватная комната, модератор, владелец, инстанс-админ, тайм-аут)
- golangci: обоснованное исключение gosec для пакета store (конверсии
  Snowflake и сборка SQL из константных шаблонов)
2026-09-19 21:24:17 +03:00
grendervill 06e313dcc6 feat(crypto): крипто-слой Фазы 1 — Argon2id+pepper, AES-256-GCM, blind index
- internal/crypto/password.go: Argon2id с параметрами под 1 vCPU (OWASP),
  обязательный pepper (HMAC-SHA-256 до Argon2id), разбор формата хэша,
  NeedsRehash для калибровки параметров
- internal/crypto/secretbox.go: AES-256-GCM с версионированным ключом (v1:),
  разделение мастер-ключа на подключи шифрования и blind index (HMAC-SHA-256),
  blind index для поиска по email без расшифровки (§9.2)
- internal/crypto/tokens.go: opaque-токены сессий (хранится только SHA-256),
  резервные коды 2FA, нормализация email, политика пароля и username
- тесты: соль, pepper, версии ключа, чужие шифротексты, стабильность blind index
- go.mod: добавлены chi v5, huma v2, pquerna/otp (подключаются следующими шагами)
2026-09-19 21:17:52 +03:00
grendervill 3d1816a640 feat(config): схема URL в meta зависит от TLS_ENABLED
- при установке без TLS (--skip-tls, стенд без домена) клиент получает
  http:// и ws:// вместо неработающих https:// и wss://
- установщик пишет TLS_ENABLED в .env; тесты фиксируют обе схемы
2026-09-19 19:58:32 +03:00
grendervill b13f9a7479 test(httpx): тесты middleware, конверта ошибок и определения IP клиента 2026-09-19 19:36:07 +03:00
grendervill 2bf7101f2d chore(lint): настроить golangci-lint v2, gofumpt и форматирование в make
- .golangci.yml: набор линтеров под требования §14 (gosec, gocritic, sloglint,
  noctx, revive, depguard для запрета циклических зависимостей internal/*)
- gofumpt вынесен из formatters golangci-lint v2.13 (сообщал о расхождениях,
  которые сам же не устранял) и применяется отдельным инструментом
- make fmt/go-lint: проверка gofumpt + golangci-lint fmt --diff
- scripts/gitleaks-scan.sh: поддержка gitleaks 8.19+ (dir) и старых версий (detect)
- исправлены замечания линтеров: обработка ошибок Close, контекст в логах,
  NewRequestWithContext в тестах, комментарий к blank-import драйвера SQLite
2026-09-19 18:25:20 +03:00
grendervill 69aa98e18c feat(server): скелет Go-сервера с healthz, readyz и api/v1/meta
- internal/config: конфигурация из env с валидацией и дефолтами (§5.1, §9.1)
- internal/httpx: middleware (request id, slog-логи, recover, security-заголовки),
  единый формат ошибок §8.5 и лимит тела запроса
- internal/database: SQLite WAL + busy_timeout + foreign_keys, один writer
  и пул чтения, goose-миграции embedded, PRAGMA optimize по расписанию
- internal/meta: полезная нагрузка /api/v1/meta (версия, фичи, лимиты)
- internal/server: маршруты, SPA-раздача собранного клиента, OpenAPI 3.1
- cmd/glchat: точка входа с graceful shutdown и JSON-логами
- тесты: конфиг, миграции и идемпотентность, HTTP-эндпоинты, статика, 404/405
2026-09-19 18:20:56 +03:00