diff --git a/src/lib/flarebase-auth.ts b/src/lib/flarebase-auth.ts index 6cfc466..35a450a 100644 --- a/src/lib/flarebase-auth.ts +++ b/src/lib/flarebase-auth.ts @@ -232,7 +232,13 @@ export class FlarebaseAuth { async verifySessionCookie(sessionCookie: string): Promise { //Fetch google public key const res = await fetch( - 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys' + 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys', + { + cf: { + cacheTtl: 60 * 60 * 24, + cacheEverything: true + } + } ); const header = decodeProtectedHeader(sessionCookie); @@ -258,6 +264,6 @@ export class FlarebaseAuth { * @param idToken An Identity Platform ID token */ async verifyIdToken(idToken: string): Promise { - return (await verifyIdToken(idToken)) as any as DecodedIdToken; + return (await verifyIdToken(idToken, this.config.cache)) as any as DecodedIdToken; } } diff --git a/src/lib/google-oauth.ts b/src/lib/google-oauth.ts index cf0e4e1..1cc8453 100644 --- a/src/lib/google-oauth.ts +++ b/src/lib/google-oauth.ts @@ -7,6 +7,8 @@ import { SignJWT, } from 'jose'; +import type { Cache } from './cache/cache'; + /** * Get an OAuth 2.0 token from google authentication apis using * a service account @@ -44,18 +46,61 @@ export async function getAuthToken( return oauth.access_token; } +async function getTtl(url: string) { + const response = await fetch(url); + + // Step 2: Check and parse Cache-Control header + const cacheControl = response.headers.get('Cache-Control'); + if (cacheControl) { + const directives = cacheControl.split(',').map((d) => d.trim()); + const maxAgeDirective = directives.find((d) => d.startsWith('max-age=')); + + if (maxAgeDirective) { + const maxAge = parseInt(maxAgeDirective.split('=')[1], 10); + return { maxAge, response }; + } else { + return { maxAge: 0, response }; + } + } else { + return { magAge: 0, response }; + } +} + /** * Verifies an Identity Platform ID token. * If the token is valid, the promise is fulfilled with the token's decoded claims; otherwise, the promise is rejected. * @param idToken An Identity Platform ID token */ -export async function verifyIdToken(idToken: string): Promise { - //Fetch public keys - //TODO: Public keys should be cached until they expire - const res = await fetch( - 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com' - ); - const data = await res.json(); +export async function verifyIdToken(idToken: string, cache: Cache | undefined = undefined): Promise { + let data: any; + const url = + 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'; + + if (cache) { + const key = 'google-public-keys'; + + try { + const cachedValue = await cache.get(key); + data = cachedValue ? JSON.parse(cachedValue as string) : undefined; + + if (!data) { + const { maxAge, response } = await getTtl(url); + data = await response.json(); + + await cache.put(key, JSON.stringify(data), { + expirationTtl: maxAge + }); + } + } catch (error) { + console.error(error); + + const res = await fetch(url); + data = await res.json(); + } + } else { + const res = await fetch(url); + data = await res.json(); + } //Get the correct publicKey from the key id const header = decodeProtectedHeader(idToken);