Firebase Module
Shared infrastructure module wrapping the Firebase Admin SDK. Provides token verification and custom claims management to other backend modules.
Architecture
firebase/
├── firebase.module.ts # NestJS module definition
├── domain/
│ ├── firebase-auth.port.ts # Port interface (FirebaseAuthPort)
│ ├── firebase.types.ts # Shared type definitions
│ └── firebase.errors.ts # Domain error classes
└── infrastructure/
├── firebase.service.ts # Concrete adapter (implements FirebaseAuthPort)
└── firebase.providers.ts # Factory providers (Firebase App + port binding)
Domain layer — Framework-agnostic port interface (FirebaseAuthPort), types, and error classes. Consumers can depend on the port abstraction for testability.
Infrastructure layer — Concrete FirebaseService implementing the port using Firebase Admin SDK. Provider factory handles SDK initialization from environment variables.
Capabilities
Token Verification
verifyIdToken(idToken: string): Promise<DecodedIdToken>
Verifies a Firebase ID token and returns the decoded payload. Used by AuthGuard (globally) and AuthService (auth flow).
Custom Claims Management
updateUserCustomClaims({ uid, claims }): Promise<void>
Deep-merges new claims onto the user's existing Firebase custom claims using mergeDeepLeft. Preserves identity/platform claims (db_user_id, role, onboarding_completed). Always strips role_by_business_id — membership roles live in business_user and are resolved by RolesGuard / MembershipRoleLookup. Validates the slim payload does not exceed Firebase's 1000-character limit.
Slim JWT contract:
| Claim | Purpose |
|---|---|
db_user_id | FlowPOS user UUID |
role | Optional platform role (root / admin) |
onboarding_completed | Onboarding flag |
Consumers:
AuthService— syncsdb_user_idon user creation; may set platformroleOnboardingOrchestratorService— setsdb_user_id/onboarding_completedUserIdentityService— setsdb_user_idon user creation
Do not write membership roles into Firebase claims.
Injection
| Token | Type | Description |
|---|---|---|
FirebaseService | Class | Concrete implementation (default for existing consumers) |
FIREBASE_AUTH_PORT | Interface | Port abstraction for new consumers and testing |
FIREBASE_APP | App | null | Raw Firebase Admin App instance |
Environment Variables
| Variable | Required | Description |
|---|---|---|
FIREBASE_PROJECT_ID | Yes | Firebase project ID |
FIREBASE_CLIENT_EMAIL | Yes | Service account email |
FIREBASE_PRIVATE_KEY | One of these | Service account private key (plain text, \n escaped) |
FIREBASE_PRIVATE_KEY_B64 | One of these | Service account private key (base64 encoded, takes precedence) |
If credentials are missing, the module initializes with a null app and logs a warning. All operations will throw FirebaseNotInitializedError.
Error Classes
| Error | When |
|---|---|
FirebaseNotInitializedError | Firebase credentials missing or invalid |
ClaimsTooLargeError | Slim claims payload still exceeds 1000 characters |
Design Decisions
- No HTTP endpoints — This is a pure infrastructure module. HTTP auth endpoints live in the
authmodule. - Deep merge + strip membership map — Uses
mergeDeepLeft(rambda) so partial claim updates don't destroy unrelated claims. Legacyrole_by_business_idis removed on every write so multi-business users stay under Firebase's 1KB limit. - Graceful null-app — Allows the backend to start without Firebase credentials (useful for local dev/testing), failing only when Firebase operations are actually called.
- Port interface —
FirebaseAuthPortenables consumers to depend on an abstraction, improving testability without requiring a full Firebase emulator.