初次提交
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
import { navigateTo, useState } from '#app'
|
||||
import type { User } from '~~/types'
|
||||
|
||||
interface LoginResponse {
|
||||
success: boolean
|
||||
user?: User
|
||||
requires2FA?: boolean
|
||||
userId?: number
|
||||
methods?: string[]
|
||||
tempToken?: string // 预认证临时令牌
|
||||
}
|
||||
|
||||
export const useAuth = () => {
|
||||
const user = useState<User | null>('user', () => null)
|
||||
const token = useState<string | null>('token', () => null)
|
||||
const isAuthenticated = useState<boolean>('isAuthenticated', () => false)
|
||||
const isAdmin = useState<boolean>('isAdmin', () => false)
|
||||
const loading = useState<boolean>('loading', () => false)
|
||||
|
||||
const clearAuthState = () => {
|
||||
user.value = null
|
||||
token.value = null
|
||||
isAuthenticated.value = false
|
||||
isAdmin.value = false
|
||||
}
|
||||
|
||||
const setAuthState = (loggedInUser: User) => {
|
||||
token.value = 'cookie-based'
|
||||
user.value = loggedInUser
|
||||
isAuthenticated.value = true
|
||||
isAdmin.value = ['ADMIN', 'SUPER_ADMIN', 'SONG_ADMIN'].includes(loggedInUser.role)
|
||||
}
|
||||
|
||||
const initAuth = async () => {
|
||||
// 客户端执行
|
||||
if (typeof window === 'undefined' || import.meta.server) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 如果已认证,直接返回缓存的用户信息
|
||||
if (isAuthenticated.value && user.value) {
|
||||
return user.value
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await $fetch<{ user: User }>('/api/auth/verify', {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache'
|
||||
}
|
||||
})
|
||||
|
||||
if (data && data.user) {
|
||||
user.value = data.user
|
||||
isAuthenticated.value = true
|
||||
isAdmin.value = ['ADMIN', 'SUPER_ADMIN', 'SONG_ADMIN'].includes(data.user.role)
|
||||
token.value = 'cookie-based'
|
||||
return data.user
|
||||
} else {
|
||||
clearAuthState()
|
||||
return null
|
||||
}
|
||||
} catch (error: any) {
|
||||
const hadAuth = isAuthenticated.value
|
||||
|
||||
// 只有当之前是已认证状态,且接口明确返回401(Token无效/过期),才进行清理和跳转
|
||||
if (hadAuth && error.statusCode === 401) {
|
||||
clearAuthState()
|
||||
// Token失效,重定向到登录页
|
||||
await navigateTo('/login?error=SessionExpired')
|
||||
} else if (!hadAuth && error.statusCode === 401) {
|
||||
// 未登录状态下的 401,仅确保状态清理,不跳转
|
||||
clearAuthState()
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const login = async (username: string, password: string) => {
|
||||
const response = await $fetch<LoginResponse>('/api/auth/login', {
|
||||
method: 'POST',
|
||||
body: { username, password }
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
if (response.requires2FA) {
|
||||
return response
|
||||
}
|
||||
|
||||
if (response.user) {
|
||||
setAuthState(response.user)
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('登录响应格式错误')
|
||||
}
|
||||
|
||||
const verify2FA = async (userId: number, code: string, type: 'totp' | 'email', tempToken?: string) => {
|
||||
const response = await $fetch<{ success: boolean; user: User }>('/api/auth/2fa/verify', {
|
||||
method: 'POST',
|
||||
body: { userId, code, type, token: tempToken }
|
||||
})
|
||||
|
||||
if (response.success && response.user) {
|
||||
setAuthState(response.user)
|
||||
return response
|
||||
}
|
||||
throw new Error('验证失败')
|
||||
}
|
||||
|
||||
const changePassword = async (currentPassword: string, newPassword: string) => {
|
||||
loading.value = true
|
||||
try {
|
||||
await $fetch('/api/auth/change-password', {
|
||||
method: 'POST',
|
||||
body: { currentPassword, newPassword }
|
||||
})
|
||||
} catch (error: any) {
|
||||
// 处理FetchError,提取错误信息
|
||||
if (error.data && error.data.statusMessage) {
|
||||
throw new Error(error.data.statusMessage)
|
||||
} else if (error.data && error.data.message) {
|
||||
throw new Error(error.data.message)
|
||||
} else if (error.statusMessage) {
|
||||
throw new Error(error.statusMessage)
|
||||
} else if (error.message) {
|
||||
throw new Error(error.message)
|
||||
} else {
|
||||
throw new Error('密码修改失败,请重试')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const setInitialPassword = async (newPassword: string) => {
|
||||
loading.value = true
|
||||
try {
|
||||
await $fetch('/api/auth/set-initial-password', {
|
||||
method: 'POST',
|
||||
body: { newPassword }
|
||||
})
|
||||
if (user.value) {
|
||||
user.value.needsPasswordChange = false
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const refreshUser = async () => {
|
||||
const data = await $fetch<{ user: User }>('/api/auth/verify')
|
||||
if (data && data.user) {
|
||||
user.value = data.user
|
||||
isAuthenticated.value = true
|
||||
isAdmin.value = ['ADMIN', 'SUPER_ADMIN', 'SONG_ADMIN'].includes(data.user.role)
|
||||
}
|
||||
}
|
||||
|
||||
const register = async (data: Record<string, any>) => {
|
||||
const response = await $fetch<LoginResponse>('/api/auth/register', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
})
|
||||
if (response.success && response.user) {
|
||||
setAuthState(response.user)
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
const logout = async (redirect = true) => {
|
||||
try {
|
||||
await $fetch('/api/auth/logout', { method: 'POST' })
|
||||
} catch (error) {
|
||||
// 忽略登出错误
|
||||
}
|
||||
|
||||
clearAuthState()
|
||||
|
||||
if (import.meta.client && redirect) {
|
||||
await navigateTo('/')
|
||||
}
|
||||
}
|
||||
|
||||
const getAuthConfig = () => {
|
||||
// 返回认证配置
|
||||
return {
|
||||
credentials: 'include' as RequestCredentials
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
token,
|
||||
isAuthenticated,
|
||||
isAdmin,
|
||||
loading,
|
||||
login,
|
||||
verify2FA,
|
||||
register,
|
||||
logout,
|
||||
changePassword,
|
||||
setInitialPassword,
|
||||
refreshUser,
|
||||
initAuth,
|
||||
getAuthConfig
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user