spomeni/docs/adminImplem.md
dimitar 8c6390d6ac
Some checks failed
CI / build (push) Has been cancelled
super admin prod fix
2026-08-03 18:10:19 +02:00

9.4 KiB
Raw Blame History

SuperAdmin & Admin + Code Access System — Implementation Plan

⚠ Historical implementation plan — see docs/admin.md for current state

This was the original plan doc. Several specifics differ from the actual implementation (src/lib/admin-session.ts, src/app/api/admin/*, src/app/admin/**):

  • Cookie path: planned as /admin; shipped as / (so admin API routes under /api/admin/* receive the cookie).
  • Super-admin credentials: planned as hardcoded super/admin; shipped from env (SUPER_ADMIN_USERNAME + SUPER_ADMIN_PASSWORD_HASH, bcrypt) — see docs/admin.md.
  • Super-admin row in AdminUser: planned as optional seed; required by the Code.createdById FK and provisioned by prisma/seed.cjs (run by scripts/start.sh on boot).
  • Rate limiting, CSRF/Origin checks, password complexity, atomic code claim (updateMany guard): all added in Phases 16 and not in the original plan.
  • All admin API routes now funnel through requireAdmin / requireAdminPost / requireSuperAdminPost in src/lib/admin-session.ts; the rolling-helpers in admin-session.ts supersede the inline getAdminSession() checks described per-route in this top-of-the-doc walkthrough.

Overview

Implement SuperAdmin/admin role management and code-gated memorial creation as outlined in admin.md.

  • SuperAdmin: hardcoded username super, password admin
  • SuperAdmin creates admin accounts
  • Admins generate access codes
  • Users must enter a valid code during onboarding to create memories

Phase 1 — Database & Dependencies

New Prisma Models (prisma/schema.prisma)

enum Role {
  SUPER_ADMIN
  ADMIN
}

model AdminUser {
  id           String   @id @default(cuid())
  username     String   @unique
  passwordHash String
  role         Role     @default(ADMIN)
  createdAt    DateTime @default(now())
  createdCodes Code[]
}

model Code {
  id           String    @id @default(cuid())
  code         String    @unique
  createdById  String
  createdBy    AdminUser @relation(fields: [createdById], references: [id])
  usedByUserId String?                            // User.clerkId
  usedAt       DateTime?
  createdAt    DateTime  @default(now())
  expiresAt    DateTime?

  @@index([code])
  @@index([usedByUserId])
}

New Dependencies

  • bcryptjs + @types/bcryptjs — hash admin passwords

Seed SuperAdmin

Seed script or migration that creates the SuperAdmin AdminUser record with hashed password. However, the login itself checks hardcoded super/admin first (and also queries DB by role for token auth), so the seed is optional — used mainly for listing in the admin panel.

Migration

npx prisma migrate dev --name add_admin_and_code


Phase 2 — Admin Auth

Admin auth is separate from Clerk. Uses a signed HMAC cookie.

src/lib/admin-session.ts (new)

Helpers:

  • createAdminSession(username: string, role: Role): string — sign a cookie value with HMAC-SHA256 using ADMIN_SESSION_SECRET env var
  • verifyAdminSession(token: string): { username: string; role: Role } | null — verify and decode
  • getAdminSession(): { username: string; role: Role } | null — read from request.cookies or cookies()
  • Cookie name: admin_session

Payload: { username, role, iat } serialized + HMAC signature.

src/app/api/admin/login/route.ts (new)

  • POST: accept { username, password }
  • If username === "super" and password === "admin" → set session with role SUPER_ADMIN
  • Else query AdminUser where username === username, compare with bcrypt.compare
  • Return { success: true } and set admin_session cookie (httpOnly, secure, sameSite=lax, path=/admin)
  • On failure: 401

src/app/api/admin/logout/route.ts (new)

  • POST: clear admin_session cookie

src/app/api/admin/change-password/route.ts (new)

  • POST: accept { currentPassword, newPassword }
  • Verify admin session, then verify current password against DB
  • Hash new password, update AdminUser record

src/app/admin/login/page.tsx (new)

  • Macedonian UI: username/password form
  • On submit → POST /api/admin/login
  • On success → redirect to /admin/dashboard
  • Show error on failure

src/middleware.ts (modify)

  • Add /admin(.*) and /api/admin(.*) to the Clerk exclude list
  • Before Clerk middleware runs: if path starts with /admin, check admin session cookie
    • No/invalid cookie → redirect to /admin/login
    • Valid → allow
  • If path starts with /api/admin, check admin session cookie
    • No/invalid cookie → return 401

Phase 3 — Admin Panel

All UI in Macedonian. Layout with sidebar navigation.

src/app/admin/layout.tsx (new)

  • Checks admin session (server component)
  • Redirects to /admin/login if not authenticated
  • Provides sidebar with links: Dashboard, Users (SuperAdmin only), Codes
  • Logout button

src/app/admin/dashboard/page.tsx (new)

  • Stats cards:
    • Total AdminUsers count
    • Total Codes generated
    • Codes used vs unused
  • Simple overview

src/app/admin/users/page.tsx (new)

  • Accessible only to SUPER_ADMIN
  • Table of admin users (username, role, created at)
  • Button to create new admin (modal/page with username + password fields)
  • Button to delete admin (with confirmation)
  • Inline password reset option

src/app/admin/users/create/page.tsx or modal (new)

  • Form: username, password (with confirmation)
  • POST to /api/admin/users/

src/app/admin/codes/page.tsx (new)

  • "Generate Code" button → POST /api/admin/codes → returns new code string, displays it
  • Table of all codes: code value, who created it, status (used/unused), used by, used at
  • Admin can see their own codes. SuperAdmin sees all.
  • Delete code button

src/app/api/admin/users/route.ts (new)

  • GET: list all AdminUser (SuperAdmin only)
  • POST: create AdminUser with hashed password (SuperAdmin only)

src/app/api/admin/users/[id]/route.ts (new)

  • DELETE: remove AdminUser (SuperAdmin only)
  • PUT: reset password (SuperAdmin only)

src/app/api/admin/codes/route.ts (new)

  • GET: list codes (Admins see their own, SuperAdmin sees all)
  • POST: generate a new code (crypto.randomBytes(6).toString('hex').toUpperCase() → 12 chars), store with createdById from session

src/app/api/admin/codes/[id]/route.ts (new)

  • DELETE: remove unused code

Phase 4 — Code Gating

src/app/api/validate-code/route.ts (new)

  • Requires Clerk auth (auth() from @clerk/nextjs/server)
  • POST: { code: string }
  • Look up Code where code === code and usedByUserId === null
  • If not found → { valid: false, error: "Невалиден или веќе искористен код" }
  • If found → update usedByUserId = userId, usedAt = now()
  • Return { valid: true }

src/app/onboarding/page.tsx (modify)

  • Add "Код" as step 0 (before "Податоци")
  • STEPS becomes: ["Код", "Податоци", "Датуми", "Фотографии", "Поддомен", "Шаблон"]
  • Step 0: single input for code + "Потврди" button
    • Calls POST /api/validate-code
    • On success → shows green checkmark, enables "Продолжи"
    • On failure → shows error, blocks progression
  • Once validated, user can proceed to step 1
  • canProceed() for step 0 returns true only if code validated successfully

src/app/api/publish/route.ts (modify)

  • After Clerk auth check, query: prisma.code.findFirst({ where: { usedByUserId: userId } })
  • If no code found → return 403 with "Потребен е валиден код за креирање спомен страница"

Phase 5 — Environment & Build

New Env Variable

ADMIN_SESSION_SECRET=<random-64-char-string>

Add to .env.example and docker-compose files.

Build & Verify

npm install bcryptjs
npm install -D @types/bcryptjs
npx prisma migrate dev --name add_admin_and_code
npm run build

Verify:

  • Admin login at /admin/login with super/admin
  • Create admin user from admin panel
  • Login as created admin
  • Generate code from admin panel
  • Sign up as new user via Clerk
  • Enter code in onboarding step 0
  • Proceed through onboarding and publish
  • Verify publish fails without valid code

File Change Summary

New Files

Path Purpose
src/lib/admin-session.ts HMAC cookie helpers
src/app/admin/login/page.tsx Admin login form
src/app/admin/layout.tsx Admin layout with sidebar
src/app/admin/dashboard/page.tsx Dashboard stats
src/app/admin/users/page.tsx Admin user management
src/app/admin/codes/page.tsx Code generation & listing
src/app/api/admin/login/route.ts Admin login API
src/app/api/admin/logout/route.ts Admin logout API
src/app/api/admin/change-password/route.ts Self-service password change
src/app/api/admin/users/route.ts List/create admin users
src/app/api/admin/users/[id]/route.ts Delete/reset-password admin user
src/app/api/admin/codes/route.ts List/create codes
src/app/api/admin/codes/[id]/route.ts Delete code
src/app/api/validate-code/route.ts Public code validation

Modified Files

Path Change
prisma/schema.prisma Add AdminUser and Code models
src/middleware.ts Exclude /admin/* from Clerk, add admin session check
src/app/onboarding/page.tsx Add code step (step 0)
src/app/api/publish/route.ts Add code usage check before publish