mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-12 21:20:49 +08:00
fix(uds-auth): keep login error codes on QR login, name the scanned empNo (0.3.17)
0.3.16 mapped every post-scan failure to login_finalize_failed, hiding codes with their own message (bootstrap_required, account_disabled, registration_closed). Pass those through again and only fall back for unknown errors. The bootstrap_required message now includes the scanned employee number and how to proceed (UDS_AUTH_INITIAL_ADMIN or emergency account login). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
08a5520791
commit
e61f9c6336
4 changed files with 21 additions and 6 deletions
|
|
@ -167,7 +167,7 @@ export const MESSAGES = {
|
|||
'err.invalid_workspace_root': 'workspaceRoot 必须是绝对路径',
|
||||
'err.invalid_ttl': 'TTL 超出允许范围',
|
||||
'err.invalid_outbound_host': 'outbound 白名单 host 格式无效',
|
||||
'err.bootstrap_required': '系统尚未初始化管理员,请配置 UDS_AUTH_INITIAL_ADMIN',
|
||||
'err.bootstrap_required': '系统尚未初始化管理员(当前扫码工号 {empNo})。请设置环境变量 UDS_AUTH_INITIAL_ADMIN={empNo} 后重启 Harness;或先用「应急账号登录」进入,在设置中添加用户',
|
||||
'err.registration_closed': '未开放自动注册,请联系管理员',
|
||||
'err.raw_token_disabled': '原始凭证接口已关闭,请使用 outbound 代理',
|
||||
'err.caller_unauthenticated': '调用方未通过本地会话认证',
|
||||
|
|
@ -359,7 +359,7 @@ export const MESSAGES = {
|
|||
'err.invalid_workspace_root': 'workspaceRoot must be an absolute path',
|
||||
'err.invalid_ttl': 'TTL out of allowed range',
|
||||
'err.invalid_outbound_host': 'Invalid outbound host entry',
|
||||
'err.bootstrap_required': 'Admin not initialized; set UDS_AUTH_INITIAL_ADMIN',
|
||||
'err.bootstrap_required': 'Admin not initialized (scanned employee no. {empNo}). Set UDS_AUTH_INITIAL_ADMIN={empNo} and restart Harness, or sign in with the emergency account and add users in Settings',
|
||||
'err.registration_closed': 'Open registration is disabled; contact an admin',
|
||||
'err.raw_token_disabled': 'Raw credential API disabled; use outbound proxy',
|
||||
'err.caller_unauthenticated': 'Caller lacks a verified local session',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import { requirePermission } from './middleware/auth-middleware.js'
|
||||
import { computePermissions } from './roles.js'
|
||||
import { apiError, apiOk, resolveLocale } from './i18n.js'
|
||||
import { apiError, apiOk, resolveLocale, MESSAGES } from './i18n.js'
|
||||
import { readFile, writeFile, mkdir } from 'node:fs/promises'
|
||||
import { resolve, dirname, isAbsolute, join } from 'node:path'
|
||||
import { homedir } from 'node:os'
|
||||
|
|
@ -420,6 +420,7 @@ async function handleQRProxy(req, res) {
|
|||
})
|
||||
proxyRes.on('end', () => {
|
||||
if (aborted || res.headersSent) return
|
||||
let scannedEmpNo = ''
|
||||
void (async () => {
|
||||
let payload
|
||||
try { payload = JSON.parse(Buffer.concat(chunks).toString('utf8')) } catch {
|
||||
|
|
@ -441,6 +442,7 @@ async function handleQRProxy(req, res) {
|
|||
_logger?.warn?.('[uds-auth] qr-proxy upstream %s unexpected result code=%s bo=%s', url.hostname, top, boCode)
|
||||
return sendErr(res, req, 502, 'invalid_upstream_response', { host: url.hostname })
|
||||
}
|
||||
scannedEmpNo = credentials.empNo
|
||||
const consumed = consumeQrChallenge(parsedBody.qrCodeKey, parsedBody.qrCodeValue,
|
||||
req.headers['x-uds-qr-binding'])
|
||||
if (!consumed.ok) return sendErr(res, req, 409, consumed.reason)
|
||||
|
|
@ -472,7 +474,11 @@ async function handleQRProxy(req, res) {
|
|||
userContext: { empNo: id, username: userContext.username, role, isAuthenticated: true } } })
|
||||
})().catch(err => {
|
||||
_logger?.warn?.('[uds-auth] qr-proxy post-scan login failed: %s %s', err?.code || '', err?.message || err)
|
||||
if (!res.headersSent) sendErr(res, req, 502, 'login_finalize_failed', { detail: err?.code || err?.message || '' })
|
||||
if (res.headersSent) return
|
||||
// Codes with their own message (bootstrap_required, account_disabled, ...) must reach the user.
|
||||
const known = err?.code && MESSAGES.zh['err.' + err.code]
|
||||
if (known) return sendErr(res, req, 403, err.code, { empNo: scannedEmpNo })
|
||||
sendErr(res, req, 502, 'login_finalize_failed', { detail: err?.code || err?.message || '' })
|
||||
})
|
||||
})
|
||||
proxyRes.on('error', (err) => {
|
||||
|
|
@ -886,7 +892,7 @@ async function handleBridgeBind(req, res) {
|
|||
try {
|
||||
role = await _authMiddleware.resolveRoleOnLogin(id)
|
||||
} catch (err) {
|
||||
return sendErr(res, req, 403, err?.code || 'bootstrap_required')
|
||||
return sendErr(res, req, 403, err?.code || 'bootstrap_required', { empNo: id })
|
||||
}
|
||||
const { bearer, bridge } = await mintLoginSession(id, userContext, 'uds', token)
|
||||
await ensureUserWorkspace(id)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "uds-auth",
|
||||
"version": "0.3.16",
|
||||
"version": "0.3.17",
|
||||
"description": "UDS authentication plugin for DeepSeek Harness — trusted single-instance access control (not strong multi-tenant isolation)",
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
|
|
|
|||
|
|
@ -48,4 +48,13 @@ describe('upstream error messages', () => {
|
|||
assert.equal(client.split(`"err.${code}":`).length - 1, 2, code)
|
||||
}
|
||||
})
|
||||
|
||||
it('bootstrap_required names the scanned employee number and the way out', () => {
|
||||
for (const locale of ['zh', 'en']) {
|
||||
const m = apiError('bootstrap_required', locale, { empNo: '10012345' })
|
||||
assert.equal(m.error, 'bootstrap_required')
|
||||
assert.match(m.message, /UDS_AUTH_INITIAL_ADMIN=10012345/)
|
||||
assert.doesNotMatch(m.message, /\{empNo\}/)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue