-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathfirebaseInit.js
More file actions
128 lines (112 loc) · 5.02 KB
/
firebaseInit.js
File metadata and controls
128 lines (112 loc) · 5.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
const { SSMClient, GetParameterCommand } = require('@aws-sdk/client-ssm');
const admin = require('firebase-admin');
// Load configuration (Lambda package has no ./lib/config — only load from disk outside Lambda)
let config = {};
try {
if (process.env.VCMAIL_CONFIG) {
config = JSON.parse(process.env.VCMAIL_CONFIG);
} else if (process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.LAMBDA_TASK_ROOT) {
config = {
ssmPrefix: process.env.SSM_PREFIX || '/vcmail/prod',
awsRegion: process.env.AWS_REGION || 'us-east-1'
};
} else {
const { loadConfig } = require('./lib/config');
config = loadConfig(process.cwd());
}
} catch (error) {
console.warn('Could not load VCMail config in firebaseInit, using defaults:', error.message);
config = {
ssmPrefix: process.env.SSM_PREFIX || '/vcmail/prod',
awsRegion: process.env.AWS_REGION || 'us-east-1'
};
}
const awsRegion = config.awsRegion || process.env.AWS_REGION || 'us-east-1';
class FirebaseInitializer {
constructor() {
this.ssm = new SSMClient({ region: awsRegion });
this.firebaseAppMap = new Map();
}
async get(databaseURL, ssmPrefixOverride = null) {
try {
// Use provided ssmPrefix or fall back to config/environment
const ssmPrefix = ssmPrefixOverride || config.ssmPrefix || process.env.SSM_PREFIX || '/vcmail/prod';
// Cache key includes both databaseURL and ssmPrefix to support multiple domains
const cacheKey = `${databaseURL}:${ssmPrefix}`;
// Return existing instance if already initialized
if (this.firebaseAppMap.has(cacheKey)) {
return this.firebaseAppMap.get(cacheKey);
}
const paramName = `${ssmPrefix}/firebase_service_account`;
const params = {
Name: paramName,
WithDecryption: true
};
console.log(`Attempting to read SSM parameter: ${paramName}`);
console.log(`SSM Prefix from config: ${config.ssmPrefix}`);
console.log(`SSM Prefix from env: ${process.env.SSM_PREFIX}`);
let result;
try {
result = await this.ssm.send(new GetParameterCommand(params));
console.log(`Successfully retrieved SSM parameter: ${paramName}`);
} catch (ssmError) {
console.error(`Failed to get SSM parameter ${paramName}:`, ssmError);
console.error(`SSM Error Code: ${ssmError.name || ssmError.code}`);
console.error(`SSM Error Message: ${ssmError.message}`);
console.error(`SSM Error Stack: ${ssmError.stack}`);
throw new Error(`Failed to retrieve Firebase service account from SSM parameter ${paramName}: ${ssmError.name || ssmError.code} - ${ssmError.message}`);
}
if (!result?.Parameter?.Value) {
throw new Error('Firebase service account credentials not found in SSM');
}
let serviceAccount;
let paramValue = result.Parameter.Value.trim();
// Try multiple parsing strategies
// Strategy 1: Direct JSON parse (if stored as plain JSON)
try {
const parsed = JSON.parse(paramValue);
// If result is a string, it might be a JSON-encoded base64 string
if (typeof parsed === 'string') {
// Try base64 decoding the string
try {
const decoded = Buffer.from(parsed, 'base64').toString('utf-8');
serviceAccount = JSON.parse(decoded);
} catch (e) {
// Not base64, try parsing the string as JSON again (double-encoded JSON)
try {
serviceAccount = JSON.parse(parsed);
} catch (e2) {
throw new Error('Service account parameter contains a JSON string, not a JSON object');
}
}
} else {
// It's already a JSON object
serviceAccount = parsed;
}
} catch (directParseError) {
// Strategy 2: Try base64 decoding first, then JSON parse
try {
const decoded = Buffer.from(paramValue, 'base64').toString('utf-8');
serviceAccount = JSON.parse(decoded);
} catch (base64Error) {
// All strategies failed
console.error('Failed to parse Firebase service account JSON.');
console.error('Value preview (first 100 chars):', paramValue.substring(0, 100));
console.error('Direct parse error:', directParseError.message);
console.error('Base64 decode error:', base64Error.message);
throw new Error(`Invalid JSON in Firebase service account parameter. The parameter value appears to be incorrectly formatted. Expected valid JSON object or base64-encoded JSON. Original error: ${directParseError.message}`);
}
}
const firebaseApp = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL
}, cacheKey); // Use cacheKey as app name to avoid conflicts
this.firebaseAppMap.set(cacheKey, firebaseApp);
return this.firebaseAppMap.get(cacheKey);
} catch (error) {
console.error('Failed to initialize Firebase:', error);
throw error;
}
}
}
module.exports = new FirebaseInitializer();