feat(auth): 接入 MEDISIGN 登录接口
- 新增认证 API、类型与本地会话存储 - 登录页改为真实账号密码登录并移除验证码 - 请求拦截器改用 X-Token 并处理 401 跳转 - 添加 Vite /api 开发代理配置
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import type { AuthenticatedUser, LoginResponse } from '@/types/auth'
|
||||
|
||||
export const AUTH_TOKEN_KEY = 'token'
|
||||
export const AUTH_USER_KEY = 'userInfo'
|
||||
export const AUTH_EXPIRES_AT_KEY = 'tokenExpiresAt'
|
||||
|
||||
export interface StoredAuthSession {
|
||||
token: string
|
||||
user: AuthenticatedUser | null
|
||||
expiresAt: string | null
|
||||
}
|
||||
|
||||
function readStoredUser(): AuthenticatedUser | null {
|
||||
const rawUser = localStorage.getItem(AUTH_USER_KEY)
|
||||
|
||||
if (!rawUser) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(rawUser) as AuthenticatedUser
|
||||
} catch {
|
||||
localStorage.removeItem(AUTH_USER_KEY)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function readStoredAuthSession(): StoredAuthSession | null {
|
||||
const token = localStorage.getItem(AUTH_TOKEN_KEY)
|
||||
|
||||
if (!token) {
|
||||
return null
|
||||
}
|
||||
|
||||
const expiresAt = localStorage.getItem(AUTH_EXPIRES_AT_KEY)
|
||||
|
||||
if (expiresAt && Date.parse(expiresAt) <= Date.now()) {
|
||||
clearAuthStorage()
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
user: readStoredUser(),
|
||||
expiresAt,
|
||||
}
|
||||
}
|
||||
|
||||
export function saveAuthSession(session: LoginResponse) {
|
||||
localStorage.setItem(AUTH_TOKEN_KEY, session.token)
|
||||
localStorage.setItem(AUTH_EXPIRES_AT_KEY, session.expiresAt)
|
||||
localStorage.setItem(AUTH_USER_KEY, JSON.stringify(session.user))
|
||||
}
|
||||
|
||||
export function clearAuthStorage() {
|
||||
localStorage.removeItem(AUTH_TOKEN_KEY)
|
||||
localStorage.removeItem(AUTH_EXPIRES_AT_KEY)
|
||||
localStorage.removeItem(AUTH_USER_KEY)
|
||||
}
|
||||
@@ -5,23 +5,38 @@ import axios, {
|
||||
type InternalAxiosRequestConfig,
|
||||
} from 'axios'
|
||||
|
||||
import { clearAuthStorage, AUTH_TOKEN_KEY } from '@/utils/auth-storage'
|
||||
|
||||
const DEFAULT_TIMEOUT = 15_000
|
||||
const DEFAULT_API_BASE_URL = '/api'
|
||||
|
||||
function redirectToLogin() {
|
||||
if (typeof window === 'undefined' || window.location.pathname === '/login') {
|
||||
return
|
||||
}
|
||||
|
||||
const redirect = `${window.location.pathname}${window.location.search}${window.location.hash}`
|
||||
window.location.assign(`/login?redirect=${encodeURIComponent(redirect)}`)
|
||||
}
|
||||
|
||||
class Request {
|
||||
private readonly instance: AxiosInstance
|
||||
|
||||
constructor(baseURL = import.meta.env.VITE_API_BASE_URL || '/api', timeout = DEFAULT_TIMEOUT) {
|
||||
constructor(
|
||||
baseURL = import.meta.env.VITE_API_BASE_URL || DEFAULT_API_BASE_URL,
|
||||
timeout = DEFAULT_TIMEOUT,
|
||||
) {
|
||||
this.instance = axios.create({
|
||||
baseURL,
|
||||
timeout,
|
||||
withCredentials: true,
|
||||
withCredentials: false,
|
||||
})
|
||||
|
||||
this.instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
||||
const token = localStorage.getItem('token')
|
||||
const token = localStorage.getItem(AUTH_TOKEN_KEY)
|
||||
|
||||
if (token) {
|
||||
config.headers.set('Authorization', 'Bearer ' + token)
|
||||
config.headers.set('X-Token', token)
|
||||
}
|
||||
|
||||
return config
|
||||
@@ -29,7 +44,14 @@ class Request {
|
||||
|
||||
this.instance.interceptors.response.use(
|
||||
(response: AxiosResponse) => response,
|
||||
(error: unknown) => Promise.reject(error),
|
||||
(error: unknown) => {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
||||
clearAuthStorage()
|
||||
redirectToLogin()
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user