feat(config): load .env and log Firebase credential source at startup

Add dotenv via src/env.ts so dev/start read .env before Firebase init,
and log whether credentials come from FIREBASE_SERVICE_ACCOUNT_JSON or ADC.
This commit is contained in:
Jose Olarte III
2026-06-11 17:50:16 +08:00
parent 6261f1baa0
commit 7a2bb88207
5 changed files with 69 additions and 40 deletions

68
package-lock.json generated
View File

@@ -8,21 +8,23 @@
"name": "notification-wakeup-service", "name": "notification-wakeup-service",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@peculiar/asn1-ecc": "^2.3.8", "@peculiar/asn1-ecc": "^2.7.0",
"@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-schema": "^2.7.0",
"cbor-x": "^1.5.9", "cbor-x": "^1.6.4",
"cors": "^2.8.6", "cors": "^2.8.6",
"did-jwt": "^7.4.7", "did-jwt": "^7.4.7",
"did-resolver": "^4.1.0", "did-resolver": "^4.1.0",
"express": "^5.1.0", "dotenv": "^16.6.1",
"firebase-admin": "^13.9.0" "express": "^5.2.1",
"firebase-admin": "^13.10.0",
"tsx": "^4.22.3"
}, },
"devDependencies": { "devDependencies": {
"@types/cors": "^2.8.19", "@types/cors": "^2.8.19",
"@types/express": "^5.0.0", "@types/express": "^5.0.6",
"@types/node": "^22.10.0", "@types/node": "^22.19.19",
"tsx": "^4.19.2", "tsx": "^4.19.2",
"typescript": "^5.7.2" "typescript": "^5.9.3"
} }
}, },
"node_modules/@cbor-extract/cbor-extract-darwin-arm64": { "node_modules/@cbor-extract/cbor-extract-darwin-arm64": {
@@ -1146,9 +1148,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.19.18", "version": "22.19.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.18.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.21.tgz",
"integrity": "sha512-9v00a+dn2yWVsYDEunWC4g/TcRKVq3r8N5FuZp7u0SGrPvdN9c2yXI9bBuf5Fl0hNCb+QTIePTn5pJs2pwBOQQ==", "integrity": "sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
@@ -1631,6 +1633,18 @@
"integrity": "sha512-S6fWHvCXkZg2IhS4RcVHxwuyVejPR7c+a4Go0xbQ9ps5kILa8viiYQgrM4gfTyeTjJ0ekgJH9gk/BawTpmkbZA==", "integrity": "sha512-S6fWHvCXkZg2IhS4RcVHxwuyVejPR7c+a4Go0xbQ9ps5kILa8viiYQgrM4gfTyeTjJ0ekgJH9gk/BawTpmkbZA==",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -1982,9 +1996,9 @@
} }
}, },
"node_modules/firebase-admin": { "node_modules/firebase-admin": {
"version": "13.9.0", "version": "13.10.0",
"resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.9.0.tgz", "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.10.0.tgz",
"integrity": "sha512-qiCVBBFH+kfLiCXuuE9eAbBQSckPuA43fbQ/MNvQfd9nZcHFQExmQICD/N0sZrNZDNy8FSywhjFzJJGVQzG5UA==", "integrity": "sha512-rbuCrJvYRwqBqvbccMS8fj/x2zsaMisdf5RQbRzQzr14Rbq9r2UlpuBHqWAwrO6c9dIRF56xF/xoepXsD5yDuQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@fastify/busboy": "^3.0.0", "@fastify/busboy": "^3.0.0",
@@ -1994,9 +2008,7 @@
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"google-auth-library": "^10.6.1", "google-auth-library": "^10.6.1",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"jwks-rsa": "^3.1.0", "jwks-rsa": "^3.1.0"
"node-forge": "^1.4.0",
"uuid": "^11.0.2"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -2906,15 +2918,6 @@
} }
} }
}, },
"node_modules/node-forge": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz",
"integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==",
"license": "(BSD-3-Clause OR GPL-2.0)",
"engines": {
"node": ">= 6.13.0"
}
},
"node_modules/node-gyp-build-optional-packages": { "node_modules/node-gyp-build-optional-packages": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz",
@@ -3622,19 +3625,6 @@
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
"node_modules/uuid": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz",
"integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/vary": { "node_modules/vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

@@ -14,6 +14,7 @@
"@peculiar/asn1-schema": "^2.7.0", "@peculiar/asn1-schema": "^2.7.0",
"cbor-x": "^1.6.4", "cbor-x": "^1.6.4",
"cors": "^2.8.6", "cors": "^2.8.6",
"dotenv": "^16.6.1",
"did-jwt": "^7.4.7", "did-jwt": "^7.4.7",
"did-resolver": "^4.1.0", "did-resolver": "^4.1.0",
"express": "^5.2.1", "express": "^5.2.1",

3
src/env.ts Normal file
View File

@@ -0,0 +1,3 @@
import { config } from "dotenv";
config();

View File

@@ -1,3 +1,4 @@
import "./env.js";
import cors from "cors"; import cors from "cors";
import express from "express"; import express from "express";
import "./services/firebase.js"; import "./services/firebase.js";

View File

@@ -2,11 +2,45 @@ import admin from "firebase-admin";
import type { ServiceAccount } from "firebase-admin/app"; import type { ServiceAccount } from "firebase-admin/app";
import type { Messaging } from "firebase-admin/messaging"; import type { Messaging } from "firebase-admin/messaging";
type ServiceAccountJson = ServiceAccount & { project_id?: string };
function serviceAccountProjectId(account: ServiceAccountJson): string | undefined {
if (typeof account.projectId === "string" && account.projectId.length > 0) {
return account.projectId;
}
if (typeof account.project_id === "string" && account.project_id.length > 0) {
return account.project_id;
}
return undefined;
}
function resolveCredential(): admin.credential.Credential { function resolveCredential(): admin.credential.Credential {
const json = process.env.FIREBASE_SERVICE_ACCOUNT_JSON; const json = process.env.FIREBASE_SERVICE_ACCOUNT_JSON;
if (json !== undefined && json.trim() !== "") { if (json !== undefined && json.trim() !== "") {
return admin.credential.cert(JSON.parse(json) as ServiceAccount); let account: ServiceAccountJson;
try {
account = JSON.parse(json) as ServiceAccountJson;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
console.error(
"[Firebase] FIREBASE_SERVICE_ACCOUNT_JSON parse failed:",
message
);
throw err;
}
const projectId = serviceAccountProjectId(account);
console.log(
"[Firebase] Credential: FIREBASE_SERVICE_ACCOUNT_JSON (parsed successfully)"
);
if (projectId !== undefined) {
console.log("[Firebase] project_id:", projectId);
} else {
console.log("[Firebase] project_id: (not found in service account JSON)");
}
return admin.credential.cert(account);
} }
console.log("[Firebase] Credential: Application Default Credentials");
return admin.credential.applicationDefault(); return admin.credential.applicationDefault();
} }