fix(frontend): restore pending auth session flow

This commit is contained in:
IanShaw027
2026-04-21 00:05:44 +08:00
parent 4f6966d7b3
commit 85fc54b205
6 changed files with 556 additions and 24 deletions

View File

@@ -52,6 +52,7 @@ interface MockAuthState {
isAdmin: boolean
isSimpleMode: boolean
backendModeEnabled: boolean
hasPendingAuthSession: boolean
}
/**
@@ -78,7 +79,18 @@ function simulateGuard(
}
if (authState.backendModeEnabled && !authState.isAuthenticated) {
const allowed = ['/login', '/key-usage', '/setup']
if (!allowed.some((path) => toPath === path || toPath.startsWith(path))) {
const callbackPaths = [
'/auth/callback',
'/auth/linuxdo/callback',
'/auth/oidc/callback',
'/auth/wechat/callback'
]
const pendingAuthPaths = ['/register', '/email-verify']
const isAllowed =
allowed.some((path) => toPath === path || toPath.startsWith(path)) ||
callbackPaths.includes(toPath) ||
(authState.hasPendingAuthSession && pendingAuthPaths.includes(toPath))
if (!isAllowed) {
return '/login'
}
}
@@ -115,7 +127,18 @@ function simulateGuard(
return null
}
const allowed = ['/login', '/key-usage', '/setup']
if (!allowed.some((path) => toPath === path || toPath.startsWith(path))) {
const callbackPaths = [
'/auth/callback',
'/auth/linuxdo/callback',
'/auth/oidc/callback',
'/auth/wechat/callback'
]
const pendingAuthPaths = ['/register', '/email-verify']
const isAllowed =
allowed.some((path) => toPath === path || toPath.startsWith(path)) ||
callbackPaths.includes(toPath) ||
(authState.hasPendingAuthSession && pendingAuthPaths.includes(toPath))
if (!isAllowed) {
return '/login'
}
}
@@ -136,6 +159,7 @@ describe('路由守卫逻辑', () => {
isAdmin: false,
isSimpleMode: false,
backendModeEnabled: false,
hasPendingAuthSession: false,
}
it('访问需要认证的页面重定向到 /login', () => {
@@ -167,6 +191,7 @@ describe('路由守卫逻辑', () => {
isAdmin: false,
isSimpleMode: false,
backendModeEnabled: false,
hasPendingAuthSession: false,
}
it('访问 /login 重定向到 /dashboard', () => {
@@ -203,6 +228,7 @@ describe('路由守卫逻辑', () => {
isAdmin: true,
isSimpleMode: false,
backendModeEnabled: false,
hasPendingAuthSession: false,
}
it('访问 /login 重定向到 /admin/dashboard', () => {
@@ -230,6 +256,7 @@ describe('路由守卫逻辑', () => {
isAdmin: false,
isSimpleMode: true,
backendModeEnabled: false,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/subscriptions', {}, authState)
expect(redirect).toBe('/dashboard')
@@ -241,6 +268,7 @@ describe('路由守卫逻辑', () => {
isAdmin: false,
isSimpleMode: true,
backendModeEnabled: false,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/redeem', {}, authState)
expect(redirect).toBe('/dashboard')
@@ -252,6 +280,7 @@ describe('路由守卫逻辑', () => {
isAdmin: true,
isSimpleMode: true,
backendModeEnabled: false,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/admin/groups', { requiresAdmin: true }, authState)
expect(redirect).toBe('/admin/dashboard')
@@ -263,6 +292,7 @@ describe('路由守卫逻辑', () => {
isAdmin: true,
isSimpleMode: true,
backendModeEnabled: false,
hasPendingAuthSession: false,
}
const redirect = simulateGuard(
'/admin/subscriptions',
@@ -278,6 +308,7 @@ describe('路由守卫逻辑', () => {
isAdmin: false,
isSimpleMode: true,
backendModeEnabled: false,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/dashboard', {}, authState)
expect(redirect).toBeNull()
@@ -289,6 +320,7 @@ describe('路由守卫逻辑', () => {
isAdmin: false,
isSimpleMode: true,
backendModeEnabled: false,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/keys', {}, authState)
expect(redirect).toBeNull()
@@ -302,6 +334,7 @@ describe('路由守卫逻辑', () => {
isAdmin: false,
isSimpleMode: false,
backendModeEnabled: true,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/home', { requiresAuth: false }, authState)
expect(redirect).toBe('/login')
@@ -313,6 +346,7 @@ describe('路由守卫逻辑', () => {
isAdmin: false,
isSimpleMode: false,
backendModeEnabled: true,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/login', { requiresAuth: false }, authState)
expect(redirect).toBeNull()
@@ -324,6 +358,7 @@ describe('路由守卫逻辑', () => {
isAdmin: false,
isSimpleMode: false,
backendModeEnabled: true,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/key-usage', { requiresAuth: false }, authState)
expect(redirect).toBeNull()
@@ -335,6 +370,7 @@ describe('路由守卫逻辑', () => {
isAdmin: false,
isSimpleMode: false,
backendModeEnabled: true,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/setup', { requiresAuth: false }, authState)
expect(redirect).toBeNull()
@@ -346,6 +382,7 @@ describe('路由守卫逻辑', () => {
isAdmin: true,
isSimpleMode: false,
backendModeEnabled: true,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/admin/dashboard', { requiresAdmin: true }, authState)
expect(redirect).toBeNull()
@@ -357,6 +394,7 @@ describe('路由守卫逻辑', () => {
isAdmin: true,
isSimpleMode: false,
backendModeEnabled: true,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/login', { requiresAuth: false }, authState)
expect(redirect).toBe('/admin/dashboard')
@@ -368,6 +406,7 @@ describe('路由守卫逻辑', () => {
isAdmin: false,
isSimpleMode: false,
backendModeEnabled: true,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/dashboard', {}, authState)
expect(redirect).toBe('/login')
@@ -379,6 +418,7 @@ describe('路由守卫逻辑', () => {
isAdmin: false,
isSimpleMode: false,
backendModeEnabled: true,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/login', { requiresAuth: false }, authState)
expect(redirect).toBeNull()
@@ -390,9 +430,46 @@ describe('路由守卫逻辑', () => {
isAdmin: false,
isSimpleMode: false,
backendModeEnabled: true,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/key-usage', { requiresAuth: false }, authState)
expect(redirect).toBeNull()
})
it('unauthenticated: callback routes are allowed', () => {
const authState: MockAuthState = {
isAuthenticated: false,
isAdmin: false,
isSimpleMode: false,
backendModeEnabled: true,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/auth/wechat/callback', { requiresAuth: false }, authState)
expect(redirect).toBeNull()
})
it('unauthenticated: /register is allowed when a pending auth session exists', () => {
const authState: MockAuthState = {
isAuthenticated: false,
isAdmin: false,
isSimpleMode: false,
backendModeEnabled: true,
hasPendingAuthSession: true,
}
const redirect = simulateGuard('/register', { requiresAuth: false }, authState)
expect(redirect).toBeNull()
})
it('unauthenticated: /email-verify is blocked without a pending auth session', () => {
const authState: MockAuthState = {
isAuthenticated: false,
isAdmin: false,
isSimpleMode: false,
backendModeEnabled: true,
hasPendingAuthSession: false,
}
const redirect = simulateGuard('/email-verify', { requiresAuth: false }, authState)
expect(redirect).toBe('/login')
})
})
})

View File

@@ -341,6 +341,16 @@ const routes: RouteRecordRaw[] = [
descriptionKey: 'admin.users.description'
}
},
{
path: '/admin/users/auth-identity-migration-reports',
name: 'AdminAuthIdentityMigrationReports',
component: () => import('@/views/admin/AuthIdentityMigrationReportsView.vue'),
meta: {
requiresAuth: true,
requiresAdmin: true,
title: 'Auth Identity Migration Reports'
}
},
{
path: '/admin/groups',
name: 'AdminGroups',
@@ -538,6 +548,29 @@ const navigationLoading = useNavigationLoadingState()
// 延迟初始化预加载,传入 router 实例
let routePrefetch: ReturnType<typeof useRoutePrefetch> | null = null
const BACKEND_MODE_ALLOWED_PATHS = ['/login', '/key-usage', '/setup']
const BACKEND_MODE_CALLBACK_PATHS = [
'/auth/callback',
'/auth/linuxdo/callback',
'/auth/oidc/callback',
'/auth/wechat/callback'
]
const BACKEND_MODE_PENDING_AUTH_PATHS = ['/register', '/email-verify']
function isBackendModePublicRouteAllowed(path: string, hasPendingAuthSession: boolean): boolean {
if (BACKEND_MODE_ALLOWED_PATHS.some((allowedPath) => path === allowedPath || path.startsWith(allowedPath))) {
return true
}
if (BACKEND_MODE_CALLBACK_PATHS.some((callbackPath) => path === callbackPath)) {
return true
}
if (hasPendingAuthSession && BACKEND_MODE_PENDING_AUTH_PATHS.some((allowedPath) => path === allowedPath)) {
return true
}
return false
}
router.beforeEach((to, _from, next) => {
// 开始导航加载状态
@@ -590,7 +623,7 @@ router.beforeEach((to, _from, next) => {
}
// Backend mode: block public pages for unauthenticated users (except login, key-usage, setup)
if (appStore.backendModeEnabled && !authStore.isAuthenticated) {
const isAllowed = BACKEND_MODE_ALLOWED_PATHS.some((p) => to.path === p || to.path.startsWith(p))
const isAllowed = isBackendModePublicRouteAllowed(to.path, authStore.hasPendingAuthSession)
if (!isAllowed) {
next('/login')
return
@@ -650,7 +683,7 @@ router.beforeEach((to, _from, next) => {
next()
return
}
const isAllowed = BACKEND_MODE_ALLOWED_PATHS.some((p) => to.path === p || to.path.startsWith(p))
const isAllowed = isBackendModePublicRouteAllowed(to.path, authStore.hasPendingAuthSession)
if (!isAllowed) {
next('/login')
return