diff --git a/.env.example b/.env.example index a73c123..33b7f27 100644 --- a/.env.example +++ b/.env.example @@ -10,4 +10,5 @@ CIRCLE_BLOCKCHAIN=ARC-TESTNET CIRCLE_USDC_TOKEN_ID=15dc2b5d-0994-58b0-bf8c-3a0501148ee8 # Misc -ADMIN_EMAIL=admin@admin.com +ADMIN_EMAIL=admin@example.com +ADMIN_PASSWORD= diff --git a/README.md b/README.md index 50ca285..b8ef71c 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,8 @@ CIRCLE_BLOCKCHAIN=ARC-TESTNET CIRCLE_USDC_TOKEN_ID= # Misc -ADMIN_EMAIL=admin@admin.com +ADMIN_EMAIL=admin@example.com +ADMIN_PASSWORD=your-strong-password-here ``` | Variable | Scope | Purpose | @@ -125,19 +126,19 @@ ADMIN_EMAIL=admin@admin.com | `CIRCLE_ENTITY_SECRET` | Server-side | Circle entity secret for wallet operations. | | `CIRCLE_BLOCKCHAIN` | Server-side | Blockchain network identifier (e.g., "ARC-TESTNET"). | | `CIRCLE_USDC_TOKEN_ID` | Server-side | USDC token ID for the specified blockchain. Pre-filled for ARC-TESTNET. | -| `ADMIN_EMAIL` | Server-side | Admin user email address. | +| `ADMIN_EMAIL` | Server-side | Admin user email for first-run bootstrap. | +| `ADMIN_PASSWORD` | Server-side | Admin password for first-run bootstrap (required; never use defaults in production). | ## User Accounts ### Admin Account -On first startup, an admin user is automatically created with the following credentials: - -- **Email:** `admin@admin.com` -- **Password:** `123456` +On first startup, an admin user is created from `ADMIN_EMAIL` and `ADMIN_PASSWORD` in `.env.local`. **Set a strong, unique password before deploying publicly.** The admin account has access to the **Admin Dashboard**, which provides an overview of all users, wallets, and transactions in the system. +> **Note:** Database triggers that exclude the admin from credits still reference `admin@admin.com`. Use that email locally unless you update the SQL migrations. + Regular users who sign up will see the **User Dashboard**, which allows them to purchase credits with USDC and view their own transaction history. ### Signup Rate Limits diff --git a/components/auth-button.tsx b/components/auth-button.tsx index 6b25f5c..abbda50 100644 --- a/components/auth-button.tsx +++ b/components/auth-button.tsx @@ -31,7 +31,7 @@ export async function AuthButton() { if (user) { // Check if the logged-in user is the admin. - const isAdmin = user.email === 'admin@admin.com'; + const isAdmin = user.email === process.env.ADMIN_EMAIL; // Only fetch credits if the user is NOT the admin. let initialCredits = 0; diff --git a/lib/supabase/initialize-admin-user.ts b/lib/supabase/initialize-admin-user.ts index 95c59c6..c4c9096 100644 --- a/lib/supabase/initialize-admin-user.ts +++ b/lib/supabase/initialize-admin-user.ts @@ -24,6 +24,9 @@ import { SupabaseClient } from "@supabase/supabase-js"; const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; const supabaseServiceRoleKey = process.env.SUPABASE_SECRET_KEY; +const DOCUMENTED_DEFAULT_EMAIL = "admin@admin.com"; +const BANNED_PASSWORDS = new Set(["123456", "password", "admin"]); + // A server-side-only, admin client for Supabase. let adminAuthClient: SupabaseClient | null = null; @@ -40,13 +43,46 @@ if (supabaseUrl && supabaseServiceRoleKey) { ); } +function resolveAdminCredentials(): + | { email: string; password: string } + | { error: string } { + const email = process.env.ADMIN_EMAIL?.trim(); + const password = process.env.ADMIN_PASSWORD?.trim(); + + if (!email || !password) { + return { + error: + "ADMIN_EMAIL and ADMIN_PASSWORD must be set to bootstrap the admin account.", + }; + } + + if (process.env.NODE_ENV === "production") { + if ( + email === DOCUMENTED_DEFAULT_EMAIL || + BANNED_PASSWORDS.has(password) + ) { + return { + error: + "Refusing to bootstrap admin with documented default credentials in production.", + }; + } + } + + return { email, password }; +} + const createAdminUserIfNotExists = async () => { if (!adminAuthClient) { return; } - const adminEmail = "admin@admin.com"; - const adminPassword = "123456"; + const credentials = resolveAdminCredentials(); + if ("error" in credentials) { + console.warn(`Admin user initialization skipped: ${credentials.error}`); + return; + } + + const { email: adminEmail, password: adminPassword } = credentials; // We call our custom database function via RPC (Remote Procedure Call). // This is a single, fast, and scalable database query. @@ -112,4 +148,4 @@ const createAdminUserIfNotExists = async () => { // This is the key: we call the function immediately. // When this file is imported, this function will run. -createAdminUserIfNotExists(); \ No newline at end of file +createAdminUserIfNotExists(); diff --git a/types/environment.d.ts b/types/environment.d.ts index ca95fb6..27c232e 100644 --- a/types/environment.d.ts +++ b/types/environment.d.ts @@ -31,5 +31,6 @@ namespace NodeJS { // Misc ADMIN_EMAIL: string + ADMIN_PASSWORD: string } }