fix: refactor scopes for service token
This commit is contained in:
@@ -2,7 +2,7 @@ import { metrics } from "@opentelemetry/api"
|
|||||||
|
|
||||||
import * as api from "@/lib/api"
|
import * as api from "@/lib/api"
|
||||||
import { getVerifiedUser } from "@/server/routers/user/query"
|
import { getVerifiedUser } from "@/server/routers/user/query"
|
||||||
import { bookingServiceProcedure, router } from "@/server/trpc"
|
import { router, serviceProcedure } from "@/server/trpc"
|
||||||
|
|
||||||
import { getMembership } from "@/utils/user"
|
import { getMembership } from "@/utils/user"
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ async function getMembershipNumber(
|
|||||||
|
|
||||||
export const bookingMutationRouter = router({
|
export const bookingMutationRouter = router({
|
||||||
booking: router({
|
booking: router({
|
||||||
create: bookingServiceProcedure
|
create: serviceProcedure
|
||||||
.input(createBookingInput)
|
.input(createBookingInput)
|
||||||
.mutation(async function ({ ctx, input }) {
|
.mutation(async function ({ ctx, input }) {
|
||||||
const { checkInDate, checkOutDate, hotelId } = input
|
const { checkInDate, checkOutDate, hotelId } = input
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { metrics } from "@opentelemetry/api"
|
|||||||
|
|
||||||
import * as api from "@/lib/api"
|
import * as api from "@/lib/api"
|
||||||
import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc"
|
import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc"
|
||||||
import { bookingServiceProcedure, router } from "@/server/trpc"
|
import { router, serviceProcedure } from "@/server/trpc"
|
||||||
|
|
||||||
import { getBookingStatusInput } from "./input"
|
import { getBookingStatusInput } from "./input"
|
||||||
import { createBookingSchema } from "./output"
|
import { createBookingSchema } from "./output"
|
||||||
@@ -17,69 +17,70 @@ const getBookingStatusFailCounter = meter.createCounter(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const bookingQueryRouter = router({
|
export const bookingQueryRouter = router({
|
||||||
status: bookingServiceProcedure
|
status: serviceProcedure.input(getBookingStatusInput).query(async function ({
|
||||||
.input(getBookingStatusInput)
|
ctx,
|
||||||
.query(async function ({ ctx, input }) {
|
input,
|
||||||
const { confirmationNumber } = input
|
}) {
|
||||||
getBookingStatusCounter.add(1, { confirmationNumber })
|
const { confirmationNumber } = input
|
||||||
|
getBookingStatusCounter.add(1, { confirmationNumber })
|
||||||
|
|
||||||
const apiResponse = await api.get(
|
const apiResponse = await api.get(
|
||||||
`${api.endpoints.v1.booking}/${confirmationNumber}/status`,
|
`${api.endpoints.v1.booking}/${confirmationNumber}/status`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${ctx.serviceToken}`,
|
Authorization: `Bearer ${ctx.serviceToken}`,
|
||||||
},
|
},
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!apiResponse.ok) {
|
|
||||||
const responseMessage = await apiResponse.text()
|
|
||||||
getBookingStatusFailCounter.add(1, {
|
|
||||||
confirmationNumber,
|
|
||||||
error_type: "http_error",
|
|
||||||
error: responseMessage,
|
|
||||||
})
|
|
||||||
console.error(
|
|
||||||
"api.booking.status error",
|
|
||||||
JSON.stringify({
|
|
||||||
query: { confirmationNumber },
|
|
||||||
error: {
|
|
||||||
status: apiResponse.status,
|
|
||||||
statusText: apiResponse.statusText,
|
|
||||||
text: responseMessage,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
throw serverErrorByStatus(apiResponse.status, apiResponse)
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const apiJson = await apiResponse.json()
|
if (!apiResponse.ok) {
|
||||||
const verifiedData = createBookingSchema.safeParse(apiJson)
|
const responseMessage = await apiResponse.text()
|
||||||
if (!verifiedData.success) {
|
getBookingStatusFailCounter.add(1, {
|
||||||
getBookingStatusFailCounter.add(1, {
|
confirmationNumber,
|
||||||
confirmationNumber,
|
error_type: "http_error",
|
||||||
error_type: "validation_error",
|
error: responseMessage,
|
||||||
error: JSON.stringify(verifiedData.error),
|
})
|
||||||
})
|
console.error(
|
||||||
console.error(
|
"api.booking.status error",
|
||||||
"api.booking.status validation error",
|
|
||||||
JSON.stringify({
|
|
||||||
query: { confirmationNumber },
|
|
||||||
error: verifiedData.error,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
throw badRequestError()
|
|
||||||
}
|
|
||||||
|
|
||||||
getBookingStatusSuccessCounter.add(1, { confirmationNumber })
|
|
||||||
console.info(
|
|
||||||
"api.booking.status success",
|
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
query: { confirmationNumber },
|
query: { confirmationNumber },
|
||||||
|
error: {
|
||||||
|
status: apiResponse.status,
|
||||||
|
statusText: apiResponse.statusText,
|
||||||
|
text: responseMessage,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
return verifiedData.data
|
throw serverErrorByStatus(apiResponse.status, apiResponse)
|
||||||
}),
|
}
|
||||||
|
|
||||||
|
const apiJson = await apiResponse.json()
|
||||||
|
const verifiedData = createBookingSchema.safeParse(apiJson)
|
||||||
|
if (!verifiedData.success) {
|
||||||
|
getBookingStatusFailCounter.add(1, {
|
||||||
|
confirmationNumber,
|
||||||
|
error_type: "validation_error",
|
||||||
|
error: JSON.stringify(verifiedData.error),
|
||||||
|
})
|
||||||
|
console.error(
|
||||||
|
"api.booking.status validation error",
|
||||||
|
JSON.stringify({
|
||||||
|
query: { confirmationNumber },
|
||||||
|
error: verifiedData.error,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
throw badRequestError()
|
||||||
|
}
|
||||||
|
|
||||||
|
getBookingStatusSuccessCounter.add(1, { confirmationNumber })
|
||||||
|
console.info(
|
||||||
|
"api.booking.status success",
|
||||||
|
JSON.stringify({
|
||||||
|
query: { confirmationNumber },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return verifiedData.data
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { request } from "@/lib/graphql/request"
|
|||||||
import { Context } from "@/server/context"
|
import { Context } from "@/server/context"
|
||||||
import { notFound } from "@/server/errors/trpc"
|
import { notFound } from "@/server/errors/trpc"
|
||||||
import {
|
import {
|
||||||
contentStackBaseWithProfileServiceProcedure,
|
|
||||||
contentStackBaseWithProtectedProcedure,
|
contentStackBaseWithProtectedProcedure,
|
||||||
|
contentStackBaseWithServiceProcedure,
|
||||||
router,
|
router,
|
||||||
} from "@/server/trpc"
|
} from "@/server/trpc"
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ export const rewardQueryRouter = router({
|
|||||||
nextCursor,
|
nextCursor,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
byLevel: contentStackBaseWithProfileServiceProcedure
|
byLevel: contentStackBaseWithServiceProcedure
|
||||||
.input(rewardsByLevelInput)
|
.input(rewardsByLevelInput)
|
||||||
.query(async function ({ input, ctx }) {
|
.query(async function ({ input, ctx }) {
|
||||||
getByLevelRewardCounter.add(1)
|
getByLevelRewardCounter.add(1)
|
||||||
@@ -310,7 +310,7 @@ export const rewardQueryRouter = router({
|
|||||||
getByLevelRewardSuccessCounter.add(1)
|
getByLevelRewardSuccessCounter.add(1)
|
||||||
return { level: loyaltyLevelsConfig, rewards: levelsWithRewards }
|
return { level: loyaltyLevelsConfig, rewards: levelsWithRewards }
|
||||||
}),
|
}),
|
||||||
all: contentStackBaseWithProfileServiceProcedure
|
all: contentStackBaseWithServiceProcedure
|
||||||
.input(rewardsAllInput)
|
.input(rewardsAllInput)
|
||||||
.query(async function ({ input, ctx }) {
|
.query(async function ({ input, ctx }) {
|
||||||
getAllRewardCounter.add(1)
|
getAllRewardCounter.add(1)
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import {
|
|||||||
} from "@/server/errors/trpc"
|
} from "@/server/errors/trpc"
|
||||||
import { extractHotelImages } from "@/server/routers/utils/hotels"
|
import { extractHotelImages } from "@/server/routers/utils/hotels"
|
||||||
import {
|
import {
|
||||||
contentStackUidWithHotelServiceProcedure,
|
contentStackUidWithServiceProcedure,
|
||||||
hotelServiceProcedure,
|
|
||||||
publicProcedure,
|
publicProcedure,
|
||||||
router,
|
router,
|
||||||
|
serviceProcedure,
|
||||||
} from "@/server/trpc"
|
} from "@/server/trpc"
|
||||||
import { toApiLang } from "@/server/utils"
|
import { toApiLang } from "@/server/utils"
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ async function getContentstackData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const hotelQueryRouter = router({
|
export const hotelQueryRouter = router({
|
||||||
get: contentStackUidWithHotelServiceProcedure
|
get: contentStackUidWithServiceProcedure
|
||||||
.input(getHotelInputSchema)
|
.input(getHotelInputSchema)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { lang, uid } = ctx
|
const { lang, uid } = ctx
|
||||||
@@ -264,7 +264,7 @@ export const hotelQueryRouter = router({
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
availability: router({
|
availability: router({
|
||||||
hotels: hotelServiceProcedure
|
hotels: serviceProcedure
|
||||||
.input(getHotelsAvailabilityInputSchema)
|
.input(getHotelsAvailabilityInputSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const {
|
const {
|
||||||
@@ -388,7 +388,7 @@ export const hotelQueryRouter = router({
|
|||||||
.flatMap((hotels) => hotels.attributes),
|
.flatMap((hotels) => hotels.attributes),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
rooms: hotelServiceProcedure
|
rooms: serviceProcedure
|
||||||
.input(getRoomsAvailabilityInputSchema)
|
.input(getRoomsAvailabilityInputSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const {
|
const {
|
||||||
@@ -543,7 +543,7 @@ export const hotelQueryRouter = router({
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
hotelData: router({
|
hotelData: router({
|
||||||
get: hotelServiceProcedure
|
get: serviceProcedure
|
||||||
.input(getlHotelDataInputSchema)
|
.input(getlHotelDataInputSchema)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { hotelId, language, include } = input
|
const { hotelId, language, include } = input
|
||||||
@@ -641,7 +641,7 @@ export const hotelQueryRouter = router({
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
locations: router({
|
locations: router({
|
||||||
get: hotelServiceProcedure.query(async function ({ ctx }) {
|
get: serviceProcedure.query(async function ({ ctx }) {
|
||||||
const searchParams = new URLSearchParams()
|
const searchParams = new URLSearchParams()
|
||||||
searchParams.set("language", toApiLang(ctx.lang))
|
searchParams.set("language", toApiLang(ctx.lang))
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { metrics } from "@opentelemetry/api"
|
import { metrics } from "@opentelemetry/api"
|
||||||
import { SafeParseSuccess } from "zod"
|
|
||||||
|
|
||||||
import * as api from "@/lib/api"
|
import * as api from "@/lib/api"
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,13 +1,28 @@
|
|||||||
|
import { metrics } from "@opentelemetry/api"
|
||||||
import { revalidateTag, unstable_cache } from "next/cache"
|
import { revalidateTag, unstable_cache } from "next/cache"
|
||||||
|
|
||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
import { generateServiceTokenTag } from "@/utils/generateTag"
|
import { generateServiceTokenTag } from "@/utils/generateTag"
|
||||||
|
|
||||||
import { ServiceTokenScope } from "@/types/enums/serviceToken"
|
import { ServiceTokenScopeEnum } from "@/types/enums/serviceToken"
|
||||||
import { ServiceTokenResponse } from "@/types/tokens"
|
import { ServiceTokenResponse } from "@/types/tokens"
|
||||||
|
|
||||||
async function getServiceToken(scopes: ServiceTokenScope[]) {
|
// OpenTelemetry metrics: Service token
|
||||||
|
const meter = metrics.getMeter("trpc.context.serviceToken")
|
||||||
|
const getServiceTokenCounter = meter.createCounter(
|
||||||
|
"trpc.context.serviceToken.get-new-token"
|
||||||
|
)
|
||||||
|
const getTempServiceTokenCounter = meter.createCounter(
|
||||||
|
"trpc.context.serviceToken.get-temporary"
|
||||||
|
)
|
||||||
|
const getServiceTokenFailCounter = meter.createCounter(
|
||||||
|
"trpc.context.serviceToken.get-fail"
|
||||||
|
)
|
||||||
|
|
||||||
|
async function getServiceToken() {
|
||||||
|
getServiceTokenCounter.add(1)
|
||||||
|
const scopes = Object.keys(ServiceTokenScopeEnum)
|
||||||
const response = await fetch(`${env.CURITY_ISSUER_USER}/oauth/v2/token`, {
|
const response = await fetch(`${env.CURITY_ISSUER_USER}/oauth/v2/token`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -23,32 +38,45 @@ async function getServiceToken(scopes: ServiceTokenScope[]) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
getServiceTokenFailCounter.add(1, {
|
||||||
|
error_type: "http_error",
|
||||||
|
error: JSON.stringify({
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
}),
|
||||||
|
})
|
||||||
throw new Error("Failed to obtain service token")
|
throw new Error("Failed to obtain service token")
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchServiceToken(
|
export async function fetchServiceToken(): Promise<ServiceTokenResponse> {
|
||||||
scopes: ServiceTokenScope[]
|
|
||||||
): Promise<ServiceTokenResponse> {
|
|
||||||
try {
|
try {
|
||||||
const tag = generateServiceTokenTag(scopes)
|
const tag = generateServiceTokenTag()
|
||||||
const getCachedJwt = unstable_cache(
|
const getCachedJwt = unstable_cache(
|
||||||
async (scopes) => {
|
async () => {
|
||||||
const jwt = await getServiceToken(scopes)
|
const jwt = await getServiceToken()
|
||||||
|
|
||||||
const expiresAt = Date.now() + jwt.expires_in * 1000
|
const expiresAt = Date.now() + jwt.expires_in * 1000
|
||||||
return { expiresAt, jwt }
|
return { expiresAt, jwt }
|
||||||
},
|
},
|
||||||
scopes,
|
[],
|
||||||
{ tags: [tag] }
|
{ tags: [tag] }
|
||||||
)
|
)
|
||||||
|
|
||||||
const cachedJwt = await getCachedJwt(scopes)
|
const cachedJwt = await getCachedJwt()
|
||||||
if (cachedJwt.expiresAt < Date.now()) {
|
if (cachedJwt.expiresAt < Date.now()) {
|
||||||
|
console.log(
|
||||||
|
"trpc.context.serviceToken: Service token expired, revalidating tag"
|
||||||
|
)
|
||||||
revalidateTag(tag)
|
revalidateTag(tag)
|
||||||
const newToken = await getServiceToken(scopes)
|
|
||||||
|
console.log(
|
||||||
|
"trpc.context.serviceToken: Fetching new temporary service token."
|
||||||
|
)
|
||||||
|
getTempServiceTokenCounter.add(1)
|
||||||
|
const newToken = await getServiceToken()
|
||||||
return newToken
|
return newToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,29 +125,17 @@ export const safeProtectedProcedure = t.procedure.use(async function (opts) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function createServiceProcedure(serviceName: ServiceTokenScope) {
|
export const serviceProcedure = t.procedure.use(async (opts) => {
|
||||||
return t.procedure.use(async (opts) => {
|
const { access_token } = await fetchServiceToken()
|
||||||
const { access_token } = await fetchServiceToken([serviceName])
|
if (!access_token) {
|
||||||
if (!access_token) {
|
throw internalServerError(`Failed to obtain service token`)
|
||||||
throw internalServerError(`Failed to obtain ${serviceName} service token`)
|
}
|
||||||
}
|
return opts.next({
|
||||||
return opts.next({
|
ctx: {
|
||||||
ctx: {
|
serviceToken: access_token,
|
||||||
serviceToken: access_token,
|
},
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
|
|
||||||
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(
|
export const serverActionProcedure = t.procedure.experimental_caller(
|
||||||
experimental_nextAppDirCaller({
|
experimental_nextAppDirCaller({
|
||||||
@@ -178,11 +166,11 @@ export const protectedServerActionProcedure = serverActionProcedure.use(
|
|||||||
|
|
||||||
// NOTE: This is actually save to use, just the implementation could change
|
// NOTE: This is actually save to use, just the implementation could change
|
||||||
// in minor version bumps. Please read: https://trpc.io/docs/faq#unstable
|
// in minor version bumps. Please read: https://trpc.io/docs/faq#unstable
|
||||||
export const contentStackUidWithHotelServiceProcedure =
|
export const contentStackUidWithServiceProcedure =
|
||||||
contentstackExtendedProcedureUID.unstable_concat(hotelServiceProcedure)
|
contentstackExtendedProcedureUID.unstable_concat(serviceProcedure)
|
||||||
|
|
||||||
export const contentStackBaseWithProfileServiceProcedure =
|
export const contentStackBaseWithServiceProcedure =
|
||||||
contentstackBaseProcedure.unstable_concat(profileServiceProcedure)
|
contentstackBaseProcedure.unstable_concat(serviceProcedure)
|
||||||
|
|
||||||
export const contentStackBaseWithProtectedProcedure =
|
export const contentStackBaseWithProtectedProcedure =
|
||||||
contentstackBaseProcedure.unstable_concat(protectedProcedure)
|
contentstackBaseProcedure.unstable_concat(protectedProcedure)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ServiceTokenScopeEnum } from "@/types/enums/serviceToken"
|
||||||
import { System } from "@/types/requests/system"
|
import { System } from "@/types/requests/system"
|
||||||
import type { Edges } from "@/types/requests/utils/edges"
|
import type { Edges } from "@/types/requests/utils/edges"
|
||||||
import type { NodeRefs } from "@/types/requests/utils/refs"
|
import type { NodeRefs } from "@/types/requests/utils/refs"
|
||||||
@@ -106,6 +107,7 @@ export function generateLoyaltyConfigTag(
|
|||||||
* @param serviceTokenScope scope of service token
|
* @param serviceTokenScope scope of service token
|
||||||
* @returns string
|
* @returns string
|
||||||
*/
|
*/
|
||||||
export function generateServiceTokenTag(serviceTokenScopes: string[]) {
|
export function generateServiceTokenTag() {
|
||||||
return `service_token:${serviceTokenScopes.join("-")}`
|
const scopes = Object.keys(ServiceTokenScopeEnum).join("-")
|
||||||
|
return `service_token:${scopes}`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user