#!/usr/bin/env node const fs = require('node:fs') const path = require('node:path') const { authenticator } = require('otplib') function parseEnvFile(content) { const env = {} for (const rawLine of content.split(/\r?\n/)) { const line = rawLine.trim() if (!line || line.startsWith('#')) continue const normalized = line.startsWith('export ') ? line.slice(7).trim() : line const separatorIndex = normalized.indexOf('=') if (separatorIndex === -1) continue const key = normalized.slice(0, separatorIndex).trim() if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue let value = normalized.slice(separatorIndex + 1).trim() if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { const quote = value[0] value = value.slice(1, -1) if (quote === '"') { value = value .replace(/\\n/g, '\n') .replace(/\\r/g, '\r') .replace(/\\t/g, '\t') .replace(/\\"/g, '"') .replace(/\\\\/g, '\\') } } env[key] = value } return env } function loadLocalEnv() { const envPath = path.resolve(process.cwd(), '.env.local') if (!fs.existsSync(envPath)) return {} return parseEnvFile(fs.readFileSync(envPath, 'utf8')) } function normalizeApiBase(value) { const base = (value || 'http://localhost:4000').replace(/\/$/, '') return base.endsWith('/api/v1') ? base : `${base}/api/v1` } function updateCookieJar(cookieJar, setCookieHeaders) { for (const header of setCookieHeaders) { const pair = header.split(';')[0] const separatorIndex = pair.indexOf('=') if (separatorIndex === -1) continue const name = pair.slice(0, separatorIndex) const value = pair.slice(separatorIndex + 1) if (value) cookieJar.set(name, value) else cookieJar.delete(name) } } function cookieHeader(cookieJar) { return Array.from(cookieJar.entries()) .map(([name, value]) => `${name}=${value}`) .join('; ') } async function request(apiBase, cookieJar, pathName, options = {}) { const headers = { ...(options.body ? { 'Content-Type': 'application/json' } : {}), ...(cookieJar.size ? { Cookie: cookieHeader(cookieJar) } : {}), ...options.headers, } const response = await fetch(`${apiBase}${pathName}`, { ...options, headers, }) updateCookieJar(cookieJar, response.headers.getSetCookie?.() ?? []) const text = await response.text() let json = null if (text) { try { json = JSON.parse(text) } catch { throw new Error(`Non-JSON response from ${pathName}: ${text.slice(0, 200)}`) } } return { response, json } } async function main() { const localEnv = loadLocalEnv() const env = { ...localEnv, ...process.env } const email = env.ADMIN_SEED_EMAIL || 'admin@rentaldrivego.ma' const password = env.ADMIN_SEED_PASSWORD const apiBase = normalizeApiBase(env.NEXT_PUBLIC_API_URL || env.API_URL) if (!password || /^(placeholder|changeme|change-me)$/i.test(password)) { throw new Error('Set ADMIN_SEED_PASSWORD in .env.local or in the command environment.') } const cookieJar = new Map() const login = await request(apiBase, cookieJar, '/admin/auth/login', { method: 'POST', body: JSON.stringify({ email, password }), }) if (!login.response.ok) { const error = login.json?.error || login.response.status if (error === 'totp_required') { throw new Error('This admin already has 2FA enabled. Use your authenticator or recovery code to sign in.') } throw new Error(login.json?.message || `Admin login failed: ${error}`) } const me = await request(apiBase, cookieJar, '/admin/auth/me') const admin = me.json?.data ?? me.json if (admin?.totpEnabled) { console.log(`Admin 2FA is already enabled for ${email}.`) return } const setup = await request(apiBase, cookieJar, '/admin/auth/2fa/setup', { method: 'POST' }) if (!setup.response.ok) { throw new Error(setup.json?.message || 'Failed to create admin 2FA setup secret.') } const secret = setup.json?.data?.secret ?? setup.json?.secret if (!secret) throw new Error('2FA setup response did not include a secret.') const code = authenticator.generate(secret) const verify = await request(apiBase, cookieJar, '/admin/auth/2fa/verify', { method: 'POST', body: JSON.stringify({ code }), }) if (!verify.response.ok) { throw new Error(verify.json?.message || 'Failed to verify generated 2FA code.') } const result = verify.json?.data ?? verify.json console.log(`Admin 2FA enabled for ${email}.`) console.log(`TOTP secret: ${secret}`) console.log('Add this secret to your authenticator app. The generated QR code data URL is available from /admin/auth/2fa/setup if you prefer scanning.') if (Array.isArray(result?.recoveryCodes) && result.recoveryCodes.length > 0) { console.log('\nRecovery codes:') for (const recoveryCode of result.recoveryCodes) console.log(`- ${recoveryCode}`) } } main().catch((error) => { console.error(error.message) process.exit(1) })