import { clerkClient } from '@clerk/express'; import { logger } from './logger'; /** * Owner-configured bootstrap runs independently in each Clerk environment. * The completion marker prevents a restart from restoring a revoked role. * Passwords are read only from Secrets and never logged or reset on restart. */ export async function bootstrapAdmin() { const email = process.env.ADMIN_EMAIL?.trim().toLowerCase(); if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { throw new Error('Set a valid ADMIN_EMAIL before starting admin authentication.'); } try { const client = clerkClient; const { data: users } = await client.users.getUserList({ emailAddress: [email], limit: 2 }); if (users.length > 1) throw new Error('Admin bootstrap found multiple matching accounts.'); const existing = users[0]; if (existing?.privateMetadata.ecomAdminBootstrapComplete) { logger.info('Admin account bootstrap already completed; existing permissions preserved.'); return; } if (existing) { const verified = existing.emailAddresses.some(address => address.emailAddress.toLowerCase() === email && address.verification?.status === 'verified', ); if (!verified) throw new Error('Verify ownership of the configured admin email before granting access.'); await client.users.updateUserMetadata(existing.id, { publicMetadata: { ...existing.publicMetadata, role: 'admin' }, privateMetadata: { ...existing.privateMetadata, ecomAdminBootstrapComplete: true }, }); } else { const password = process.env.ADMIN_INITIAL_PASSWORD; if (!password) throw new Error('Set ADMIN_INITIAL_PASSWORD in Secrets to create the initial admin account.'); await client.users.createUser({ emailAddress: [email], password, firstName: 'Admin', publicMetadata: { role: 'admin' }, privateMetadata: { ecomAdminBootstrapComplete: true }, }); } logger.info('Owner-configured admin account is ready.'); } catch (error) { // Do not log SDK request/response objects: they can contain credentials. if (error && typeof error === 'object' && 'errors' in error) { const errors = error.errors as { code?: string }[]; const codes = errors.map(item => item.code?.replace(/[^a-zA-Z0-9_]/g, '')).filter(Boolean).join(', '); throw new Error(`Admin account setup was rejected by the login provider (${codes || 'request_failed'}). Check the admin settings and use a strong ADMIN_INITIAL_PASSWORD.`); } throw error; } }