feat(auth): add opaque bearer sessions

This commit is contained in:
caoxiaozhu
2026-07-13 14:45:36 +08:00
parent 661990b27b
commit 653eda0596
59 changed files with 1408 additions and 408 deletions

View File

@@ -1,7 +1,10 @@
import { normalizeAuthUserSnapshot, resolveAuthUserAdminFlag } from '../utils/authUser.js'
import {
buildBearerHeaders,
notifyAuthSessionExpired,
readAuthAccessToken
} from '../utils/authSessionStorage.js'
const API_BASE_STORAGE_KEY = 'x-financial-api-base-url'
const AUTH_USER_STORAGE_KEY = 'x-financial-auth-user'
function isHeaderValueSafe(value) {
const normalized = String(value || '').trim()
@@ -33,85 +36,6 @@ export function pickSafeHeaderValue(value, fallback = '') {
return ''
}
function readCurrentUserHeaders() {
if (typeof window === 'undefined') {
return {}
}
const raw = window.sessionStorage.getItem(AUTH_USER_STORAGE_KEY)
if (!raw) {
return {}
}
try {
const payload = JSON.parse(raw)
const user = normalizeAuthUserSnapshot(payload)
const username = user.username
const name = user.name || username
const roleCodes = user.roleCodes
const isAdmin = resolveAuthUserAdminFlag(payload, roleCodes)
const department = user.department || user.departmentName
const costCenter = user.costCenter
const position = user.position
const grade = user.grade
const employeeNo = user.employeeNo
const managerName = user.managerName
const safeUsername = pickSafeHeaderValue(username)
const safeName = pickSafeHeaderValue(name)
const safeDepartment = pickSafeHeaderValue(department)
const safeCostCenter = pickSafeHeaderValue(costCenter)
const safePosition = pickSafeHeaderValue(position)
const safeGrade = pickSafeHeaderValue(grade)
const safeEmployeeNo = pickSafeHeaderValue(employeeNo)
const safeManagerName = pickSafeHeaderValue(managerName)
if (!safeUsername && !safeName) {
return {}
}
const headers = {
'x-auth-role-codes': roleCodes.join(','),
'x-auth-is-admin': String(isAdmin)
}
if (safeUsername) {
headers['x-auth-username'] = safeUsername
}
if (safeName) {
headers['x-auth-name'] = safeName
}
if (safeDepartment) {
headers['x-auth-department'] = safeDepartment
}
if (safeCostCenter) {
headers['x-auth-cost-center'] = safeCostCenter
}
if (safePosition) {
headers['x-auth-position'] = safePosition
}
if (safeGrade) {
headers['x-auth-grade'] = safeGrade
}
if (safeEmployeeNo) {
headers['x-auth-employee-no'] = safeEmployeeNo
}
if (safeManagerName) {
headers['x-auth-manager-name'] = safeManagerName
}
return headers
} catch {
return {}
}
}
function normalizeApiBaseUrl(value) {
return String(value || '/api/v1').replace(/\/$/, '')
}
@@ -260,8 +184,20 @@ function sanitizeHeaders(headers) {
return nextHeaders
}
function buildApiResponseError(response, payload, { auth, handleUnauthorized }) {
const error = new Error(resolveErrorMessage(payload))
error.status = response.status
if (response.status === 401 && auth && handleUnauthorized && readAuthAccessToken()) {
error.code = 'AUTH_SESSION_EXPIRED'
notifyAuthSessionExpired()
}
return error
}
export async function apiRequest(path, options = {}) {
const {
auth = true,
handleUnauthorized = true,
contentType = 'application/json',
responseType = 'json',
headers: customHeaders,
@@ -270,10 +206,15 @@ export async function apiRequest(path, options = {}) {
...fetchOptions
} = options
const headers = sanitizeHeaders({
...readCurrentUserHeaders(),
...(customHeaders || {})
})
const headers = sanitizeHeaders(customHeaders || {})
if (auth) {
Object.keys(headers).forEach((key) => {
if (key.toLowerCase() === 'authorization') {
delete headers[key]
}
})
Object.assign(headers, buildBearerHeaders())
}
if (contentType !== null && typeof headers['Content-Type'] === 'undefined') {
headers['Content-Type'] = contentType
@@ -327,7 +268,7 @@ export async function apiRequest(path, options = {}) {
payload = null
}
throw new Error(resolveErrorMessage(payload))
throw buildApiResponseError(response, payload, { auth, handleUnauthorized })
}
return response.blob()
@@ -341,7 +282,7 @@ export async function apiRequest(path, options = {}) {
}
if (!response.ok) {
throw new Error(resolveErrorMessage(payload))
throw buildApiResponseError(response, payload, { auth, handleUnauthorized })
}
return payload