Merged in feat/sw-2859-set-up-shared-trpc-package (pull request #2319)
feat(SW-2859): Create trpc package * Add isEdge, safeTry and dataCache to new common package * Add eslint and move prettier config * Clean up tests * Create trpc package and move initialization * Move errors and a few procedures * Move telemetry to common package * Move tokenManager to common package * Add Sentry to procedures * Clean up procedures * Fix self-referencing imports * Add exports to packages and lint rule to prevent relative imports * Add env to trpc package * Add eslint to trpc package * Apply lint rules * Use direct imports from trpc package * Add lint-staged config to trpc * Move lang enum to common * Restructure trpc package folder structure * Fix lang imports Approved-by: Linus Flood
This commit is contained in:
2
packages/common/tokenManager/index.ts
Normal file
2
packages/common/tokenManager/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import { getServiceToken } from "./tokenManager"
|
||||
export { getServiceToken }
|
||||
123
packages/common/tokenManager/tokenManager.ts
Normal file
123
packages/common/tokenManager/tokenManager.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { trace, type Tracer } from "@opentelemetry/api"
|
||||
|
||||
import { getCacheClient } from "../dataCache"
|
||||
import { env } from "../env/server"
|
||||
import { createCounter } from "../telemetry"
|
||||
|
||||
interface ServiceTokenResponse {
|
||||
access_token: string
|
||||
scope?: string
|
||||
token_type: string
|
||||
expires_in: number
|
||||
}
|
||||
|
||||
export async function getServiceToken() {
|
||||
const tracer = trace.getTracer("getServiceToken")
|
||||
|
||||
return await tracer.startActiveSpan("getServiceToken", async () => {
|
||||
const scopes = ["profile", "hotel", "booking", "package", "availability"]
|
||||
|
||||
const cacheKey = getServiceTokenCacheKey(scopes)
|
||||
const cacheClient = await getCacheClient()
|
||||
const token = await getOrSetServiceTokenFromCache(cacheKey, scopes, tracer)
|
||||
|
||||
if (token.expiresAt < Date.now()) {
|
||||
await cacheClient.deleteKey(cacheKey)
|
||||
|
||||
const newToken = await getOrSetServiceTokenFromCache(
|
||||
cacheKey,
|
||||
scopes,
|
||||
tracer
|
||||
)
|
||||
return newToken.jwt
|
||||
}
|
||||
|
||||
return token.jwt
|
||||
})
|
||||
}
|
||||
|
||||
async function getOrSetServiceTokenFromCache(
|
||||
cacheKey: string,
|
||||
scopes: string[],
|
||||
tracer: Tracer
|
||||
) {
|
||||
const cacheClient = await getCacheClient()
|
||||
const token = await cacheClient.cacheOrGet(
|
||||
cacheKey,
|
||||
async () => {
|
||||
return await tracer.startActiveSpan("fetch new token", async () => {
|
||||
const newToken = await getJwt(scopes)
|
||||
return newToken
|
||||
})
|
||||
},
|
||||
"1h"
|
||||
)
|
||||
return token
|
||||
}
|
||||
|
||||
async function getJwt(scopes: string[]) {
|
||||
const getJwtCounter = createCounter("tokenManager", "getJwt")
|
||||
const metricsGetJwt = getJwtCounter.init({
|
||||
scopes,
|
||||
})
|
||||
|
||||
metricsGetJwt.start()
|
||||
|
||||
const jwt = await fetchServiceToken(scopes)
|
||||
|
||||
const expiresAt = Date.now() + jwt.expires_in * 1000
|
||||
|
||||
metricsGetJwt.success()
|
||||
|
||||
return { expiresAt, jwt }
|
||||
}
|
||||
|
||||
async function fetchServiceToken(scopes: string[]) {
|
||||
const fetchServiceTokenCounter = createCounter(
|
||||
"tokenManager",
|
||||
"fetchServiceToken"
|
||||
)
|
||||
const metricsFetchServiceToken = fetchServiceTokenCounter.init({
|
||||
scopes,
|
||||
})
|
||||
|
||||
metricsFetchServiceToken.start()
|
||||
|
||||
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(" "),
|
||||
}),
|
||||
signal: AbortSignal.timeout(15_000),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
await metricsFetchServiceToken.httpError(response)
|
||||
|
||||
const text = await response.text()
|
||||
throw new Error(
|
||||
`[fetchServiceToken] Failed to obtain service token: ${JSON.stringify({
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
text,
|
||||
})}`
|
||||
)
|
||||
}
|
||||
|
||||
const result = response.json() as Promise<ServiceTokenResponse>
|
||||
|
||||
metricsFetchServiceToken.success()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function getServiceTokenCacheKey(scopes: string[]): string {
|
||||
return `serviceToken:${scopes.join(",")}`
|
||||
}
|
||||
Reference in New Issue
Block a user