/** * MotorSignal Firebase Configuration and Isolation Guards * * Enforces strict environment separation between production (motoapp-d22e0) * and staging (motorsignal-staging), preventing staging environments from * accidentally initializing against production Firebase resources. */ export const PRODUCTION_FIREBASE_PROJECT_ID = 'motoapp-d22e0'; export const STAGING_FIREBASE_PROJECT_ID = 'motorsignal-staging'; export interface FirebaseClientOptions { apiKey: string; authDomain: string; projectId: string; storageBucket: string; messagingSenderId?: string; appId?: string; } /** * Determines whether the current execution context is a staging deployment. * Inspects standard environment variables and browser hostname. */ export function isStagingEnvironment(): boolean { // Check standard environment descriptors const motorsignalEnv = (process.env.MOTORSIGNAL_ENV || '').toLowerCase().trim(); const appEnv = (process.env.APP_ENV || process.env.NEXT_PUBLIC_APP_ENV || '').toLowerCase().trim(); // If explicitly flagged as production, it is never staging if (motorsignalEnv === 'production' || appEnv === 'production') { return false; } // Explicit staging flags if (motorsignalEnv === 'staging' || appEnv === 'staging') { return true; } if (process.env.STAGING === 'true' || process.env.NEXT_PUBLIC_STAGING === 'true') { return true; } const nodeEnv = (process.env.NODE_ENV || '').toLowerCase().trim(); if (nodeEnv === 'staging') { return true; } // Staging API base URL indicator (e.g. https://api-staging.motorsignal.app) const apiBaseUrl = (process.env.MOTORSIGNAL_API_BASE_URL || '').toLowerCase(); if (apiBaseUrl.includes('api-staging.motorsignal.app') || apiBaseUrl.includes('//staging.')) { return true; } // Staging site URL indicator (e.g. https://staging.motorsignal.app) const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL || '').toLowerCase(); if (siteUrl.includes('staging.motorsignal.app')) { return true; } // Browser hostname inspection if (typeof window !== 'undefined' && window.location?.hostname) { const hostname = window.location.hostname.toLowerCase(); if (hostname === 'staging.motorsignal.app') { return true; } } return false; } /** * Staging safety guard: strictly prevents any staging deployment from * initializing Firebase against the production project (motoapp-d22e0). */ export function assertStagingSafety(projectId: string): void { if (isStagingEnvironment() && projectId === PRODUCTION_FIREBASE_PROJECT_ID) { throw new Error( `Staging safety guard violation: Staging deployment cannot initialize Firebase against production project "${PRODUCTION_FIREBASE_PROJECT_ID}". ` + `Expected staging project "${STAGING_FIREBASE_PROJECT_ID}".` ); } } /** * Validates that server-side and browser-side Firebase project IDs agree. * Throws if both are provided but do not match. */ export function assertProjectIdsMatch(clientProjectId?: string, serverProjectId?: string): void { if (clientProjectId && serverProjectId && clientProjectId !== serverProjectId) { throw new Error( `Firebase project ID mismatch: Client project ID "${clientProjectId}" ` + `does not match server project ID "${serverProjectId}".` ); } } /** * Validates and retrieves the client-side Firebase configuration options * from environment variables. * * Throws if required configuration is missing or if the staging safety guard is violated. */ export function getFirebaseClientConfig(): FirebaseClientOptions { const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY; const authDomain = process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN; const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID; const storageBucket = process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET; const messagingSenderId = process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID; const appId = process.env.NEXT_PUBLIC_FIREBASE_APP_ID; if (!projectId || !apiKey) { throw new Error( 'Missing required Firebase client configuration: NEXT_PUBLIC_FIREBASE_PROJECT_ID ' + 'and NEXT_PUBLIC_FIREBASE_API_KEY must be set.' ); } // Enforce staging safety guard assertStagingSafety(projectId); // Enforce client/server project ID consistency if server env var is visible if (process.env.FIREBASE_PROJECT_ID) { assertProjectIdsMatch(projectId, process.env.FIREBASE_PROJECT_ID); } return { apiKey, authDomain: authDomain || `${projectId}.firebaseapp.com`, projectId, storageBucket: storageBucket || `${projectId}.firebasestorage.app`, ...(messagingSenderId ? { messagingSenderId } : {}), ...(appId ? { appId } : {}), }; } /** * Validates and retrieves the server-side Firebase project ID. * Requires explicit FIREBASE_PROJECT_ID; fails closed without falling back to production. */ export function getServerFirebaseProjectId(): string { const projectId = process.env.FIREBASE_PROJECT_ID; if (!projectId) { throw new Error('FIREBASE_PROJECT_ID is required for Firebase Admin SDK initialization.'); } // Enforce staging safety guard assertStagingSafety(projectId); // Enforce client/server project ID consistency if client env var is set if (process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID) { assertProjectIdsMatch(process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, projectId); } return projectId; }