fix(认证): 修复首次登录改密跳转

This commit is contained in:
xy
2026-09-24 17:34:26 +08:00
parent 53c6f05969
commit f99bee4286
3 changed files with 111 additions and 16 deletions
+44 -10
View File
@@ -6,12 +6,27 @@ import ForbiddenView from '@/views/auth/ForbiddenView.vue'
import LoginView from '@/views/auth/LoginView.vue'
import MenuResourceView from '@/views/management/MenuResourceView.vue'
import { useLoginStore } from '@/stores/login'
import { ApiResponseError } from '@/utils/api-response'
import { readStoredAuthSession } from '@/utils/auth-storage'
function firstAuthorizedPath(loginStore: ReturnType<typeof useLoginStore>) {
return loginStore.firstAuthorizedPath ?? '/forbidden'
}
function safeRedirectPath(value: unknown) {
return typeof value === 'string' && value.startsWith('/') && !value.startsWith('//')
? value
: null
}
function isPasswordChangeRequired(error: unknown) {
return (
error instanceof ApiResponseError &&
String(error.code) === '40300' &&
error.message === '请先修改密码'
)
}
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
@@ -65,6 +80,7 @@ const router = createRouter({
router.beforeEach(async (to) => {
const loginStore = useLoginStore()
const requestedRedirect = safeRedirectPath(to.query.redirect)
if (to.name === 'login' && loginStore.isLoggedIn) {
if (!readStoredAuthSession()) {
@@ -72,9 +88,14 @@ router.beforeEach(async (to) => {
return true
}
return loginStore.user?.passwordChangeRequired
? { name: 'change-password' }
: firstAuthorizedPath(loginStore)
if (loginStore.user?.passwordChangeRequired) {
return {
name: 'change-password',
query: requestedRedirect ? { redirect: requestedRedirect } : undefined,
}
}
return requestedRedirect ?? firstAuthorizedPath(loginStore)
}
if (to.meta.requiresAuth && !loginStore.isLoggedIn) {
@@ -88,15 +109,22 @@ router.beforeEach(async (to) => {
return true
}
// The backend intentionally blocks /auth/me and /authorization/me until a
// forced password change is complete. The login response already supplies
// the authenticated user's requirement, so don't call those endpoints here.
if (to.name === 'change-password') {
return true
}
if (loginStore.user?.passwordChangeRequired) {
return { name: 'change-password', query: { redirect: to.fullPath } }
}
try {
const user = await loginStore.ensureCurrentUser()
if (user?.passwordChangeRequired && to.name !== 'change-password') {
return { name: 'change-password' }
}
if (!user?.passwordChangeRequired && to.name === 'change-password') {
return firstAuthorizedPath(loginStore)
if (user?.passwordChangeRequired) {
return { name: 'change-password', query: { redirect: to.fullPath } }
}
if (to.name === 'menu-resource') {
@@ -113,7 +141,13 @@ router.beforeEach(async (to) => {
}
return true
} catch {
} catch (error: unknown) {
// A forced-change response is an authenticated session state, not an
// expired session. Keep the token so the user can call the change endpoint.
if (isPasswordChangeRequired(error)) {
return { name: 'change-password', query: { redirect: to.fullPath } }
}
loginStore.clearSession()
return {
name: 'login',