UNPKG

@genkit-ai/firebase

Version:

Genkit AI framework plugin for Firebase including Firestore trace/state store and deployment helpers for Cloud Functions for Firebase.

199 lines 6 kB
import { getAppCheck } from "firebase-admin/app-check"; import { getAuth } from "firebase-admin/auth"; import { UserFacingError } from "genkit"; import { initializeAppIfNecessary } from "./helpers.js"; let cachedDebugSkipTokenVerification; function setDebugSkipTokenVerification(skip) { cachedDebugSkipTokenVerification = skip; } function debugSkipTokenVerification() { if (cachedDebugSkipTokenVerification !== void 0) { return cachedDebugSkipTokenVerification; } if (!process.env.FIREBASE_DEBUG_MODE) { return false; } if (!process.env.FIREBASE_DEBUG_FEATURES) { return false; } const features = JSON.parse( process.env.FIREBASE_DEBUG_FEATURES ); cachedDebugSkipTokenVerification = features.skipTokenVerification ?? false; return cachedDebugSkipTokenVerification; } function firebaseContext(policy) { return async (request) => { initializeAppIfNecessary(); let auth; const authIdToken = extractBearerToken(request.headers["authorization"]); const appCheckToken = request.headers["x-firebase-appcheck"]; if ("authorization" in request.headers) { auth = await verifyAuthToken(authIdToken); } let app; if ("x-firebase-appcheck" in request.headers) { const consumeAppCheckToken = typeof policy === "object" && policy["consumeAppCheckToken"]; app = await verifyAppCheckToken( appCheckToken, consumeAppCheckToken ?? false ); } let instanceIdToken; if ("firebase-instance-id-token" in request.headers) { instanceIdToken = request.headers["firebase-instance-id-token"]; } const context = {}; if (typeof policy === "object" && policy.serverAppConfig) { const { initializeServerApp } = await import("firebase/app"); context.firebaseApp = initializeServerApp(policy.serverAppConfig, { appCheckToken, authIdToken, releaseOnDeref: context }); } if (auth) { context.auth = auth; } if (app) { context.app = app; } if (instanceIdToken) { context.instanceIdToken = instanceIdToken; } if (typeof policy === "function") { await policy(context, request.input); } else if (typeof policy === "object") { enforceDelcarativePolicy(policy, context); } return context; }; } function verifyHasClaims(claims, token) { for (const claim of claims) { if (!token[claim] || token[claim] === "false") { if (claim == "email_verified") { throw new UserFacingError( "PERMISSION_DENIED", "Email must be verified" ); } if (claim === "admin") { throw new UserFacingError("PERMISSION_DENIED", "Must be an admin"); } throw new UserFacingError( "PERMISSION_DENIED", `${claim} claim is required` ); } } } function enforceDelcarativePolicy(policy, context) { if ((policy.signedIn || policy.hasClaim || policy.emailVerified) && !context.auth) { throw new UserFacingError("UNAUTHENTICATED", "Auth is required"); } if (policy.hasClaim) { if (typeof policy.hasClaim === "string") { verifyHasClaims([policy.hasClaim], context.auth.token); } else if (Array.isArray(policy.hasClaim)) { verifyHasClaims(policy.hasClaim, context.auth.token); } else if (typeof policy.hasClaim === "object") { for (const [claim, value] of Object.entries(policy.hasClaim)) { if (context.auth.token[claim] !== value) { throw new UserFacingError( "PERMISSION_DENIED", `Claim ${claim} must be ${value}` ); } } } else { throw Error(`Invalid type ${typeof policy.hasClaim} for hasClaim`); } } if (policy.emailVerified) { verifyHasClaims(["email_verified"], context.auth.token); } if (policy.enforceAppCheck && !context.app) { throw new UserFacingError( "PERMISSION_DENIED", `AppCheck token is required` ); } } function extractBearerToken(authHeader) { return /[bB]earer (.*)/.exec(authHeader)?.[1]; } async function verifyAuthToken(token) { if (!token) { return void 0; } if (debugSkipTokenVerification()) { const decoded = unsafeDecodeToken(token); return { uid: decoded["sub"], token: decoded, rawToken: token }; } try { const decoded = await getAuth().verifyIdToken(token); return { uid: decoded["sub"], token: decoded, rawToken: token }; } catch (err) { console.error(`Error decoding auth token: ${err}`); throw new UserFacingError("PERMISSION_DENIED", "Invalid auth token"); } } async function verifyAppCheckToken(token, consumeAppCheckToken) { if (debugSkipTokenVerification()) { const decoded = unsafeDecodeToken(token); return { appId: decoded["sub"], token: decoded, alreadyConsumed: false, rawToken: token }; } try { return { ...await getAppCheck().verifyToken(token, { consume: consumeAppCheckToken }), rawToken: token }; } catch (err) { console.error(`Got error verifying AppCheck token: ${err}`); throw new UserFacingError("PERMISSION_DENIED", "Invalid AppCheck token"); } } function fakeToken(claims) { return `fake.${Buffer.from(JSON.stringify(claims), "utf-8").toString("base64")}.fake`; } const TOKEN_REGEX = /[a-zA-Z0-9_=-]+\.[a-zA-Z0-9_=-]+\.[a-zA-Z0-9_=-]+/; function unsafeDecodeToken(token) { if (!TOKEN_REGEX.test(token)) { throw new UserFacingError( "PERMISSION_DENIED", "Invalid fake token. Use the fakeToken() method to create a valid fake token" ); } try { return JSON.parse(Buffer.from(token.split(".")[1], "base64").toString()); } catch (err) { throw new UserFacingError( "PERMISSION_DENIED", "Invalid fake token. Use the fakeToken() method to create a valid fake token" ); } } export { fakeToken, firebaseContext, setDebugSkipTokenVerification }; //# sourceMappingURL=context.mjs.map