fix(auth): org-scoped team tokens and header search order
This commit is contained in:
@@ -27,14 +27,19 @@ const EXPIRY_BUFFER_MS = 60 * 1000
|
||||
*/
|
||||
export const useLogtoTokens = () => {
|
||||
const tokens = useState<Partial<Record<ResourceKey, TokenInfo>>>('logto-tokens', () => ({}))
|
||||
const tokensOrgId = useState<string | null>('logto-tokens-org-id', () => null)
|
||||
const idToken = useState<string | null>('logto-id-token', () => null)
|
||||
const activeOrgId = useState<string | null>('activeLogtoOrgId', () => null)
|
||||
const orgState = useState<string | null>('logto-org-id', () => null)
|
||||
const isRefreshing = ref(false)
|
||||
let refreshPromise: Promise<void> | null = null
|
||||
let refreshPromiseOrgId: string | null = null
|
||||
|
||||
/**
|
||||
* Get organization ID from Logto user (first organization)
|
||||
*/
|
||||
const getOrganizationId = (): string | undefined => {
|
||||
const resolveOrganizationId = (preferred?: string | null): string | undefined => {
|
||||
if (preferred) return preferred
|
||||
if (import.meta.server) {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const context = nuxtApp.ssrContext?.event.context as {
|
||||
@@ -43,8 +48,7 @@ export const useLogtoTokens = () => {
|
||||
} | undefined
|
||||
return context?.logtoOrgId || context?.logtoUser?.organizations?.[0]
|
||||
}
|
||||
const orgId = useState<string | null>('logto-org-id', () => null)
|
||||
return orgId.value || undefined
|
||||
return activeOrgId.value || orgState.value || undefined
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +58,7 @@ export const useLogtoTokens = () => {
|
||||
if (import.meta.server) {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const client = nuxtApp.ssrContext?.event.context.logtoClient
|
||||
const organizationId = getOrganizationId()
|
||||
const organizationId = resolveOrganizationId()
|
||||
|
||||
if (client) {
|
||||
const results: Partial<Record<ResourceKey, TokenInfo>> = {}
|
||||
@@ -78,6 +82,7 @@ export const useLogtoTokens = () => {
|
||||
)
|
||||
|
||||
tokens.value = results
|
||||
tokensOrgId.value = organizationId || null
|
||||
|
||||
// Also fetch ID token for SSR
|
||||
try {
|
||||
@@ -124,24 +129,36 @@ export const useLogtoTokens = () => {
|
||||
/**
|
||||
* Refresh all tokens from server
|
||||
*/
|
||||
const refreshTokens = async (): Promise<void> => {
|
||||
const refreshTokens = async (organizationId?: string | null): Promise<void> => {
|
||||
const resolvedOrgId = resolveOrganizationId(organizationId) || null
|
||||
|
||||
// Deduplicate concurrent refresh calls
|
||||
if (refreshPromise) {
|
||||
return refreshPromise
|
||||
if (refreshPromiseOrgId === resolvedOrgId) {
|
||||
return refreshPromise
|
||||
}
|
||||
await refreshPromise
|
||||
}
|
||||
|
||||
isRefreshing.value = true
|
||||
refreshPromiseOrgId = resolvedOrgId
|
||||
|
||||
refreshPromise = (async () => {
|
||||
try {
|
||||
const response = await $fetch<RefreshResponse>('/api/auth/refresh', {
|
||||
method: 'POST'
|
||||
method: 'POST',
|
||||
body: resolvedOrgId ? { organizationId: resolvedOrgId } : undefined
|
||||
})
|
||||
tokens.value = response.tokens
|
||||
tokensOrgId.value = resolvedOrgId
|
||||
if (resolvedOrgId) {
|
||||
orgState.value = resolvedOrgId
|
||||
}
|
||||
}
|
||||
finally {
|
||||
isRefreshing.value = false
|
||||
refreshPromise = null
|
||||
refreshPromiseOrgId = null
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -152,19 +169,21 @@ export const useLogtoTokens = () => {
|
||||
* Get access token for a resource URL.
|
||||
* Auto-refreshes if token is expired.
|
||||
*/
|
||||
const getToken = async (resourceUrl: ResourceUrl): Promise<string> => {
|
||||
const getToken = async (resourceUrl: ResourceUrl, organizationId?: string | null): Promise<string> => {
|
||||
// Find resource key by URL
|
||||
const entry = Object.entries(RESOURCES).find(([, url]) => url === resourceUrl)
|
||||
if (!entry) {
|
||||
throw new Error(`Unknown resource: ${resourceUrl}`)
|
||||
}
|
||||
const key = entry[0] as ResourceKey
|
||||
const resolvedOrgId = resolveOrganizationId(organizationId) || null
|
||||
|
||||
const tokenInfo = tokens.value[key]
|
||||
const isOrgMismatch = tokensOrgId.value !== resolvedOrgId
|
||||
|
||||
// If expired, refresh all tokens
|
||||
if (isTokenExpired(tokenInfo)) {
|
||||
await refreshTokens()
|
||||
// If expired (or cached for another org), refresh all tokens
|
||||
if (isOrgMismatch || isTokenExpired(tokenInfo)) {
|
||||
await refreshTokens(resolvedOrgId)
|
||||
}
|
||||
|
||||
const refreshedToken = tokens.value[key]
|
||||
@@ -178,11 +197,13 @@ export const useLogtoTokens = () => {
|
||||
/**
|
||||
* Get token by resource key (teams, exchange, orders, kyc)
|
||||
*/
|
||||
const getTokenByKey = async (key: ResourceKey): Promise<string> => {
|
||||
const getTokenByKey = async (key: ResourceKey, organizationId?: string | null): Promise<string> => {
|
||||
const tokenInfo = tokens.value[key]
|
||||
const resolvedOrgId = resolveOrganizationId(organizationId) || null
|
||||
const isOrgMismatch = tokensOrgId.value !== resolvedOrgId
|
||||
|
||||
if (isTokenExpired(tokenInfo)) {
|
||||
await refreshTokens()
|
||||
if (isOrgMismatch || isTokenExpired(tokenInfo)) {
|
||||
await refreshTokens(resolvedOrgId)
|
||||
}
|
||||
|
||||
const refreshedToken = tokens.value[key]
|
||||
|
||||
Reference in New Issue
Block a user