"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createDefaultOrganization = exports.verifyDomain = exports.requestDomainVerification = exports.resolveDomain = void 0; const v2_1 = require("firebase-functions/v2"); const firestore_1 = require("firebase-admin/firestore"); const zod_1 = require("zod"); // Validation schemas const resolveRequestSchema = zod_1.z.object({ host: zod_1.z.string().min(1), }); const verificationRequestSchema = zod_1.z.object({ orgId: zod_1.z.string().min(1), host: zod_1.z.string().min(1), }); const verifyRequestSchema = zod_1.z.object({ orgId: zod_1.z.string().min(1), host: zod_1.z.string().min(1), }); // Default theme for new organizations const DEFAULT_THEME = { accent: '#F0C457', bgCanvas: '#2B2D2F', bgSurface: '#34373A', textPrimary: '#F1F3F5', textSecondary: '#C9D0D4', }; /** * Resolve organization by host domain * GET /api/domains/resolve?host=tickets.acme.com */ exports.resolveDomain = v2_1.https.onRequest({ cors: true, region: "us-central1", }, async (req, res) => { try { if (req.method !== 'GET') { res.status(405).json({ error: 'Method not allowed' }); return; } const { host } = resolveRequestSchema.parse(req.query); v2_1.logger.info(`Resolving domain for host: ${host}`); const db = (0, firestore_1.getFirestore)(); // First, try to find org by exact domain match const orgsSnapshot = await db.collection('organizations').get(); for (const doc of orgsSnapshot.docs) { const org = doc.data(); const matchingDomain = org.domains?.find(d => d.host === host && d.verified); if (matchingDomain) { v2_1.logger.info(`Found org by domain: ${org.id} for host: ${host}`); res.json({ orgId: org.id, name: org.name, branding: org.branding, domains: org.domains, }); return; } } // If no direct domain match, try subdomain pattern (e.g., acme.bct.dev) const subdomainMatch = host.match(/^([^.]+)\.bct\.dev$/); if (subdomainMatch) { const slug = subdomainMatch[1]; const orgBySlugSnapshot = await db.collection('organizations') .where('slug', '==', slug) .limit(1) .get(); if (!orgBySlugSnapshot.empty) { const org = orgBySlugSnapshot.docs[0].data(); v2_1.logger.info(`Found org by slug: ${org.id} for subdomain: ${slug}`); res.json({ orgId: org.id, name: org.name, branding: org.branding, domains: org.domains, }); return; } } // No organization found v2_1.logger.warn(`No organization found for host: ${host}`); res.status(404).json({ error: 'Organization not found', host, message: 'No organization is configured for this domain' }); } catch (error) { v2_1.logger.error('Error resolving domain:', error); if (error instanceof zod_1.z.ZodError) { res.status(400).json({ error: 'Invalid request', details: error.errors }); } else { res.status(500).json({ error: 'Internal server error', message: 'Failed to resolve domain' }); } } }); /** * Request domain verification * POST /api/domains/request-verification * Body: { orgId: string, host: string } */ exports.requestDomainVerification = v2_1.https.onRequest({ cors: true, region: "us-central1", }, async (req, res) => { try { if (req.method !== 'POST') { res.status(405).json({ error: 'Method not allowed' }); return; } const { orgId, host } = verificationRequestSchema.parse(req.body); v2_1.logger.info(`Requesting verification for ${host} on org ${orgId}`); const db = (0, firestore_1.getFirestore)(); const orgRef = db.collection('organizations').doc(orgId); const orgDoc = await orgRef.get(); if (!orgDoc.exists) { res.status(404).json({ error: 'Organization not found' }); return; } const org = orgDoc.data(); // Generate verification token const verificationToken = `bct-verify-${Date.now()}-${Math.random().toString(36).substring(2)}`; // Check if domain already exists const existingDomains = org.domains || []; const existingDomainIndex = existingDomains.findIndex(d => d.host === host); const newDomain = { host, verified: false, createdAt: new Date().toISOString(), verificationToken, }; let updatedDomains; if (existingDomainIndex >= 0) { // Update existing domain updatedDomains = [...existingDomains]; updatedDomains[existingDomainIndex] = newDomain; } else { // Add new domain updatedDomains = [...existingDomains, newDomain]; } await orgRef.update({ domains: updatedDomains }); v2_1.logger.info(`Generated verification token for ${host}: ${verificationToken}`); res.json({ success: true, host, verificationToken, instructions: { type: 'TXT', name: '_bct-verification', value: verificationToken, ttl: 300, description: `Add this TXT record to your DNS configuration for ${host}`, }, }); } catch (error) { v2_1.logger.error('Error requesting domain verification:', error); if (error instanceof zod_1.z.ZodError) { res.status(400).json({ error: 'Invalid request', details: error.errors }); } else { res.status(500).json({ error: 'Internal server error', message: 'Failed to request domain verification' }); } } }); /** * Verify domain ownership * POST /api/domains/verify * Body: { orgId: string, host: string } */ exports.verifyDomain = v2_1.https.onRequest({ cors: true, region: "us-central1", }, async (req, res) => { try { if (req.method !== 'POST') { res.status(405).json({ error: 'Method not allowed' }); return; } const { orgId, host } = verifyRequestSchema.parse(req.body); v2_1.logger.info(`Verifying domain ${host} for org ${orgId}`); const db = (0, firestore_1.getFirestore)(); const orgRef = db.collection('organizations').doc(orgId); const orgDoc = await orgRef.get(); if (!orgDoc.exists) { res.status(404).json({ error: 'Organization not found' }); return; } const org = orgDoc.data(); const domains = org.domains || []; const domainIndex = domains.findIndex(d => d.host === host); if (domainIndex === -1) { res.status(404).json({ error: 'Domain not found in organization' }); return; } const domain = domains[domainIndex]; if (!domain.verificationToken) { res.status(400).json({ error: 'No verification token found', message: 'Please request verification first' }); return; } // In development, we'll mock DNS verification // In production, you would use a real DNS lookup library const isDevelopment = process.env.NODE_ENV === 'development' || process.env.FUNCTIONS_EMULATOR === 'true'; let dnsVerified = false; if (isDevelopment) { // Mock verification - always succeed in development v2_1.logger.info(`Mock DNS verification for ${host} - always succeeds in development`); dnsVerified = true; } else { // TODO: Implement real DNS lookup // const dns = require('dns').promises; // const txtRecords = await dns.resolveTxt(`_bct-verification.${host}`); // dnsVerified = txtRecords.some(record => // record.join('') === domain.verificationToken // ); v2_1.logger.warn('Real DNS verification not implemented yet - mocking success'); dnsVerified = true; } if (dnsVerified) { // Update domain as verified const updatedDomains = [...domains]; updatedDomains[domainIndex] = { ...domain, verified: true, verifiedAt: new Date().toISOString(), }; await orgRef.update({ domains: updatedDomains }); v2_1.logger.info(`Successfully verified domain ${host} for org ${orgId}`); res.json({ success: true, host, verified: true, verifiedAt: updatedDomains[domainIndex].verifiedAt, message: 'Domain successfully verified', }); } else { v2_1.logger.warn(`DNS verification failed for ${host}`); res.status(400).json({ success: false, verified: false, error: 'DNS verification failed', message: `TXT record with value "${domain.verificationToken}" not found at _bct-verification.${host}`, }); } } catch (error) { v2_1.logger.error('Error verifying domain:', error); if (error instanceof zod_1.z.ZodError) { res.status(400).json({ error: 'Invalid request', details: error.errors }); } else { res.status(500).json({ error: 'Internal server error', message: 'Failed to verify domain' }); } } }); /** * Helper function to create a default organization * Used for seeding or testing */ const createDefaultOrganization = async (orgId, name, slug) => { const db = (0, firestore_1.getFirestore)(); const org = { id: orgId, name, slug, branding: { theme: DEFAULT_THEME, }, domains: [], }; await db.collection('organizations').doc(orgId).set(org); return org; }; exports.createDefaultOrganization = createDefaultOrganization; // # sourceMappingURL=domains.js.map