diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts index 335d2d806..7f9fcc933 100644 --- a/backend/src/utils/auth.ts +++ b/backend/src/utils/auth.ts @@ -1,9 +1,44 @@ import admin from "firebase-admin"; import { UserRecord } from "firebase-admin/lib/auth/user-record"; import { DecodedIdToken } from "firebase-admin/lib/auth/token-verifier"; +import LRUCache from "lru-cache"; +import { + recordTokenCacheAccess, + setTokenCacheLength, + setTokenCacheSize, +} from "./prometheus"; + +const tokenCache = new LRUCache({ + max: 20000, + maxSize: 20000000, // 20MB + sizeCalculation: (token, key): number => + JSON.stringify(token).length + key.length, //sizeInBytes +}); + +const TOKEN_CACHE_BUFFER = 1000 * 60 * 5; // 5 minutes export async function verifyIdToken(idToken: string): Promise { - return await admin.auth().verifyIdToken(idToken, true); + setTokenCacheLength(tokenCache.size); + setTokenCacheSize(tokenCache.calculatedSize ?? 0); + + const cached = tokenCache.get(idToken); + + if (cached) { + const expirationDate = (cached.exp - TOKEN_CACHE_BUFFER) * 1000; + + if (expirationDate > Date.now()) { + recordTokenCacheAccess("hit_expired"); + tokenCache.delete(idToken); + } else { + recordTokenCacheAccess("hit"); + return cached; + } + } + recordTokenCacheAccess("miss"); + + const decoded = await admin.auth().verifyIdToken(idToken, true); + tokenCache.set(idToken, decoded); + return decoded; } export async function updateUserEmail( diff --git a/backend/src/utils/prometheus.ts b/backend/src/utils/prometheus.ts index 1e3c6d854..98d59ebaf 100644 --- a/backend/src/utils/prometheus.ts +++ b/backend/src/utils/prometheus.ts @@ -227,3 +227,33 @@ export function recordRequestCountry( requestCountry.inc({ path: pathNoGet, country }); } + +const tokenCacheAccess = new Counter({ + name: "api_token_cache_access", + help: "Token cache access", + labelNames: ["status"], +}); + +export function recordTokenCacheAccess( + status: "hit" | "miss" | "hit_expired" +): void { + tokenCacheAccess.inc({ status }); +} + +const tokenCacheSize = new Gauge({ + name: "api_token_cache_size", + help: "Token cache size", +}); + +export function setTokenCacheSize(size: number): void { + tokenCacheSize.set(size); +} + +const tokenCacheLength = new Gauge({ + name: "api_token_cache_length", + help: "Token cache length", +}); + +export function setTokenCacheLength(length: number): void { + tokenCacheLength.set(length); +}