From 1c2a90b5209985f9e831a6f2e4e534ef6eb0ed86 Mon Sep 17 00:00:00 2001 From: Ryan Moon Date: Sun, 5 Apr 2026 10:27:34 -0500 Subject: [PATCH] feat: add Resend email + encryption key + initial user to provision flow - RESEND_API_KEY added to config (required env var) - Provision generates per-customer ENCRYPTION_KEY and patches lunarfront-secrets with resend-api-key, mail-from, business-name, encryption-key - initialEmail field in ProvisionSchema seeds first admin user via env vars on app first boot Co-Authored-By: Claude Sonnet 4.6 --- src/lib/config.ts | 1 + src/routes/customers.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/lib/config.ts b/src/lib/config.ts index fd5d4d4..12e5fd4 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -15,6 +15,7 @@ export const config = { cfApiToken: process.env.CF_API_TOKEN!, cfZoneId: process.env.CF_ZONE_ID!, ingressIp: process.env.INGRESS_IP ?? "167.99.21.170", + resendApiKey: process.env.RESEND_API_KEY!, }; for (const [key, val] of Object.entries(config)) { diff --git a/src/routes/customers.ts b/src/routes/customers.ts index a6f8dd4..3ed5939 100644 --- a/src/routes/customers.ts +++ b/src/routes/customers.ts @@ -24,6 +24,12 @@ const ProvisionSchema = z.object({ modules: z.array(z.enum(MODULES)).default([]), startDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).default(() => new Date().toISOString().slice(0, 10)), expirationDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().default(null), + initialEmail: z.object({ + email: z.string().email(), + password: z.string().min(12).max(128), + firstName: z.string().min(1).max(100), + lastName: z.string().min(1).max(100), + }).optional(), }); const ListQuerySchema = z.object({ @@ -128,12 +134,23 @@ export async function customerRoutes(app: FastifyInstance) { await setStep("namespace", "done"); await setStep("secrets", "done"); + const encryptionKey = crypto.randomBytes(32).toString("hex"); await patchSecret(namespace, "lunarfront-secrets", { "spaces-key": config.spacesKey, "spaces-secret": config.spacesSecret, "spaces-bucket": config.spacesBucket, "spaces-endpoint": `https://${config.spacesRegion}.digitaloceanspaces.com`, "spaces-prefix": `${slug}/`, + "encryption-key": encryptionKey, + "resend-api-key": config.resendApiKey, + "mail-from": `noreply@${slug}.lunarfront.tech`, + "business-name": body.name, + ...(body.initialEmail ? { + "initial-user-email": body.initialEmail.email, + "initial-user-password": body.initialEmail.password, + "initial-user-first-name": body.initialEmail.firstName, + "initial-user-last-name": body.initialEmail.lastName, + } : {}), }); await setStep("storage", "done");