Fix local fallback_admin workspace list on HTTP.

Drop Secure cookies when not HTTPS so WS upgrades keep UDS_FALLBACK_*, and accept UDS_FALLBACK_UI for ACL identity.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-08 11:20:10 +08:00
parent 93526e7d45
commit 3e5c1f846e
2 changed files with 24 additions and 9 deletions

View file

@ -124,12 +124,14 @@ export function resolveIdentityFromRequestSync(req, deps) {
const empNo = parseCookie(cookie, 'PORTALSSOUser')
|| parseCookie(cookie, 'ZTEDPGSSOUser')
|| parseCookie(cookie, 'UDS_FALLBACK_USER')
|| parseCookie(cookie, 'UDS_FALLBACK_UI')
if (!empNo) return null
const token = parseCookie(cookie, 'PORTALSSOCookie')
|| parseCookie(cookie, 'ZTEDPGSSOCookie')
const isFallback = empNo === 'administrator'
|| !!parseCookie(cookie, 'UDS_FALLBACK_USER')
|| !!parseCookie(cookie, 'UDS_FALLBACK_UI')
// Bare portal empNo without token is NOT enough — otherwise logout/未登录
// still leaks workspace names via leftover SSO cookies on the WebSocket.
@ -157,6 +159,7 @@ export async function resolveIdentityFromRequest(req, deps) {
const empNo = parseCookie(cookie, 'PORTALSSOUser')
|| parseCookie(cookie, 'ZTEDPGSSOUser')
|| parseCookie(cookie, 'UDS_FALLBACK_USER')
|| parseCookie(cookie, 'UDS_FALLBACK_UI')
if (!empNo) return null
const { sessionStore, rolesStore } = deps
@ -171,6 +174,7 @@ export async function resolveIdentityFromRequest(req, deps) {
|| parseCookie(cookie, 'ZTEDPGSSOCookie')
const isFallback = empNo === 'administrator'
|| !!parseCookie(cookie, 'UDS_FALLBACK_USER')
|| !!parseCookie(cookie, 'UDS_FALLBACK_UI')
if (!userContext) {
// Require session, token, or fallback cookie — never empNo alone.

View file

@ -414,6 +414,14 @@ function sendJSON(res, code, data) {
res.end(JSON.stringify(data))
}
/** Prefer Secure cookies only on HTTPS — http://127.0.0.1 drops Secure cookies from WS. */
function isHttpsRequest(req) {
if (req?.socket?.encrypted) return true
const xf = String(req?.headers?.['x-forwarded-proto'] || '').split(',')[0].trim().toLowerCase()
return xf === 'https'
}
async function handleFallbackLogin(req, res) {
let body = ''
for await (const chunk of req) body += chunk
@ -450,24 +458,27 @@ async function handleFallbackLogin(req, res) {
// 给浏览器设 cookie,让后续请求 auth-middleware 能识别
const fbMaxAge = Math.floor(INTERNAL.session.cookieMaxAge / 1000)
res.setHeader('Set-Cookie', [
[
res.setHeader('Set-Cookie', (() => {
const secure = isHttpsRequest(req)
const partsUser = [
'UDS_FALLBACK_USER=administrator',
`Max-Age=${fbMaxAge}`,
'Path=/',
'Secure',
'HttpOnly',
'SameSite=Lax',
].join('; '),
// Readable by document.cookie so client ACL gates see fallback login before /api/me.
[
]
const partsUi = [
'UDS_FALLBACK_UI=administrator',
`Max-Age=${fbMaxAge}`,
'Path=/',
'Secure',
'SameSite=Lax',
].join('; '),
])
]
if (secure) {
partsUser.push('Secure')
partsUi.push('Secure')
}
return [partsUser.join('; '), partsUi.join('; ')]
})())
sendJSON(res, 200, {
success: true,