Skip to content

Commit 4306eb7

Browse files
Updated firebase provider to use google-services.json instead of .env variable (gh deploy.yml creates the file from base64 encoded value stored on repo).
1 parent 946f955 commit 4306eb7

File tree

1 file changed

+33
-95
lines changed

1 file changed

+33
-95
lines changed
Lines changed: 33 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,54 @@
11
import { Provider } from '@nestjs/common';
22
import admin from 'firebase-admin';
33
import fs from 'fs';
4-
import { ConfigService } from '../common/configs/config.service';
4+
import path from 'path';
55

66
export const FIREBASE_ADMIN = 'FIREBASE_ADMIN';
77

88
/**
99
* Firebase Admin provider.
1010
*
1111
* Initialization strategy:
12-
* 1. Prefer FIREBASE_SERVICE_ACCOUNT_KEY (stringified JSON) from env or ConfigService
13-
* 2. Fallback to FIREBASE_SERVICE_ACCOUNT_PATH (path to JSON file)
14-
* 3. If neither present, do not initialize and return the admin namespace (attempts to use it will fail with a clear log)
12+
* 1. Only use google-services.json from project root. If missing or invalid, throw error and fail startup.
1513
*/
1614
export const FirebaseProvider: Provider = {
1715
provide: FIREBASE_ADMIN,
18-
useFactory: (configService: ConfigService) => {
19-
// Prefer process.env values. If not present, attempt to read from ConfigService.getConfigs()
20-
let envKey: string | undefined = process.env.FIREBASE_SERVICE_ACCOUNT_KEY;
21-
let envPath: string | undefined = process.env.FIREBASE_SERVICE_ACCOUNT_PATH;
22-
23-
if ((!envKey || !envPath) && configService) {
24-
const cfg = (configService as any).getConfigs?.() as Record<string, any> | undefined;
25-
if (cfg) {
26-
if (!envKey && typeof cfg.FIREBASE_SERVICE_ACCOUNT_KEY === 'string') {
27-
envKey = cfg.FIREBASE_SERVICE_ACCOUNT_KEY;
28-
}
29-
if (!envPath && typeof cfg.FIREBASE_SERVICE_ACCOUNT_PATH === 'string') {
30-
envPath = cfg.FIREBASE_SERVICE_ACCOUNT_PATH;
31-
}
32-
}
16+
useFactory: () => {
17+
const serviceAccountPath = path.resolve(process.cwd(), 'google-services.json');
18+
if (!fs.existsSync(serviceAccountPath)) {
19+
console.error('[FirebaseProvider] google-services.json not found in project root.');
20+
throw new Error('google-services.json not found in project root');
3321
}
3422

35-
if (!admin.apps.length) {
36-
let credentialObj: any = null;
37-
38-
const tryParse = (raw: string | undefined): any | null => {
39-
if (!raw) return null;
40-
let s = raw.trim();
41-
// Strip surrounding single/double quotes
42-
if ((s.startsWith("'") && s.endsWith("'")) || (s.startsWith('"') && s.endsWith('"'))) {
43-
s = s.slice(1, -1);
44-
}
45-
// Unescape newline sequences often present when JSON is stored in env vars
46-
s = s.replace(/\\n/g, '\n');
47-
48-
// If it looks like JSON, try parsing directly
49-
if (s.startsWith('{') || s.startsWith('[')) {
50-
try {
51-
return JSON.parse(s);
52-
} catch (err) {
53-
// continue to other attempts
54-
}
55-
}
56-
57-
// Try base64 decode (common when storing secrets in environment variables)
58-
try {
59-
const decoded = Buffer.from(s, 'base64').toString('utf8');
60-
if (decoded && (decoded.trim().startsWith('{') || decoded.trim().startsWith('['))) {
61-
try {
62-
return JSON.parse(decoded);
63-
} catch (err) {
64-
// fallthrough
65-
}
66-
}
67-
} catch (_) {
68-
// not base64 or failed decode
69-
}
70-
71-
return null;
72-
};
73-
74-
if (envKey) {
75-
const parsed = tryParse(envKey);
76-
if (parsed) {
77-
credentialObj = parsed;
78-
} else {
79-
console.error('[FirebaseProvider] FIREBASE_SERVICE_ACCOUNT_KEY is not valid JSON or base64-encoded JSON. Sample (truncated):',
80-
String(envKey).slice(0, 200) + (String(envKey).length > 200 ? '...[truncated]' : '')
81-
);
82-
}
83-
} else if (envPath) {
84-
try {
85-
const file = fs.readFileSync(String(envPath), 'utf8');
86-
const parsed = tryParse(file) ?? tryParse(file.replace(/\\n/g, '\n'));
87-
if (parsed) {
88-
credentialObj = parsed;
89-
} else {
90-
console.error('[FirebaseProvider] Service account file content is not valid JSON:', String(envPath));
91-
}
92-
} catch (err) {
93-
console.error('[FirebaseProvider] Failed to read/parse service account at path:', String(envPath), err);
94-
}
95-
}
96-
97-
if (credentialObj) {
98-
try {
99-
admin.initializeApp({
100-
credential: admin.credential.cert(credentialObj),
101-
});
102-
console.log('[FirebaseProvider] Initialized Firebase Admin SDK');
103-
} catch (err) {
104-
console.error('[FirebaseProvider] Failed to initialize Firebase Admin:', err);
105-
}
106-
} else {
107-
console.warn(
108-
'[FirebaseProvider] No Firebase service account provided (FIREBASE_SERVICE_ACCOUNT_KEY or FIREBASE_SERVICE_ACCOUNT_PATH). Firebase Admin not initialized. Calls to firebase services will fail.'
23+
try {
24+
const file = fs.readFileSync(serviceAccountPath, 'utf8');
25+
const parsed = JSON.parse(file);
26+
// Basic validation: service account JSON should contain a "type": "service_account"
27+
// or at least private_key and client_email fields required by firebase-admin.
28+
const looksLikeServiceAccount =
29+
parsed && (parsed.type === 'service_account' || (parsed.private_key && parsed.client_email));
30+
if (!looksLikeServiceAccount) {
31+
console.error(
32+
'[FirebaseProvider] google-services.json does not appear to be a Firebase service account:',
33+
serviceAccountPath
10934
);
35+
throw new Error('Invalid google-services.json: ' + serviceAccountPath);
11036
}
37+
if (!admin.apps.length) {
38+
admin.initializeApp({
39+
credential: admin.credential.cert(parsed),
40+
});
41+
console.log('[FirebaseProvider] Initialized Firebase Admin SDK using google-services.json');
42+
}
43+
} catch (err) {
44+
console.error(
45+
'[FirebaseProvider] Failed to read/initialize Firebase Admin from google-services.json:',
46+
serviceAccountPath,
47+
err
48+
);
49+
throw err;
11150
}
112-
11351
return admin;
11452
},
115-
inject: [ConfigService],
53+
inject: [],
11654
};

0 commit comments

Comments
 (0)