diff --git a/server/tokenManager.ts b/server/tokenManager.ts index 4007896d6..4681721e3 100644 --- a/server/tokenManager.ts +++ b/server/tokenManager.ts @@ -1,35 +1,58 @@ +import { revalidateTag, unstable_cache } from "next/cache" + import { env } from "@/env/server" +import { generateServiceTokenTag } from "@/utils/generateTag" + +import { ServiceTokenScope } from "@/types/enums/serviceToken" import { ServiceTokenResponse } from "@/types/tokens" -const SERVICE_TOKEN_REVALIDATE_SECONDS = 3599 // 59 minutes and 59 seconds. +async function getServiceToken(scopes: ServiceTokenScope[]) { + const response = await fetch(`${env.CURITY_ISSUER_USER}/oauth/v2/token`, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Accept: "application/json", + }, + body: new URLSearchParams({ + grant_type: "client_credentials", + client_id: env.CURITY_CLIENT_ID_SERVICE, + client_secret: env.CURITY_CLIENT_SECRET_SERVICE, + scope: scopes.join(" "), + }), + }) + + if (!response.ok) { + throw new Error("Failed to obtain service token") + } + + return response.json() +} export async function fetchServiceToken( - scopes: string[] + scopes: ServiceTokenScope[] ): Promise { try { - const response = await fetch(`${env.CURITY_ISSUER_USER}/oauth/v2/token`, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - Accept: "application/json", - }, - body: new URLSearchParams({ - grant_type: "client_credentials", - client_id: env.CURITY_CLIENT_ID_SERVICE, - client_secret: env.CURITY_CLIENT_SECRET_SERVICE, - scope: scopes.join(" "), - }), - next: { - revalidate: SERVICE_TOKEN_REVALIDATE_SECONDS, - }, - }) + const tag = generateServiceTokenTag(scopes) + const getCachedJwt = unstable_cache( + async (scopes) => { + const jwt = await getServiceToken(scopes) - if (!response.ok) { - throw new Error("Failed to obtain service token") + const expiresAt = Date.now() + jwt.expires_in * 1000 + return { expiresAt, jwt } + }, + scopes, + { tags: [tag] } + ) + + const cachedJwt = await getCachedJwt(scopes) + if (cachedJwt.expiresAt < Date.now()) { + revalidateTag(tag) + const newToken = await getServiceToken(scopes) + return newToken } - return response.json() + return cachedJwt.jwt } catch (error) { console.error("Error fetching service token:", error) throw error diff --git a/server/trpc.ts b/server/trpc.ts index e8f9c26f1..2ea6a871e 100644 --- a/server/trpc.ts +++ b/server/trpc.ts @@ -17,6 +17,10 @@ import { langInput } from "./utils" import type { Session } from "next-auth" +import { + ServiceTokenScope, + ServiceTokenScopeEnum, +} from "@/types/enums/serviceToken" import type { Meta } from "@/types/trpc/meta" const t = initTRPC @@ -121,7 +125,7 @@ export const safeProtectedProcedure = t.procedure.use(async function (opts) { }) }) -function createServiceProcedure(serviceName: string) { +function createServiceProcedure(serviceName: ServiceTokenScope) { return t.procedure.use(async (opts) => { const { access_token } = await fetchServiceToken([serviceName]) if (!access_token) { @@ -135,9 +139,15 @@ function createServiceProcedure(serviceName: string) { }) } -export const bookingServiceProcedure = createServiceProcedure("booking") -export const hotelServiceProcedure = createServiceProcedure("hotel") -export const profileServiceProcedure = createServiceProcedure("profile") +export const bookingServiceProcedure = createServiceProcedure( + ServiceTokenScopeEnum.booking +) +export const hotelServiceProcedure = createServiceProcedure( + ServiceTokenScopeEnum.hotel +) +export const profileServiceProcedure = createServiceProcedure( + ServiceTokenScopeEnum.profile +) export const serverActionProcedure = t.procedure.experimental_caller( experimental_nextAppDirCaller({ diff --git a/types/enums/serviceToken.ts b/types/enums/serviceToken.ts new file mode 100644 index 000000000..4efbe1218 --- /dev/null +++ b/types/enums/serviceToken.ts @@ -0,0 +1,7 @@ +export enum ServiceTokenScopeEnum { + profile = "profile", + hotel = "hotel", + booking = "booking", +} + +export type ServiceTokenScope = keyof typeof ServiceTokenScopeEnum diff --git a/utils/generateTag.ts b/utils/generateTag.ts index 08f2b404a..41251245c 100644 --- a/utils/generateTag.ts +++ b/utils/generateTag.ts @@ -99,3 +99,13 @@ export function generateLoyaltyConfigTag( ) { return `${lang}:loyalty_config:${contentTypeUid}:${id}` } + +/** + * Function to generate tags for service tokens + * + * @param serviceTokenScope scope of service token + * @returns string + */ +export function generateServiceTokenTag(serviceTokenScopes: string[]) { + return `service_token:${serviceTokenScopes.join("-")}` +}