§01
Overview
- What it is: Hardened admin panel for two brands from a single codebase — branding and API base are picked at runtime via the
TENANTenv. Manages catalog (products, modifiers, categories, menu), sales and discount rules, locations (cities/organizations/terminals), marketing (Telegram broadcasts, templates, SMS, news, sliders), and system settings (roles, languages, order statuses, push events). The frontend is a wrapper over an existing Laravel API (OTP-by-phone auth,/api/v2/*). - Type / status / role: web-app · active (fresh, active development) · solo (Davron Yuldashev).
- Activity window: 2026-05-20 → 2026-05-21. Very young: bootstrap scaffold + one large feat commit (237 files, +27,468 lines) on day one, then a security fix the next day.
§02
Stack
- Languages: TypeScript (strict +
noUncheckedIndexedAccess). - Frameworks/libraries: Next.js 16 (App Router, Turbopack), React 19.2, better-auth 1.6 (Drizzle adapter, SQLite session store), drizzle-orm + better-sqlite3, TanStack Query v5 + TanStack Table v8, next-safe-action v8 (type-safe server actions), react-hook-form + zod v4 resolvers, nuqs (URL state), shadcn/ui (new-york) + Tailwind v4 (CSS-first, no
tailwind.config.js), lucide-react, sonner, @t3-oss/env-nextjs (env validation), hashids, input-otp, react-google-recaptcha-v3. - Infra/deploy: Bun (
bun.locklockfile,bun dev/buildscripts), ESLint flat config +eslint-plugin-security. CI/hosting not pinned in the repo.@upstash/ratelimit+@upstash/redisare wired in but flagged as a stub (rate limiting not yet active). - Data: SQLite (
local.db) — only for better-auth sessions/MFA/audit; business data lives in Laravel. Drizzle-kit for migrations. - Notable tooling:
bun smoke— a custom smoke test of the Laravel client via--conditions react-server. README "banned dependencies":react-quill(XSS),next-auth(replaced by better-auth),axios(native fetch only).
§03
What was shipped
Timeline (git log, 3 commits):
- Initial bootstrap — Next 16 + shadcn + better-auth scaffold (61 files, +5154). Author
bootstrap@admin-v2-build.local(automated scaffolding tool). - `dfc8884` feat: auth + 22 dashboard pages + broadcasts parity — (2026-05-20, Davron Yuldashev). The bulk: 237 files, +27,468 / −181. This is where the entire core is built: OTP login via Laravel, TOTP MFA, a type-safe Laravel client with zod schemas across ~30 resources, 22+ CRUD dashboard pages, and the broadcasts feature (Telegram broadcasts) with full lifecycle.
- `28d31e3` fix(proxy): close cookie-presence auth bypass — (2026-05-21, Davron Yuldashev). Security fix from a self-audit (finding C1): a forged session cookie with a junk payload was returning 200 on protected routes because middleware was "letting through" requests when
getCookieCache → null. Fixed: cookie present + parse failed ⇒ redirect to /login + delete both cookies. - Volume: 3 commits over 2 days; a "zero-to-product" burst in one go, then targeted security work. Doesn't look abandoned — fresh, active.
§04
Technical challenges
All confirmed against diffs/files:
- Edge-RBAC without DB in middleware (
proxy.ts): roles are checked directly in the Edge runtime by reading the signed better-auth cookie cache (getCookieCache, HMAC overBETTER_AUTH_SECRET) — no SQLite hit. Implemented: per-request CSP-nonce generation, absolute 12-hour session cap (session.createdAt + 12h), admin-gated prefixes (/users,/configs,/admin), normalizingrolesfrom both an array and a JSON string. → Demonstrates understanding of Edge runtime constraints and auth engineering. - Closing the auth bypass (commit
28d31e3): the classic "cookie present ⇒ let through" mistake, found by a self-driven STRIDE audit and fixed; RBAC and cap checks were moved out of theif (cache)branch after the early-return, sincecacheis now guaranteed non-null. → Real security mindset, not a checkbox. - TOTP MFA with at-rest secret encryption (
lib/auth/mfa.ts): generates a 160-bit base32 secret (RFC 6238), encrypts with AES-GCM viasymmetricEncrypt(keyMFA_ENCRYPTION_KEY⇒ fallbackBETTER_AUTH_SECRET),otpauth://URI, verification with a ±1 window (tolerant of ±30s skew), strict code normalization. → Knowledge of cryptography and TOTP standards. - DB-backed brute-force lockout (
lib/auth/lockout.ts): 5 failed TOTP attempts in 15 min ⇒ 15-min lock; state stored in theuserrow (mfa_failed_attempts/mfa_locked_until), not memory — survives restarts and is consistent across workers. Atomic increment viasql\${col} + 1\`with.returning()`. Public errors are intentionally generic. → Production-grade rate limiting. - Type-safe Laravel client (
lib/api/laravel.ts, 405 lines): a single entry pointlaravelFetch— centralizes structured JSON logging (never logs the token/body), bearer forwarding, the tenantX-Project-Codeheader, autoIdempotency-Key(UUID) on all mutations, AbortSignal + 15s timeout merging, status-to-typed-error mapping (LaravelUnauthorized/Forbidden/Validation/...), and optional zod response validation. Lazy dynamic-import of the session stack soskipAuthcalls don't load the DB. → Excellent integration-layer architecture. - Quirks of integrating with legacy Laravel (
lib/auth/laravel.ts,lib/api/decode.ts): the weird OTP contract is reproduced "as-is" (/api/keldi→ base64 CSRF,/api/send_otpwith awtfheader = recaptcha,/api/auth_otp→ base64 JSON or the literal"false"). Helpers decode double-encoded base64 envelopes. → The ability to tame someone else's non-standard API without changing it. - Server-action factory (
lib/forms/serverAction.ts):defineCrudAction({input, method, path, output, revalidate})codifies the repeating CRUD pattern over next-safe-action; Laravel 422 errors are translated back into form field errors viareturnValidationErrors;handleServerErrorcollapses any non-Laravel errors into a generic string (no leaked stack traces).pathcan be a function of the validated input. → DRY + type safety as a systemic solution. - Multipart workaround (
broadcasts/actions.ts): sincelaravelFetchis JSON-only, broadcast image uploads do a directfetchwithFormData, deliberately not settingContent-Type(so fetch sets the multipart boundary itself), while reusing the same auth/tenant headers. Client-side type/size validation (≤10 MB). Broadcast lifecycle (schedule/pause/resume/cancel/cancel-and-delete/reset-to-draft/duplicate) + live stats from Redis via polling.
§05
AI-assisted development
- Sessions found: 0 — there is no
~/.claude/projects/*admin_v2*directory. - What was done with AI: both user commits carry
Co-Authored-By: Claude Opus 4.7, so AI was used during development, but local Claude Code session logs for this repo weren't saved (possibly run from a different path/tool). The bootstrap commit itself is an automated scaffolding agent (bootstrap@admin-v2-build.local), which is also hinted at byAGENTS.mdcontaining agent rules for Next 16. - AI workflow patterns: the agentic setup is visible indirectly (AGENTS.md, the "auth-builder finalizes schema" mention in README, references to a "security audit 2026-05-21" with finding codes) — structured work with AI agents and self-driven code review/audit. No direct traces.
§06
Achievements & metrics
- Scale: 22+ dashboard pages (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 resource schemas in
lib/api/schemas/**(catalog/sales/locations/people/marketing/settings). - +27,468 lines in the main feat commit in a single day.
- Security: strict CSP with per-request nonce infrastructure, the full set of security headers (HSTS preload, COOP/CORP, X-Frame DENY, Permissions-Policy), STRIDE threat model in ARCHITECTURE.md (§6), append-only audit log, idempotency keys on mutations.
- Multi-tenancy: a single codebase → 2 brands (Restaurant chain (UZ)/Restaurant chain (UZ)) via the
TENANTenv.