- 同步版本号:VERSION / backend pom / admin-web / miniapp 均升至 1.1.0 - 合并本轮代码审查修复(详见 docs/CODE-REVIEW-2026-09-24.md): - 后端:微信登录真实实现 RealWechatLoginProvider、实名核验真实实现 RealIdentityVerificationProvider(本地强校验 + 可配置第三方 + fail-closed/fail-fast) - 照片直传落库端点 POST /me/photos/confirm(fileKey 归属校验 + 对象存在性校验后 savePhoto) - 刷新 Token Redis TTL 修正(getExpirationMs()*2 的 4h → refreshExpirationMs 的 14 天) - 管理端 token 改为 HttpOnly Cookie + silent-refresh,移除 localStorage 明文存储与 30+ 处手工注入 - 小程序:红娘接单鉴权头错位修复、serviceType 中文映射、生产域名可配置 - 隔离本地产物(.workbuddy/、*.bundle、*.dump、secrets、application-dev.yml 等已被 .gitignore 覆盖) 未验证项(打标签 ≠ 生产发布): - 后端编译 / 单测未在本环境执行(Maven 慢、Docker DNS 受限) - 管理端 登录→刷新→登出 端到端、小程序真机走查待人工验收 - 生产部署需注入 WECHAT_APP_ID / WECHAT_APP_SECRET、VERIFICATION_API_URL / VERIFICATION_API_KEY 与小程序生产域名
92 lines
3.4 KiB
TypeScript
92 lines
3.4 KiB
TypeScript
import { defineStore } from 'pinia'
|
||
import { ref, computed } from 'vue'
|
||
import { post } from '@/api/client'
|
||
import { setAccessToken, clearAccessToken } from '@/utils/token'
|
||
|
||
interface OperatorInfo {
|
||
operatorId: string
|
||
name: string
|
||
username: string
|
||
permissions: string[]
|
||
organizationId: string | null
|
||
}
|
||
|
||
const previewRolePermissions: Record<string, string[]> = {
|
||
reviewer: ['review.view', 'user.view'],
|
||
counselor: ['counseling.service.view', 'referral.manage'],
|
||
station: ['service_location.view', 'service_appointment.view'],
|
||
operator: ['review.view', 'user.view', 'organization.manage', 'permission.manage', 'activity.review_registration', 'risk.report.view', 'risk.report.process', 'analytics.view', 'banner.view', 'content.view', 'service_location.view', 'service_appointment.view', 'service_program.manage', 'counseling.service.view', 'referral.manage'],
|
||
}
|
||
|
||
export const useAuthStore = defineStore('auth', () => {
|
||
const previewParams = import.meta.env.DEV ? new URLSearchParams(window.location.search) : null
|
||
const previewRole = previewParams?.get('preview') || ''
|
||
const previewOperator: OperatorInfo | null = previewRolePermissions[previewRole]
|
||
? { operatorId: 'ui-preview', name: '界面验收员', username: 'preview', permissions: previewRolePermissions[previewRole], organizationId: null }
|
||
: null
|
||
|
||
const token = ref<string>(previewOperator ? 'ui-preview-only' : '')
|
||
const operator = ref<OperatorInfo | null>(previewOperator)
|
||
let sessionInitialized = false
|
||
|
||
const isLoggedIn = computed(() => !!token.value && !!operator.value)
|
||
const permissions = computed(() => operator.value?.permissions || [])
|
||
|
||
function applyAuthData(data: any) {
|
||
token.value = data.accessToken || ''
|
||
operator.value = {
|
||
operatorId: data.operatorId,
|
||
name: data.name,
|
||
username: data.username,
|
||
permissions: data.permissions || [],
|
||
organizationId: data.organizationId ?? null,
|
||
}
|
||
// 同步内存态令牌(供 axios 拦截器注入 Authorization 头;生产同源场景 Cookie 也会自动携带)
|
||
setAccessToken(token.value)
|
||
}
|
||
|
||
async function login(username: string, password: string) {
|
||
const res: any = await post('/auth/operator/login', { username, password })
|
||
if (res.success) {
|
||
applyAuthData(res.data)
|
||
return res.data
|
||
}
|
||
throw new Error(res.message || '登录失败')
|
||
}
|
||
|
||
/**
|
||
* 静默恢复会话:页面刷新后内存态清空,凭 HttpOnly refresh Cookie(dy_op_rt)刷新令牌,
|
||
* 无需用户重新登录。refresh token 不落地、不可被 JS 读取,抗 XSS。
|
||
* 开发跨端口代理下 Cookie 不共享,刷新将失败并回落到登录页(符合预期)。
|
||
*/
|
||
async function initializeSession(): Promise<boolean> {
|
||
if (sessionInitialized) return isLoggedIn.value
|
||
sessionInitialized = true
|
||
if (token.value) return true
|
||
try {
|
||
const res: any = await post('/auth/operator/refresh', {})
|
||
if (res.success) {
|
||
applyAuthData(res.data)
|
||
return true
|
||
}
|
||
} catch {
|
||
// 未登录或 refresh 失效,交由路由守卫跳转登录页
|
||
}
|
||
return false
|
||
}
|
||
|
||
function logout() {
|
||
// 尽力通知后端注销并清除 Cookie(best-effort)
|
||
try {
|
||
post('/auth/operator/logout', {}).catch(() => {})
|
||
} catch {
|
||
// ignore
|
||
}
|
||
token.value = ''
|
||
operator.value = null
|
||
clearAccessToken()
|
||
}
|
||
|
||
return { token, operator, isLoggedIn, permissions, login, logout, initializeSession }
|
||
})
|