mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 04:40:45 +08:00
94 lines
3.5 KiB
JavaScript
94 lines
3.5 KiB
JavaScript
import { UdsClient } from '../uds/client.js'
|
||
import { UdsValidator } from '../uds/validator.js'
|
||
import { ROLES, computePermissions } from '../roles.js'
|
||
|
||
/**
|
||
* auth-middleware: 从 PORTALSSOUser (empNo) 取用户,注入 role/permissions
|
||
*
|
||
* 三条路径:
|
||
* 1. 有 empNo + 有 session → 续期,注入
|
||
* 2. 有 empNo + 无 session → 创建 session + bootstrap role
|
||
* 3. 无 empNo → 未登录,交给下游
|
||
*/
|
||
export function createAuthMiddleware(config, sessionStore, rolesStore) {
|
||
const udsClient = new UdsClient(config.udsAuth)
|
||
const validatorConfig = {
|
||
...config.udsAuth,
|
||
authMode: config.udsAuth?.authMode || 'trust',
|
||
}
|
||
const udsValidator = new UdsValidator(validatorConfig)
|
||
const { cookieMaxAge, slidingExpiration, slidingInterval } = config.session
|
||
|
||
function extractEmpNo(cookieHeader) {
|
||
// 优先取 UDS 的 empNo,其次取 fallback administrator
|
||
const udsMatch = cookieHeader?.match(/(?:PORTALSSOUser|ZTEDPGSSOUser)=([^;]+)/)
|
||
if (udsMatch) return decodeURIComponent(udsMatch[1].trim())
|
||
const fbMatch = cookieHeader?.match(/UDS_FALLBACK_USER=([^;]+)/)
|
||
if (fbMatch) return decodeURIComponent(fbMatch[1].trim())
|
||
return null
|
||
}
|
||
|
||
async function authMiddleware(ctx, next) {
|
||
const { req } = ctx
|
||
const cookieHeader = req.headers.cookie || ''
|
||
const empNo = extractEmpNo(cookieHeader)
|
||
|
||
if (!empNo) return next()
|
||
|
||
// 1. 有 session → 续期
|
||
let userContext = await sessionStore.get(empNo)
|
||
if (userContext) {
|
||
if (slidingExpiration && userContext.lastActiveAt) {
|
||
const lastActive = new Date(userContext.lastActiveAt).getTime()
|
||
if (Date.now() - lastActive >= slidingInterval) {
|
||
userContext.lastActiveAt = new Date().toISOString()
|
||
await sessionStore.setex(empNo, Math.floor(cookieMaxAge / 1000), userContext)
|
||
}
|
||
}
|
||
} else {
|
||
// 2. 无 session → 创建
|
||
// 信任模式下,empNo 来自我们自己的 QR 登录流程设置的 cookie,
|
||
// 即使没有完整的 header 凭据也应建立会话。
|
||
const credentials = udsValidator.extractCredentials(req)
|
||
if (udsValidator.authMode === 'trust') {
|
||
// trust 模式:只要有 empNo 就建立会话;token 可选
|
||
userContext = udsValidator.buildUserContext(
|
||
credentials || { empNo, token: '', lang: 'zh-CN' },
|
||
false,
|
||
)
|
||
} else {
|
||
// verify 模式:必须有有效凭据
|
||
if (!credentials || !udsValidator.validateCredentials(credentials)) {
|
||
return next()
|
||
}
|
||
userContext = udsValidator.buildUserContext(credentials, false)
|
||
}
|
||
userContext.authenticatedAt = new Date().toISOString()
|
||
userContext.lastActiveAt = new Date().toISOString()
|
||
userContext.isAuthenticated = true
|
||
await sessionStore.setex(empNo, Math.floor(cookieMaxAge / 1000), userContext)
|
||
}
|
||
|
||
// 注入 role + permissions
|
||
const role = rolesStore.getRole(empNo)
|
||
ctx.userContext = userContext
|
||
ctx.empNo = empNo
|
||
ctx.role = role
|
||
ctx.permissions = computePermissions(role)
|
||
return next()
|
||
}
|
||
|
||
authMiddleware.udsClient = udsClient
|
||
authMiddleware.udsValidator = udsValidator
|
||
return authMiddleware
|
||
}
|
||
|
||
/**
|
||
* 权限守卫 — 拦截非 super_admin 的管理 API
|
||
* 用法: 在路由里 call guard(req, res, ctx, 'canManageUsers') → true 表示通过
|
||
*/
|
||
export function requirePermission(ctx, permission) {
|
||
if (!ctx?.permissions) return false
|
||
if (permission === 'super_admin') return ctx.role === ROLES.SUPER_ADMIN || ctx.role === ROLES.FALLBACK_ADMIN
|
||
return !!ctx.permissions[permission]
|
||
}
|