Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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=
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion components/auth-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
42 changes: 39 additions & 3 deletions lib/supabase/initialize-admin-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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.
Expand Down Expand Up @@ -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();
createAdminUserIfNotExists();
1 change: 1 addition & 0 deletions types/environment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ namespace NodeJS {

// Misc
ADMIN_EMAIL: string
ADMIN_PASSWORD: string
}
}