diff --git a/README.md b/README.md index b9b5900..1b4aaa8 100644 --- a/README.md +++ b/README.md @@ -32,22 +32,14 @@ Apply database migrations: DATABASE_URL=file:local.db pnpm db:migrate ``` -Create an initial admin user: - -```sh -ADMIN_EMAIL=admin@example.com \ -ADMIN_PASSWORD='change-me' \ -ADMIN_NAME='Administrator' \ -DATABASE_URL=file:local.db \ -pnpm admin:create -``` - Start the development server: ```sh pnpm dev ``` +Open `http://localhost:5173/login`. If no Better Auth users exist yet, Clearity shows a first-admin setup form instead of the normal sign-in form. Submitting it creates the initial user with the default Better Auth `admin` role and signs you in. + Useful development commands: ```sh @@ -68,13 +60,6 @@ Required: - `ORIGIN`: Public app origin, for example `http://localhost:5173` locally or `https://clearity.example.com` in production. - `BETTER_AUTH_SECRET`: Secret used by Better Auth. -Admin bootstrap script: - -- `ADMIN_EMAIL`: Email address for the admin account. -- `ADMIN_PASSWORD`: Password for the admin account. -- `ADMIN_NAME`: Optional display name. Defaults to `Administrator`. -- `ADMIN_OVERWRITE`: Set to `1` to update an existing admin user. - ## Database The application schema lives in `src/lib/server/db`. Drizzle migration files live in `drizzle`. @@ -163,4 +148,4 @@ Preview the built Worker locally: pnpm preview ``` -Create the first production admin user by running the admin script against the production database connection before exposing the app to users. +Create the first production admin by opening `/login` after migrations have run. The setup form is only shown while the Better Auth `user` table is empty. diff --git a/package.json b/package.json index 3698f3c..3ac8b96 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "scripts": { "dev": "vite dev", "build": "wrangler types --check && vite build", - "preview": "wrangler dev .svelte-kit/cloudflare/_worker.js --port 4173", "prepare": "svelte-kit sync || echo ''", "check": "wrangler types --check && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", @@ -16,9 +15,7 @@ "db:push": "drizzle-kit push", "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", - "db:studio": "drizzle-kit studio", - "auth:schema": "better-auth generate --config src/lib/server/auth.ts --output src/lib/server/db/auth.schema.ts --yes", - "admin:create": "sh -c 'db=\"${DATABASE_URL:-$([ -d .wrangler/state/v3/d1 ] && find .wrangler/state/v3/d1 -name \"*.sqlite\" -print -quit)}\"; if [ -z \"$db\" ]; then echo \"No local Wrangler D1 SQLite database found. Start wrangler dev or run a local D1 command first.\" >&2; exit 1; fi; if [ -z \"$DATABASE_URL\" ]; then db=\"file:$db\"; fi; DATABASE_URL=\"$db\" node scripts/create-admin.mjs'" + "auth:schema": "better-auth generate --config src/lib/server/auth.ts --output src/lib/server/db/auth.schema.ts --yes" }, "devDependencies": { "@better-auth/cli": "~1.4.21", diff --git a/scripts/create-admin.mjs b/scripts/create-admin.mjs deleted file mode 100644 index 9752a4e..0000000 --- a/scripts/create-admin.mjs +++ /dev/null @@ -1,90 +0,0 @@ -import { createClient } from '@libsql/client'; -import { hashPassword } from 'better-auth/crypto'; -import { readFileSync } from 'node:fs'; -import { randomUUID } from 'node:crypto'; - -function readDotEnv() { - try { - const entries = readFileSync('.env', 'utf8') - .split('\n') - .map((line) => line.trim()) - .filter((line) => line && !line.startsWith('#')) - .map((line) => { - const index = line.indexOf('='); - const key = line.slice(0, index); - const value = line.slice(index + 1).replace(/^"|"$/g, ''); - return [key, value]; - }); - - for (const [key, value] of entries) { - process.env[key] ??= value; - } - } catch { - // .env is optional in deployed or scripted environments. - } -} - -readDotEnv(); - -const databaseUrl = process.env.DATABASE_URL; -const email = process.env.ADMIN_EMAIL?.trim().toLowerCase(); -const password = process.env.ADMIN_PASSWORD; -const name = process.env.ADMIN_NAME?.trim() || 'Administrator'; -const overwrite = process.env.ADMIN_OVERWRITE === '1'; - -if (!databaseUrl) { - console.error('DATABASE_URL is required.'); - process.exit(1); -} - -if (!email || !password) { - console.error('ADMIN_EMAIL and ADMIN_PASSWORD are required.'); - console.error('Example: ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD=change-me pnpm admin:create'); - process.exit(1); -} - -const client = createClient({ url: databaseUrl }); -const now = Date.now(); -const existing = await client.execute({ - sql: 'select id from user where email = ? limit 1', - args: [email] -}); - -if (existing.rows.length > 0 && !overwrite) { - console.error(`A user with email ${email} already exists. Set ADMIN_OVERWRITE=1 to update the password.`); - process.exit(1); -} - -const userId = existing.rows[0]?.id?.toString() ?? randomUUID(); -const passwordHash = await hashPassword(password); - -if (existing.rows.length === 0) { - await client.execute({ - sql: 'insert into user (id, name, email, email_verified, image, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?)', - args: [userId, name, email, 1, null, now, now] - }); -} else { - await client.execute({ - sql: 'update user set name = ?, email_verified = ?, updated_at = ? where id = ?', - args: [name, 1, now, userId] - }); -} - -const account = await client.execute({ - sql: 'select id from account where user_id = ? and provider_id = ? limit 1', - args: [userId, 'credential'] -}); - -if (account.rows.length === 0) { - await client.execute({ - sql: 'insert into account (id, account_id, provider_id, user_id, password, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?)', - args: [randomUUID(), userId, 'credential', userId, passwordHash, now, now] - }); -} else { - await client.execute({ - sql: 'update account set password = ?, updated_at = ? where id = ?', - args: [passwordHash, now, account.rows[0].id] - }); -} - -console.log(`Admin user ready: ${email}`); diff --git a/src/lib/components/first-admin-form.svelte b/src/lib/components/first-admin-form.svelte new file mode 100644 index 0000000..29f85f6 --- /dev/null +++ b/src/lib/components/first-admin-form.svelte @@ -0,0 +1,131 @@ + + +