Files
webapp/server/api/auth/refresh.post.ts
2026-01-07 09:10:35 +07:00

77 lines
2.0 KiB
TypeScript

import { defineEventHandler, createError } from 'h3'
import type LogtoClient from '@logto/node'
const RESOURCES = {
teams: 'https://teams.optovia.ru',
exchange: 'https://exchange.optovia.ru',
orders: 'https://orders.optovia.ru',
kyc: 'https://kyc.optovia.ru',
billing: 'https://billing.optovia.ru'
} as const
export type ResourceKey = keyof typeof RESOURCES
export interface TokenInfo {
token: string
expiresAt: number
}
export interface RefreshResponse {
tokens: Partial<Record<ResourceKey, TokenInfo>>
}
function decodeTokenExpiry(token: string): number {
try {
const payload = token.split('.')[1]
if (payload) {
const decoded = JSON.parse(Buffer.from(payload, 'base64').toString('utf8'))
if (decoded.exp) {
return decoded.exp * 1000
}
}
}
catch {
// ignore
}
return Date.now() + 3600 * 1000 // default 1 hour
}
/**
* Refresh all access tokens for all resources.
* Gets organizationId from logtoUser (first organization).
* Returns tokens with expiry timestamps.
*/
export default defineEventHandler(async (event): Promise<RefreshResponse> => {
const client = event.context.logtoClient as LogtoClient | undefined
const logtoUser = event.context.logtoUser as { organizations?: string[] } | undefined
if (!client) {
throw createError({ statusCode: 401, message: 'Not authenticated' })
}
// Get first organization from Logto user
const organizationId = logtoUser?.organizations?.[0]
const tokens: Partial<Record<ResourceKey, TokenInfo>> = {}
// Fetch all tokens in parallel with organization context
await Promise.all(
(Object.entries(RESOURCES) as [ResourceKey, string][]).map(async ([key, resource]) => {
try {
const token = await client.getAccessToken(resource, organizationId)
if (token) {
tokens[key] = {
token,
expiresAt: decodeTokenExpiry(token)
}
}
}
catch {
// Token not available for this resource, skip
}
})
)
return { tokens }
})