From 74b3eb1eb124ab38dd910aa5674857ebe8bab60b Mon Sep 17 00:00:00 2001 From: devorun Date: Sun, 9 Aug 2026 22:41:26 +0300 Subject: [PATCH] fix(admin): require ADMIN_PASSWORD instead of a hardcoded default The admin user was auto-provisioned on startup with a hardcoded, publicly documented password ("123456"), so any exposed deployment could be taken over with a well-known credential. Read the admin password from ADMIN_PASSWORD instead of hardcoding it, skip admin creation when it is unset, and reject weak or common passwords. ADMIN_EMAIL now drives the address (still defaulting to admin@admin.com, which the database RLS policies key off). Update .env.example, the env type declaration, and the README so the default password is no longer published. Fixes #47 --- .env.example | 3 ++ README.md | 12 +++++--- lib/supabase/initialize-admin-user.ts | 42 +++++++++++++++++++++++++-- types/environment.d.ts | 1 + 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index a73c123..32fdc46 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,6 @@ CIRCLE_USDC_TOKEN_ID=15dc2b5d-0994-58b0-bf8c-3a0501148ee8 # Misc ADMIN_EMAIL=admin@admin.com +# Required to auto-provision the admin user. Must be strong (>= 8 chars, not a +# common password). Leave blank to skip automatic admin creation. +ADMIN_PASSWORD= diff --git a/README.md b/README.md index 50ca285..b74e99f 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ CIRCLE_USDC_TOKEN_ID= # Misc ADMIN_EMAIL=admin@admin.com +ADMIN_PASSWORD= ``` | Variable | Scope | Purpose | @@ -125,16 +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 address. Defaults to `admin@admin.com`. | +| `ADMIN_PASSWORD` | Server-side | Password for the auto-provisioned admin user. Required to create it; must be strong (>= 8 chars, not a common password). Leave blank to skip admin creation. | ## User Accounts ### Admin Account -On first startup, an admin user is automatically created with the following credentials: +On first startup, an admin user is automatically created **only if** `ADMIN_PASSWORD` is set to a strong value (see the environment table above): -- **Email:** `admin@admin.com` -- **Password:** `123456` +- **Email:** `ADMIN_EMAIL` (defaults to `admin@admin.com`) +- **Password:** the value of `ADMIN_PASSWORD` — choose a strong, unique password + +If `ADMIN_PASSWORD` is unset, or weak (shorter than 8 characters or a well-known password), admin creation is skipped and a message is logged — so a publicly reachable deployment never ships with a guessable default. The admin account has access to the **Admin Dashboard**, which provides an overview of all users, wallets, and transactions in the system. diff --git a/lib/supabase/initialize-admin-user.ts b/lib/supabase/initialize-admin-user.ts index 95c59c6..b6791bf 100644 --- a/lib/supabase/initialize-admin-user.ts +++ b/lib/supabase/initialize-admin-user.ts @@ -40,13 +40,51 @@ if (supabaseUrl && supabaseServiceRoleKey) { ); } +// Passwords that must never guard a privileged account, even if set explicitly. +const COMMON_WEAK_PASSWORDS = new Set([ + "123456", + "12345678", + "123456789", + "password", + "admin", + "admin123", + "qwerty", + "letmein", + "changeme", + "welcome", +]); + +const isWeakPassword = (password: string): boolean => + password.length < 8 || COMMON_WEAK_PASSWORDS.has(password.toLowerCase()); + const createAdminUserIfNotExists = async () => { if (!adminAuthClient) { return; } - const adminEmail = "admin@admin.com"; - const adminPassword = "123456"; + // Never provision the admin account with a hardcoded, publicly known + // password. Require the deployment to supply one explicitly via + // ADMIN_PASSWORD and reject obviously weak values, so an exposed instance + // cannot be taken over with a guessable default. ADMIN_EMAIL keeps its + // documented default because the database RLS policies key off that address. + const adminEmail = process.env.ADMIN_EMAIL ?? "admin@admin.com"; + const adminPassword = process.env.ADMIN_PASSWORD; + + if (!adminPassword) { + console.warn( + "ADMIN_PASSWORD is not set. Skipping admin user creation. " + + "Set a strong ADMIN_PASSWORD to enable automatic admin provisioning." + ); + return; + } + + if (isWeakPassword(adminPassword)) { + console.error( + "ADMIN_PASSWORD is too weak (must be at least 8 characters and not a " + + "well-known password). Skipping admin user creation." + ); + return; + } // We call our custom database function via RPC (Remote Procedure Call). // This is a single, fast, and scalable database query. diff --git a/types/environment.d.ts b/types/environment.d.ts index ca95fb6..a554188 100644 --- a/types/environment.d.ts +++ b/types/environment.d.ts @@ -31,5 +31,6 @@ namespace NodeJS { // Misc ADMIN_EMAIL: string + ADMIN_PASSWORD?: string } }