A secure Next.js 15 admin dashboard for managing single-use VIP codes for the Skatehive project.
- Generate Batch VIP Codes: Create up to 500 codes at once with optional expiry dates, reserved usernames, and notes
- Secure Secret Hashing: Uses Argon2id with pepper for hashing VIP code secrets
- Code Management: List, revoke, and delete unused VIP codes
- Usage Tracking: View usage timeline for each code from
vip_code_usestable - CSV Export: Export the visible code list to CSV
- Basic Auth Protection: All admin routes protected with HTTP Basic Authentication
Create a .env file in the root directory with the following variables:
# Supabase Configuration
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# VIP Code Security
VIP_PEPPER=your-long-random-string-for-hashing
# Admin Authentication
ADMIN_USER=admin
ADMIN_PASS=your-strong-admin-passwordSUPABASE_SERVICE_ROLE_KEY: Service role key from Supabase (has full database access, never expose to client)VIP_PEPPER: A long random string used as additional salt for Argon2id hashing (keep secret)ADMIN_PASS: Strong password for Basic Auth (minimum 16 characters recommended)
# Install dependencies
pnpm install
# Or using npm
npm install
# Or using yarn
yarn install# Run development server
pnpm dev
# Or
npm run devOpen http://localhost:3000 in your browser.
- Home page:
http://localhost:3000 - Admin dashboard:
http://localhost:3000/admin
# Build for production
pnpm build
# Start production server
pnpm start- Push your code to GitHub
- Import the repository in Vercel
- Add all environment variables in the Vercel project settings:
SUPABASE_URLSUPABASE_SERVICE_ROLE_KEYVIP_PEPPERADMIN_USERADMIN_PASS
- Deploy
Ensure all environment variables are set in your hosting platform's configuration.
The application expects these Supabase tables:
id(uuid, primary key)code_id(text, unique)secret_hash(text)expires_at(timestamptz, nullable)reserved_username(text, nullable)created_by(text, nullable)notes(text, nullable)created_at(timestamptz, default now())consumed_at(timestamptz, nullable)consumed_email(text, nullable)
id(uuid, primary key)vip_code_id(uuid, foreign key to vip_codes)email(text, nullable)username(text, nullable)hive_account(text, nullable)ip(inet, nullable)user_agent(text, nullable)status(use_status enum)error(text, nullable)created_at(timestamptz, default now())
All API routes are protected by Basic Auth middleware.
Generate batch VIP codes.
Request Body:
{
"count": 5,
"expires_at": "2025-12-31T23:59:59Z",
"reserved_username": "skater123",
"created_by": "admin",
"notes": "Batch for event"
}Response:
{
"codes": [
{
"id": "uuid",
"code_id": "ABC123",
"display_code": "ABC123-XYZ789AB",
"expires_at": "2025-12-31T23:59:59Z",
"reserved_username": "skater123"
}
]
}Get latest 1000 VIP codes.
Response:
{
"codes": [
{
"id": "uuid",
"code_id": "ABC123",
"expires_at": "2025-12-31T23:59:59Z",
"reserved_username": "skater123",
"created_by": "admin",
"notes": "Batch for event",
"created_at": "2025-10-26T12:00:00Z",
"consumed_at": null,
"consumed_email": null
}
]
}Revoke an unused VIP code (sets expires_at to now).
Request Body:
{
"id": "uuid"
}Delete an unused VIP code.
Request Body:
{
"id": "uuid"
}Get usage timeline for a VIP code.
Response:
{
"uses": [
{
"id": "uuid",
"email": "[email protected]",
"username": "skater123",
"hive_account": "@skater123",
"ip": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"status": "success",
"error": null,
"created_at": "2025-10-26T12:00:00Z"
}
]
}- Generate Codes: Create 5 codes with an expiry date and reserved username
- Copy Display Codes: Use the "Copy" buttons to copy generated display codes
- Revoke Code: Click "Revoke" on an unused code (sets expires_at to current time)
- Delete Code: Click "Delete" on an unused code (removes from database)
- View Uses: Click "Uses" to see the usage timeline for a code
- Export CSV: Click "Export CSV" to download all visible codes
- Server-Only Database Access: Supabase Service Role key only used in Route Handlers
- Basic Auth Protection: All
/adminand/api/adminroutes protected by middleware - Argon2id Hashing: Secrets hashed with strong memory cost (64 MB) and pepper
- No Secret Exposure: Display codes only returned on generation, never logged
- Consumed Code Protection: Revoke and delete operations only work on unused codes
- Framework: Next.js 15 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS
- Database Client: @supabase/supabase-js
- Hashing: argon2 (Argon2id algorithm)
- Authentication: HTTP Basic Auth via middleware
MIT