/** * Provision agency admin accounts (role=admin, tenant=4Ventures Central). * npx tsx scripts/provision_agency_admins.ts * * Writes credentials to .ops/agency-admins-credentials.txt (gitignored). */ import { config } from 'dotenv'; import { resolve } from 'path'; import { writeFileSync, mkdirSync } from 'fs'; import { createClient } from '@supabase/supabase-js'; import { randomBytes } from 'crypto'; config({ path: resolve(process.cwd(), '.env.local') }); const HUB_TENANT = '0b3c5fc0-6901-4caf-b26a-b6a0201d8541'; const COMPANY = '4Ventures'; const SLUG = '4ventures-hub'; const EMAILS = [ 'marcos@4ventures.es', 'belen@4ventures.es', 'rebecca@4ventures.es', 'victor@4ventures.es', ] as const; function tempPassword(local: string): string { const tag = local.split('@')[0].replace(/[^a-z]/gi, '').slice(0, 8) || 'User'; const cap = tag.charAt(0).toUpperCase() + tag.slice(1).toLowerCase(); return `4V${cap}-${randomBytes(4).toString('hex')}!9`; } async function findUser( admin: ReturnType, email: string ) { for (let page = 1; page <= 40; page++) { const { data, error } = await admin.auth.admin.listUsers({ page, perPage: 200, }); if (error) throw error; const hit = data.users.find( (u) => (u.email || '').toLowerCase() === email.toLowerCase() ); if (hit) return hit; if (data.users.length < 200) break; } return null; } async function main() { const url = process.env.NEXT_PUBLIC_SUPABASE_URL?.trim(); const key = process.env.SUPABASE_SERVICE_ROLE_KEY?.trim(); if (!url || !key) { throw new Error( 'Faltan NEXT_PUBLIC_SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY en .env.local' ); } const admin = createClient(url, key, { auth: { persistSession: false, autoRefreshToken: false }, }); const lines: string[] = [ '# Agency admin access — app.4ventures.es/login', `# generated ${new Date().toISOString()}`, `# tenant ${COMPANY} (${HUB_TENANT}) role=admin`, '', ]; for (const email of EMAILS) { const password = tempPassword(email); const app_metadata = { role: 'admin', tenant_id: HUB_TENANT, company: COMPANY, slug: SLUG, }; const user_metadata = { role: 'admin', tenant_id: HUB_TENANT, company: COMPANY, slug: SLUG, }; const found = await findUser(admin, email); if (found) { const { error } = await admin.auth.admin.updateUserById(found.id, { password, email_confirm: true, app_metadata: { ...(found.app_metadata || {}), ...app_metadata }, user_metadata: { ...(found.user_metadata || {}), ...user_metadata }, }); if (error) throw new Error(`${email}: ${error.message}`); console.log('updated', email, found.id); lines.push(`email=${email}`); lines.push(`password=${password}`); lines.push(`user_id=${found.id}`); lines.push(`action=updated`); lines.push(''); } else { const { data, error } = await admin.auth.admin.createUser({ email, password, email_confirm: true, app_metadata, user_metadata, }); if (error) throw new Error(`${email}: ${error.message}`); console.log('created', email, data.user?.id); lines.push(`email=${email}`); lines.push(`password=${password}`); lines.push(`user_id=${data.user?.id || ''}`); lines.push(`action=created`); lines.push(''); } } mkdirSync(resolve(process.cwd(), '.ops'), { recursive: true }); const out = resolve(process.cwd(), '.ops/agency-admins-credentials.txt'); writeFileSync(out, lines.join('\n'), { mode: 0o600 }); console.log('---'); console.log('credentials', out); console.log('login https://app.4ventures.es/login'); for (const email of EMAILS) { const pass = lines.find( (l, i) => lines[i - 1] === `email=${email}` && l.startsWith('password=') ); console.log(email, pass?.replace('password=', '') || '?'); } } main().catch((e) => { console.error(e); process.exit(1); });