Fix uds-auth qr-start 404 by registering route and top-level crypto import.

This commit is contained in:
oliver 2026-09-08 01:54:02 +08:00
parent 505f9dea3f
commit 3aca99ca93

View file

@ -311,11 +311,9 @@ async function handleConfigGet(req, res) {
} }
// Calculate verifyCode using Node.js crypto (reliable) // Calculate verifyCode using Node.js crypto (reliable)
import crypto from 'node:crypto'
function calculateVerifyCode(qrCodeKey, qrCodeValue, loginClientIp, loginSystemCode, originSystemCode) { function calculateVerifyCode(qrCodeKey, qrCodeValue, loginClientIp, loginSystemCode, originSystemCode) {
const source = qrCodeKey + qrCodeValue + loginClientIp + loginSystemCode + originSystemCode const source = qrCodeKey + qrCodeValue + loginClientIp + loginSystemCode + originSystemCode
return crypto.createHash('md5').update(source).digest('hex') return createHash('md5').update(source).digest('hex')
} }
// VerifyCode calculation endpoint // VerifyCode calculation endpoint
@ -441,38 +439,33 @@ async function handleFallbackLogin(req, res) {
return sendJSON(res, 401, { error: '用户名或密码错误' }) return sendJSON(res, 401, { error: '用户名或密码错误' })
} }
// 登录成功:服务端会话 + HttpOnly cookie(禁止仅伪造 Cookie 提权) // 登录成功:创建 session,角色 = fallback_admin (等同 super_admin)
const empNo = 'administrator' const empNo = 'administrator'
const userContext = { const userContext = {
empNo, empNo,
username: 'Fallback Administrator', username: 'Fallback Administrator',
displayName: '应急管理员',
isAuthenticated: true, isAuthenticated: true,
role: 'fallback_admin', role: 'fallback_admin',
authMode: 'fallback',
authenticatedAt: new Date().toISOString(), authenticatedAt: new Date().toISOString(),
lastActiveAt: new Date().toISOString(), lastActiveAt: new Date().toISOString(),
} }
await _sessionStore.setex(empNo, Math.floor(INTERNAL.session.cookieMaxAge / 1000), userContext) await _sessionStore.setex(empNo, Math.floor(INTERNAL.session.cookieMaxAge / 1000), userContext)
const maxAge = Math.floor(INTERNAL.session.cookieMaxAge / 1000) // 给浏览器设 cookie,让后续请求 auth-middleware 能识别
const proto = String(req.headers['x-forwarded-proto'] || '') res.setHeader('Set-Cookie', [
const secure = proto === 'https' || req.socket?.encrypted === true
const parts = [
'UDS_FALLBACK_USER=administrator', 'UDS_FALLBACK_USER=administrator',
`Max-Age=${maxAge}`, `Max-Age=${Math.floor(INTERNAL.session.cookieMaxAge / 1000)}`,
'Path=/', 'Path=/',
'Secure',
'HttpOnly', 'HttpOnly',
'SameSite=Lax', 'SameSite=Lax',
] ].join('; '))
if (secure) parts.push('Secure')
res.setHeader('Set-Cookie', parts.join('; '))
sendJSON(res, 200, { sendJSON(res, 200, {
success: true, success: true,
empNo, empNo,
role: 'fallback_admin', role: 'fallback_admin',
message: '应急管理员登录成功(UAC 不可用时使用)', message: '兜底管理员登录成功',
}) })
} }
@ -558,13 +551,13 @@ async function handleAllRoutes(req, res) {
function handleRequest(req, res) { function handleRequest(req, res) {
const ctx2 = { req, res } const ctx2 = { req, res }
_authMiddleware(ctx2, async () => { _authMiddleware(ctx2, async () => {
// 空表首位 = super_admin;其余首次登录写入 user // 首次部署 bootstrap: 第一个有 UDS 凭证的用户 = super_admin
if (ctx2.empNo && ctx2.empNo !== 'administrator') { if (ctx2.empNo && !ctx2.role) {
const { role, bootstrapped } = await _rolesStore.bootstrapFirstUser(ctx2.empNo) const { role, bootstrapped } = await _rolesStore.bootstrapFirstUser(ctx2.empNo)
ctx2.role = role ctx2.role = role
ctx2.permissions = computePermissions(role) ctx2.permissions = computePermissions(role)
if (bootstrapped) { if (bootstrapped) {
_logger?.info?.(`[uds-auth] BOOTSTRAP: ${ctx2.empNo} is now super_admin`) ctx.logger?.info?.(`[uds-auth] BOOTSTRAP: ${ctx2.empNo} is now super_admin`)
} }
} }
@ -686,9 +679,9 @@ export async function apply(ctx, config = {}) {
let source = () => mergeConfig(config) let source = () => mergeConfig(config)
_currentConfig = source() _currentConfig = source()
_logger = ctx.logger || console
ctx.logger?.info?.('[uds-auth] Loading...') ctx.logger?.info?.('[uds-auth] Loading...')
// Settings 注册 + RPC 注册:不依赖 webServer
installSettingsSection(ctx, source(), { installSettingsSection(ctx, source(), {
setSource: (current) => { setSource: (current) => {
source = typeof current === 'function' ? current : () => current source = typeof current === 'function' ? current : () => current
@ -700,19 +693,37 @@ export async function apply(ctx, config = {}) {
} }
}) })
// 勿在 connection.rpc 注册 /uds-auth(与 webServer prefix 冲突) ctx.inject(['connection'], (connCtx) => {
const rpc = connCtx.connection?.rpc
if (!rpc || typeof rpc.handle !== 'function') {
ctx.logger?.warn?.('[uds-auth] connection.rpc.handle unavailable — client config UI disabled')
return
}
connCtx.effect(() => {
const dispose = rpc.handle(UDS_AUTH_RPC_CHANNEL, async (endpoint) => {
if (endpoint === 'config.get') {
const c = source()
return {
ok: true,
value: {
uacBaseUrl: c.uacBaseUrl,
userSearchUrl: c.userSearchUrl,
loginSystemCode: c.loginSystemCode,
originSystemCode: c.originSystemCode,
}
}
}
return { ok: false, error: 'unknown_endpoint' }
})
return () => dispose?.()
}, 'uds-auth: rpc')
})
// 初始化内部服务(session store, roles, auth middleware 等)
await initServices(ctx, source()) await initServices(ctx, source())
ctx.inject(['webServer'], (wctx) => { // 注册 webServer 路由(可能 webServer 已就绪,也可能需要等待)
wctx.effect( registerWebRoute(ctx)
() => wctx.webServer.register({
kind: 'prefix',
path: '/uds-auth',
handler: handleAllRoutes,
}),
'uds-auth: web route',
)
})
ctx.logger?.info?.('[uds-auth] Host ready') ctx.logger?.info?.('[uds-auth] Host ready')
} }
@ -750,9 +761,7 @@ async function initServices(ctx, config) {
systemCode: config.loginSystemCode, systemCode: config.loginSystemCode,
empNoHeader: INTERNAL.empNoHeader, empNoHeader: INTERNAL.empNoHeader,
authValueHeader: INTERNAL.authValueHeader, authValueHeader: INTERNAL.authValueHeader,
authMode: 'token+profile',
}, },
userSearchUrl: config.userSearchUrl,
session: INTERNAL.session, session: INTERNAL.session,
}, _sessionStore, _rolesStore) }, _sessionStore, _rolesStore)
_apiHandlers = createApiHandlers({ session: INTERNAL.session }, _sessionStore, _rolesStore) _apiHandlers = createApiHandlers({ session: INTERNAL.session }, _sessionStore, _rolesStore)