This commit is contained in:
2026-08-07 14:47:11 +08:00
parent e7cc0cf18b
commit 7ac173fac4
21 changed files with 1714 additions and 791 deletions
+57 -7
View File
@@ -10,10 +10,12 @@ import {
import type { PlatformPermissionResource } from '@/shared/types/platformPermission'
import { decodeJwtClaims } from './jwt'
import { listCurrentPlatformPermissionCodes } from './platformAuthorization'
import { coordinateCrossTabRefresh } from './crossTabRefresh'
const TOKEN_KEY = 'cc-admin-token'
const REFRESH_TOKEN_KEY = 'cc-admin-refresh-token'
const PERMISSION_CODES_KEY = 'cc-admin-permission-codes'
const REFRESH_LOCK_NAME = 'cc-admin-auth-refresh'
interface LoginResponse {
tokenType: string
@@ -117,6 +119,33 @@ export const useAuthStore = defineStore('admin-auth', () => {
localStorage.removeItem(PERMISSION_CODES_KEY)
}
function applyStoredSession() {
const storedToken = localStorage.getItem(TOKEN_KEY) || ''
const storedRefreshToken = localStorage.getItem(REFRESH_TOKEN_KEY) || ''
if (!storedToken) {
sessionGeneration += 1
token.value = ''
refreshToken.value = ''
user.value = null
permissions.value = []
permissionResources.value = []
isServerSession.value = false
return
}
try {
const session = sessionFromToken(storedToken)
const cachedPermissions = JSON.parse(localStorage.getItem(PERMISSION_CODES_KEY) || '[]') as unknown
token.value = storedToken
refreshToken.value = storedRefreshToken
user.value = session.user
permissions.value = session.permissions.filter((code) => Array.isArray(cachedPermissions) && cachedPermissions.includes(code))
permissionResources.value = []
isServerSession.value = true
} catch {
clearSession()
}
}
async function revokeTokenPair(accessToken: string, refreshTokenToRevoke: string) {
await http.post('/auth/logout', { refreshToken: refreshTokenToRevoke }, {
headers: { Authorization: `Bearer ${accessToken}` },
@@ -146,15 +175,30 @@ export const useAuthStore = defineStore('admin-auth', () => {
}
async function refreshSession() {
if (!refreshToken.value) throw new Error('刷新令牌不存在,请重新登录')
const observedRefreshToken = localStorage.getItem(REFRESH_TOKEN_KEY) || refreshToken.value
if (!observedRefreshToken) throw new Error('刷新令牌不存在,请重新登录')
const expectedGeneration = sessionGeneration
try {
const response = await http.post<ApiEnvelope<LoginResponse>, ApiEnvelope<LoginResponse>>(
'/auth/refresh',
{ refreshToken: refreshToken.value },
)
const result = unwrapApiResponse<LoginResponse>(response)
await applySession(result, expectedGeneration)
await coordinateCrossTabRefresh({
lockName: REFRESH_LOCK_NAME,
observedRefreshToken,
currentRefreshToken: () => localStorage.getItem(REFRESH_TOKEN_KEY) || '',
reuseCurrentSession: async () => {
const storedAccessToken = localStorage.getItem(TOKEN_KEY) || ''
const storedRefreshToken = localStorage.getItem(REFRESH_TOKEN_KEY) || ''
if (!storedAccessToken || !storedRefreshToken) throw new Error('登录状态已过期')
await applySession({ tokenType: 'Bearer', accessToken: storedAccessToken, expiresIn: 0, refreshToken: storedRefreshToken, refreshExpiresIn: 0 }, expectedGeneration)
},
refresh: async (currentRefreshToken) => {
const response = await http.post<ApiEnvelope<LoginResponse>, ApiEnvelope<LoginResponse>>(
'/auth/refresh',
{ refreshToken: currentRefreshToken },
)
ensureCurrentGeneration(expectedGeneration)
if (!localStorage.getItem(REFRESH_TOKEN_KEY)) throw new SupersededSessionError('登录会话已在其他标签页退出')
await applySession(unwrapApiResponse<LoginResponse>(response), expectedGeneration)
},
})
} catch (error) {
if (expectedGeneration === sessionGeneration) clearSession()
throw error
@@ -186,6 +230,12 @@ export const useAuthStore = defineStore('admin-auth', () => {
return token.value
})
if (typeof window !== 'undefined') {
window.addEventListener('storage', (event) => {
if ([TOKEN_KEY, REFRESH_TOKEN_KEY, PERMISSION_CODES_KEY].includes(event.key || '')) applyStoredSession()
})
}
async function logout() {
const accessToken = token.value
const tokenToRevoke = refreshToken.value