366f1d667e
- login/session: 对接后端登录、鉴权恢复和首次改密 - api/store: 恢复 BPMN XML 真实接口并携带 Bearer token - layout/settings: 展示真实角色、审计 JSON 和通知平台配置文档
172 lines
3.8 KiB
TypeScript
172 lines
3.8 KiB
TypeScript
/**
|
|
* 维护后端真实登录态,前端只保存 DevOps API token,不保存任何第三方密钥。
|
|
*/
|
|
import { computed, ref } from 'vue'
|
|
import { defineStore } from 'pinia'
|
|
import { devopsApi } from '../services/devopsApi'
|
|
import { setAuthToken } from '../services/http'
|
|
import type {
|
|
AuthenticatedUser,
|
|
ChangePasswordPayload,
|
|
LoginPayload,
|
|
} from '../types/devops'
|
|
|
|
const STORAGE_KEY = 'devops-platform-session'
|
|
|
|
type StoredSession = {
|
|
token: string
|
|
expiresAt: string
|
|
user: AuthenticatedUser
|
|
}
|
|
|
|
export const useSessionStore = defineStore('session', () => {
|
|
const restored = loadStoredSession()
|
|
const operator = ref(restored?.user.account ?? '')
|
|
const displayName = ref(restored?.user.displayName ?? '')
|
|
const role = ref<AuthenticatedUser['role'] | ''>(restored?.user.role ?? '')
|
|
const token = ref(restored?.token ?? '')
|
|
const expiresAt = ref(restored?.expiresAt ?? '')
|
|
const currentUser = ref<AuthenticatedUser | null>(restored?.user ?? null)
|
|
const bootstrapped = ref(false)
|
|
|
|
setAuthToken(token.value)
|
|
|
|
const isAuthenticated = computed(() => Boolean(token.value && currentUser.value))
|
|
const mustChangePassword = computed(() =>
|
|
Boolean(currentUser.value?.mustChangePassword),
|
|
)
|
|
const roleLabel = computed(() => {
|
|
if (role.value === 'super_admin') {
|
|
return '超级管理员'
|
|
}
|
|
|
|
if (role.value === 'member') {
|
|
return '普通成员'
|
|
}
|
|
|
|
return '未登录'
|
|
})
|
|
|
|
async function bootstrap(): Promise<boolean> {
|
|
if (bootstrapped.value) {
|
|
return isAuthenticated.value
|
|
}
|
|
|
|
bootstrapped.value = true
|
|
|
|
if (!token.value) {
|
|
return false
|
|
}
|
|
|
|
try {
|
|
const user = await devopsApi.getCurrentUser()
|
|
applyUser(user)
|
|
persist()
|
|
return true
|
|
} catch {
|
|
clearSession()
|
|
return false
|
|
}
|
|
}
|
|
|
|
async function login(payload: LoginPayload) {
|
|
const result = await devopsApi.login(payload)
|
|
token.value = result.token
|
|
expiresAt.value = result.expiresAt
|
|
setAuthToken(result.token)
|
|
applyUser(result.user)
|
|
persist()
|
|
bootstrapped.value = true
|
|
return result.user
|
|
}
|
|
|
|
async function changePassword(payload: ChangePasswordPayload) {
|
|
const user = await devopsApi.changePassword(payload)
|
|
applyUser(user)
|
|
persist()
|
|
return user
|
|
}
|
|
|
|
async function logout() {
|
|
if (token.value) {
|
|
try {
|
|
await devopsApi.logout()
|
|
} catch {
|
|
// 退出以清理本地登录态为准,后端当前无状态 token 无需强依赖 logout 成功。
|
|
}
|
|
}
|
|
|
|
clearSession()
|
|
}
|
|
|
|
function applyUser(user: AuthenticatedUser) {
|
|
currentUser.value = user
|
|
operator.value = user.account
|
|
displayName.value = user.displayName
|
|
role.value = user.role
|
|
}
|
|
|
|
function persist() {
|
|
if (!token.value || !currentUser.value) {
|
|
localStorage.removeItem(STORAGE_KEY)
|
|
return
|
|
}
|
|
|
|
const stored: StoredSession = {
|
|
token: token.value,
|
|
expiresAt: expiresAt.value,
|
|
user: currentUser.value,
|
|
}
|
|
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored))
|
|
}
|
|
|
|
function clearSession() {
|
|
operator.value = ''
|
|
displayName.value = ''
|
|
role.value = ''
|
|
token.value = ''
|
|
expiresAt.value = ''
|
|
currentUser.value = null
|
|
setAuthToken()
|
|
localStorage.removeItem(STORAGE_KEY)
|
|
}
|
|
|
|
return {
|
|
operator,
|
|
displayName,
|
|
role,
|
|
roleLabel,
|
|
token,
|
|
expiresAt,
|
|
currentUser,
|
|
isAuthenticated,
|
|
mustChangePassword,
|
|
bootstrap,
|
|
login,
|
|
changePassword,
|
|
logout,
|
|
}
|
|
})
|
|
|
|
function loadStoredSession(): StoredSession | null {
|
|
try {
|
|
const rawValue = localStorage.getItem(STORAGE_KEY)
|
|
|
|
if (!rawValue) {
|
|
return null
|
|
}
|
|
|
|
const parsed = JSON.parse(rawValue) as StoredSession
|
|
|
|
if (!parsed.token || !parsed.user?.account) {
|
|
return null
|
|
}
|
|
|
|
return parsed
|
|
} catch {
|
|
localStorage.removeItem(STORAGE_KEY)
|
|
return null
|
|
}
|
|
}
|