fix: handle cache tags for service tokens
This commit is contained in:
@@ -1,35 +1,58 @@
|
|||||||
|
import { revalidateTag, unstable_cache } from "next/cache"
|
||||||
|
|
||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
|
import { generateServiceTokenTag } from "@/utils/generateTag"
|
||||||
|
|
||||||
|
import { ServiceTokenScope } from "@/types/enums/serviceToken"
|
||||||
import { ServiceTokenResponse } from "@/types/tokens"
|
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(
|
export async function fetchServiceToken(
|
||||||
scopes: string[]
|
scopes: ServiceTokenScope[]
|
||||||
): Promise<ServiceTokenResponse> {
|
): Promise<ServiceTokenResponse> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${env.CURITY_ISSUER_USER}/oauth/v2/token`, {
|
const tag = generateServiceTokenTag(scopes)
|
||||||
method: "POST",
|
const getCachedJwt = unstable_cache(
|
||||||
headers: {
|
async (scopes) => {
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
const jwt = await getServiceToken(scopes)
|
||||||
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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
const expiresAt = Date.now() + jwt.expires_in * 1000
|
||||||
throw new Error("Failed to obtain service token")
|
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) {
|
} catch (error) {
|
||||||
console.error("Error fetching service token:", error)
|
console.error("Error fetching service token:", error)
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ import { langInput } from "./utils"
|
|||||||
|
|
||||||
import type { Session } from "next-auth"
|
import type { Session } from "next-auth"
|
||||||
|
|
||||||
|
import {
|
||||||
|
ServiceTokenScope,
|
||||||
|
ServiceTokenScopeEnum,
|
||||||
|
} from "@/types/enums/serviceToken"
|
||||||
import type { Meta } from "@/types/trpc/meta"
|
import type { Meta } from "@/types/trpc/meta"
|
||||||
|
|
||||||
const t = initTRPC
|
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) => {
|
return t.procedure.use(async (opts) => {
|
||||||
const { access_token } = await fetchServiceToken([serviceName])
|
const { access_token } = await fetchServiceToken([serviceName])
|
||||||
if (!access_token) {
|
if (!access_token) {
|
||||||
@@ -135,9 +139,15 @@ function createServiceProcedure(serviceName: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bookingServiceProcedure = createServiceProcedure("booking")
|
export const bookingServiceProcedure = createServiceProcedure(
|
||||||
export const hotelServiceProcedure = createServiceProcedure("hotel")
|
ServiceTokenScopeEnum.booking
|
||||||
export const profileServiceProcedure = createServiceProcedure("profile")
|
)
|
||||||
|
export const hotelServiceProcedure = createServiceProcedure(
|
||||||
|
ServiceTokenScopeEnum.hotel
|
||||||
|
)
|
||||||
|
export const profileServiceProcedure = createServiceProcedure(
|
||||||
|
ServiceTokenScopeEnum.profile
|
||||||
|
)
|
||||||
|
|
||||||
export const serverActionProcedure = t.procedure.experimental_caller(
|
export const serverActionProcedure = t.procedure.experimental_caller(
|
||||||
experimental_nextAppDirCaller({
|
experimental_nextAppDirCaller({
|
||||||
|
|||||||
7
types/enums/serviceToken.ts
Normal file
7
types/enums/serviceToken.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export enum ServiceTokenScopeEnum {
|
||||||
|
profile = "profile",
|
||||||
|
hotel = "hotel",
|
||||||
|
booking = "booking",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServiceTokenScope = keyof typeof ServiceTokenScopeEnum
|
||||||
@@ -99,3 +99,13 @@ export function generateLoyaltyConfigTag(
|
|||||||
) {
|
) {
|
||||||
return `${lang}:loyalty_config:${contentTypeUid}:${id}`
|
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("-")}`
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user