feat(api): TokenVerifier-gränssnitt + FirebaseTokenVerifier (AUTH-2)

This commit is contained in:
Sven (AAMOS AI)
2026-08-15 19:37:11 +07:00
parent 3b313135d4
commit f417902ac9
3 changed files with 1246 additions and 9 deletions
+1
View File
@@ -37,6 +37,7 @@
"drizzle-orm": "^0.45.0",
"fastify": "^5.6.0",
"fastify-plugin": "^5.0.0",
"firebase-admin": "^14.2.0",
"ioredis": "^6.0.0",
"nodemailer": "^9.0.3",
"pg": "^8.13.0",
+50
View File
@@ -0,0 +1,50 @@
import { initializeApp, cert, getApps, type App, type ServiceAccount } from "firebase-admin/app";
import { getAuth } from "firebase-admin/auth";
/**
* Leverantörsneutralt resultat av en verifierad ID-token.
* App-koden/rutterna använder DETTA aldrig firebase-admin direkt.
*/
export interface VerifiedIdentity {
/** Stabil identitet från leverantören (Firebase-UID). */
uid: string;
email: string | null;
emailVerified: boolean;
name: string | null;
/** t.ex. "password" | "google.com" | "apple.com". */
provider: string | null;
}
/**
* Enda gränssnittet mot token-verifiering. En framtida ZitadelTokenVerifier
* (introspektion/JWKS) implementerar samma interface då byts bara denna fil.
*/
export interface TokenVerifier {
verify(idToken: string): Promise<VerifiedIdentity>;
}
/**
* Firebase-implementation. ENDA serverfilen som importerar firebase-admin.
* Admin-appen initieras lat (först vid första verify) så att build/typecheck
* aldrig kräver tjänstekontot.
*/
export function createFirebaseTokenVerifier(serviceAccount: ServiceAccount): TokenVerifier {
let app: App | undefined;
const ensureApp = (): App => {
if (app) return app;
app = getApps()[0] ?? initializeApp({ credential: cert(serviceAccount) });
return app;
};
return {
async verify(idToken: string): Promise<VerifiedIdentity> {
const decoded = await getAuth(ensureApp()).verifyIdToken(idToken);
return {
uid: decoded.uid,
email: decoded.email ?? null,
emailVerified: decoded.email_verified ?? false,
name: (decoded.name as string | undefined) ?? null,
provider: decoded.firebase?.sign_in_provider ?? null,
};
},
};
}