§01
Обзор
- Что это: усиленная по безопасности админ-панель для двух брендов из единой кодовой базы — брендинг и базовый API выбираются в рантайме через env
TENANT. Управляет каталогом (продукты, модификаторы, категории, меню), правилами продаж и скидок, локациями (города/организации/терминалы), маркетингом (Telegram-рассылки, шаблоны, SMS, новости, слайдеры) и системными настройками (роли, языки, статусы заказов, push-события). Фронтенд — это обёртка над существующим Laravel API (авторизация по OTP на номер телефона,/api/v2/*). - Тип / статус / роль: web-app · active (свежий, активная разработка) · solo (Davron Yuldashev).
- Окно активности: 2026-05-20 → 2026-05-21. Очень молодой: bootstrap-скаффолд + один большой feat-коммит (237 файлов, +27 468 строк) в первый день, затем security-фикс на следующий.
§02
Стек
- Языки: TypeScript (strict +
noUncheckedIndexedAccess). - Фреймворки/библиотеки: Next.js 16 (App Router, Turbopack), React 19.2, better-auth 1.6 (адаптер Drizzle, хранилище сессий на SQLite), drizzle-orm + better-sqlite3, TanStack Query v5 + TanStack Table v8, next-safe-action v8 (типобезопасные server actions), react-hook-form + резолверы zod v4, nuqs (состояние в URL), shadcn/ui (new-york) + Tailwind v4 (CSS-first, без
tailwind.config.js), lucide-react, sonner, @t3-oss/env-nextjs (валидация env), hashids, input-otp, react-google-recaptcha-v3. - Инфра/деплой: Bun (lockfile
bun.lock, скриптыbun dev/build), ESLint flat config +eslint-plugin-security. CI/хостинг в репозитории не зафиксированы.@upstash/ratelimit+@upstash/redisподключены, но помечены как заглушка (rate limiting ещё не активен). - Данные: SQLite (
local.db) — только для сессий/MFA/audit better-auth; бизнес-данные живут в Laravel. Миграции через Drizzle-kit. - Заметный тулинг:
bun smoke— кастомный smoke-тест Laravel-клиента через--conditions react-server. «Забаненные зависимости» из README:react-quill(XSS),next-auth(заменён на better-auth),axios(только нативный fetch).
§03
Что реализовано
Таймлайн (git log, 3 коммита):
- Initial bootstrap — скаффолд Next 16 + shadcn + better-auth (61 файл, +5154). Автор
bootstrap@admin-v2-build.local(автоматический инструмент скаффолдинга). - `dfc8884` feat: auth + 22 dashboard pages + broadcasts parity — (2026-05-20, Davron Yuldashev). Основной массив: 237 файлов, +27 468 / −181. Именно здесь построено всё ядро: OTP-логин через Laravel, TOTP MFA, типобезопасный Laravel-клиент с zod-схемами по ~30 ресурсам, 22+ CRUD-страницы дашборда и фича broadcasts (Telegram-рассылки) с полным жизненным циклом.
- `28d31e3` fix(proxy): close cookie-presence auth bypass — (2026-05-21, Davron Yuldashev). Security-фикс по итогам самоаудита (finding C1): подделанная сессионная кука с мусорным payload возвращала 200 на защищённых маршрутах, потому что middleware «пропускал» запросы при
getCookieCache → null. Исправлено: кука присутствует + парсинг провалился ⇒ редирект на /login + удаление обеих кук. - Объём: 3 коммита за 2 дня; рывок «от нуля до продукта» за один заход, затем точечная работа по безопасности. Заброшенным не выглядит — свежий, активный.
§04
Технические челленджи
Всё подтверждено по диффам/файлам:
- Edge-RBAC без обращения к БД в middleware (
proxy.ts): роли проверяются прямо в Edge-рантайме чтением подписанного кэша куки better-auth (getCookieCache, HMAC надBETTER_AUTH_SECRET) — без обращения к SQLite. Реализовано: генерация CSP-nonce на каждый запрос, абсолютный лимит сессии в 12 часов (session.createdAt + 12h), admin-only префиксы (/users,/configs,/admin), нормализацияrolesкак из массива, так и из JSON-строки. → Демонстрирует понимание ограничений Edge-рантайма и инженерии авторизации. - Закрытие auth bypass (коммит
28d31e3): классическая ошибка «кука есть ⇒ пропускаем», найденная самостоятельным STRIDE-аудитом и исправленная; проверки RBAC и лимита сессии вынесены из веткиif (cache)после раннего return, посколькуcacheтеперь гарантированно non-null. → Настоящий security-майндсет, а не галочка. - TOTP MFA с шифрованием секрета at-rest (
lib/auth/mfa.ts): генерирует 160-битный base32-секрет (RFC 6238), шифрует его через AES-GCM с помощьюsymmetricEncrypt(ключMFA_ENCRYPTION_KEY⇒ fallbackBETTER_AUTH_SECRET), выдаётotpauth://URI, верифицирует с окном ±1 (терпимо к сдвигу ±30 с), строго нормализует код. → Знание криптографии и стандартов TOTP. - Brute-force lockout на уровне БД (
lib/auth/lockout.ts): 5 неудачных попыток TOTP за 15 минут ⇒ блокировка на 15 минут; состояние хранится в строкеuser(mfa_failed_attempts/mfa_locked_until), а не в памяти — переживает рестарты и консистентно между воркерами. Атомарный инкремент черезsql\${col} + 1\`с.returning()`. Публичные ошибки намеренно обобщённые. → Rate limiting production-уровня. - Типобезопасный Laravel-клиент (
lib/api/laravel.ts, 405 строк): единая точка входаlaravelFetch— централизует структурированное JSON-логирование (никогда не логирует токен/тело), проброс bearer, тенантный заголовокX-Project-Code, авто-Idempotency-Key(UUID) на всех мутациях, объединение AbortSignal + таймаут 15 с, маппинг статуса в типизированную ошибку (LaravelUnauthorized/Forbidden/Validation/...) и опциональную zod-валидацию ответа. Ленивый динамический импорт стека сессий, чтобы вызовыskipAuthне подгружали БД. → Отличная архитектура интеграционного слоя. - Особенности интеграции с legacy-Laravel (
lib/auth/laravel.ts,lib/api/decode.ts): странный OTP-контракт воспроизведён «как есть» (/api/keldi→ base64 CSRF,/api/send_otpс заголовкомwtf= recaptcha,/api/auth_otp→ base64 JSON или буквальный"false"). Хелперы декодируют дважды закодированные base64-конверты. → Умение приручить чужой нестандартный API, не меняя его. - Фабрика server actions (
lib/forms/serverAction.ts):defineCrudAction({input, method, path, output, revalidate})кодифицирует повторяющийся CRUD-паттерн поверх next-safe-action; ошибки Laravel 422 транслируются обратно в ошибки полей формы черезreturnValidationErrors;handleServerErrorсворачивает любые не-Laravel ошибки в обобщённую строку (без утечки стектрейсов).pathможет быть функцией от провалидированного входа. → DRY + типобезопасность как системное решение. - Обход для multipart (
broadcasts/actions.ts): посколькуlaravelFetchработает только с JSON, загрузка изображений в рассылках идёт прямымfetchсFormData, намеренно без установкиContent-Type(чтобы fetch сам выставил multipart-boundary), при этом переиспользуя те же заголовки auth/tenant. Валидация типа/размера на клиенте (≤10 МБ). Жизненный цикл рассылки (schedule/pause/resume/cancel/cancel-and-delete/reset-to-draft/duplicate) + живая статистика из Redis через polling.
§05
AI-разработка
- Найдено сессий: 0 — директории
~/.claude/projects/*admin_v2*нет. - Что делалось с AI: оба пользовательских коммита несут
Co-Authored-By: Claude Opus 4.7, то есть AI использовался при разработке, но локальные логи сессий Claude Code для этого репозитория не сохранились (возможно, запуск шёл из другого пути/инструмента). Сам bootstrap-коммит — это автоматический агент скаффолдинга (bootstrap@admin-v2-build.local), на что также намекаетAGENTS.mdс правилами для агентов под Next 16. - Паттерны AI-воркфлоу: агентная настройка видна косвенно (AGENTS.md, упоминание «auth-builder finalizes schema» в README, отсылки к «security audit 2026-05-21» с кодами findings) — структурированная работа с AI-агентами и самостоятельным code review/аудитом. Прямых следов нет.
§06
Достижения и метрики
- Масштаб: 22+ страницы дашборда (broadcasts, broadcast-templates, catalog, categories, cities, configs, delivery-pricings, events-system, langs, menu-items, menu-types, mobile-push-events, modifiers, news, order-statuses, organizations, products, roles, sales, sales-rules, sliders, sms-templates, terminals).
- ~30 zod-схем ресурсов в
lib/api/schemas/**(catalog/sales/locations/people/marketing/settings). - +27 468 строк в главном feat-коммите за один день.
- Безопасность: строгий CSP с инфраструктурой nonce на каждый запрос, полный набор security-заголовков (HSTS preload, COOP/CORP, X-Frame DENY, Permissions-Policy), STRIDE threat model в ARCHITECTURE.md (§6), append-only audit log, ключи идемпотентности на мутациях.
- Мультитенантность: единая кодовая база → 2 бренда (Restaurant chain (UZ)/Restaurant chain (UZ)) через env
TENANT.