fix(frontend): restore pending auth session flow
This commit is contained in:
@@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user