oclaw/uds-auth/lib/uds/validator.js
2026-09-08 00:28:58 +08:00

190 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* UDS Credential Validator with enhanced user info support
*
* Mode 1 (verify): Call UDS verification API to validate token
* Mode 2 (trust): Trust the credentials from request headers directly (for internal network)
*/
export class UdsValidator {
/**
* @param {object} config - Validator configuration
*/
constructor(config = {}) {
this.empNoHeader = (config.empNoHeader || 'X-Emp-No').toLowerCase()
this.authValueHeader = (config.authValueHeader || 'X-Auth-Value').toLowerCase()
this.langIdHeader = (config.langIdHeader || 'X-Lang-Id').toLowerCase()
this.authMode = config.authMode || 'trust'
this.hrApiUrl = config.hrApiUrl || 'https://icosg.dt.zte.com.cn/ihol/usercenter/pginfo/usercenter/plain/queryPersonGeneralInfo'
}
/**
* 从 Cookie 头中解析单个 cookie 值
*/
static parseCookie(cookieHeader, name) {
if (!cookieHeader || typeof cookieHeader !== 'string') return null
const pairs = cookieHeader.split(';')
for (const pair of pairs) {
const idx = pair.indexOf('=')
if (idx < 0) continue
const k = pair.slice(0, idx).trim()
if (k === name) {
return decodeURIComponent(pair.slice(idx + 1).trim())
}
}
return null
}
/**
* Extract UDS credentials from HTTP request headers.
* 同时支持 cookie(QR 登录流程只设 cookie 不设 header)。
* 优先级: header > cookie
*/
extractCredentials(request) {
const headers = request.headers || {}
const getHeader = (name) => {
if (headers[name] !== undefined) return headers[name]
const lowerName = name.toLowerCase()
if (headers[lowerName] !== undefined) return headers[lowerName]
for (const key of Object.keys(headers)) {
if (key.toLowerCase() === lowerName) return headers[key]
}
return undefined
}
const cookieHeader = headers.cookie || ''
const empNo = getHeader(this.empNoHeader)
|| UdsValidator.parseCookie(cookieHeader, 'PORTALSSOUser')
|| UdsValidator.parseCookie(cookieHeader, 'ZTEDPGSSOUser')
const token = getHeader(this.authValueHeader)
|| UdsValidator.parseCookie(cookieHeader, 'PORTALSSOCookie')
|| UdsValidator.parseCookie(cookieHeader, 'ZTEDPGSSOCookie')
const lang = getHeader(this.langIdHeader)
|| UdsValidator.parseCookie(cookieHeader, 'PORTALSSOLanguage')
|| UdsValidator.parseCookie(cookieHeader, 'ZTEDPGSSOLanguage')
|| 'zh-CN'
if (!empNo || !token) {
return null
}
return {
empNo: String(empNo).trim(),
token: String(token).trim(),
lang: String(lang).trim(),
}
}
/**
* Validate extracted credentials
*/
validateCredentials(credentials) {
if (!credentials) return false
if (!credentials.empNo || typeof credentials.empNo !== 'string') return false
if (!credentials.token || typeof credentials.token !== 'string') return false
// Basic format validation
if (credentials.empNo.length < 5) return false
if (credentials.token.length < 10) return false
return true
}
/**
* Call HR API to get employee details
*/
async fetchEmployeeInfo(empNo) {
try {
const response = await fetch(this.hrApiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
bo: {
rows: [{ employeeNO: empNo }],
pageNum: 1,
pageSize: 1,
}
})
})
if (!response.ok) {
return null
}
const data = await response.json()
const rows = data?.bo?.rows
if (rows && rows.length > 0) {
const emp = rows[0]
return {
userId: emp.employeeNO || emp.empUIID || empNo,
username: emp.empName || empNo,
displayName: emp.empName || empNo,
department: emp.orgNamePath || emp.pro || '',
organization: emp.orgNamePath || '',
role: 'user',
permissions: [],
empNo: emp.employeeNO || empNo,
}
}
return null
} catch (err) {
console.error('[UdsValidator] HR API call failed:', err.message)
return null
}
}
/**
* Build user context from credentials
*/
buildUserContext(credentialsOrResponse, isUdsResponse = false) {
const now = new Date().toISOString()
let userData
if (isUdsResponse) {
userData = credentialsOrResponse
} else {
userData = {
userId: credentialsOrResponse.empNo,
username: credentialsOrResponse.empNo,
empNo: credentialsOrResponse.empNo,
token: credentialsOrResponse.token,
lang: credentialsOrResponse.lang,
}
}
return {
userId: userData.userId || userData.empno || userData.empNo || '',
username: userData.username || userData.name || userData.empNo || userData.empno || '',
displayName: userData.displayName || userData.username || userData.name || '',
department: userData.department || userData.dept || '',
organization: userData.organization || userData.org || '',
role: userData.role || 'user',
permissions: userData.permissions || [],
email: userData.email || '',
phone: userData.phone || '',
empNo: userData.empNo || userData.empno || '',
token: userData.token || '',
lang: userData.lang || 'zh-CN',
authenticatedAt: now,
lastActiveAt: now,
sessionCreatedAt: now,
isAuthenticated: true,
authMode: this.authMode,
}
}
/**
* Build error response
*/
buildUnauthResponse(loginUrl) {
return {
code: 401,
message: 'UDS authentication required',
login_url: loginUrl,
}
}
}