From 1127680425f6c8a0570531469b40b347af59fe17 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 22 Sep 2026 14:20:08 +0800 Subject: [PATCH] fix(tinode): send login_type on basic-scheme login (Zentao bug #33) server/user.go's validateLoginTenant now hard-requires a login_type field (0 enterprise / 1 personal) on every {login scheme:"basic"} message, rejecting the rest with a 422 LOGIN_TYPE_REQUIRED policy violation -- confirmed live by reading the currently-deployed server/session.go call site and server/user.go's validation logic. The vendored tinode-sdk has no API surface for this custom field (its login()/loginBasic() packet builders are private class fields that only ever write scheme/secret/cred), so every basic-scheme web login was failing. Extends the existing installTenantHiPatch wire-level interception (same technique already used for {hi}.tenant) to also inject login_type into outgoing {login} packets, derived from the resolved tenant's code. tenant_code is deliberately left unset: the backend falls back to the session's own tenant (already established via the {hi} patch) when the {login} message omits it, for both the personal and enterprise branches. token-scheme logins (session restore/ reconnect re-auth) are untouched. Verified live: captured the actual outgoing WS frame now carrying login_type, and confirmed the resulting error changed from what would be a 422 policy violation to a normal credential-mismatch error. Co-Authored-By: Claude Sonnet 5 --- web/src/lib/tinode/connection.ts | 33 +++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/web/src/lib/tinode/connection.ts b/web/src/lib/tinode/connection.ts index 04dbb057..5b076958 100644 --- a/web/src/lib/tinode/connection.ts +++ b/web/src/lib/tinode/connection.ts @@ -136,13 +136,21 @@ export function clearKickedInfo() { } /** - * The server extends the stock {hi} packet with a `tenant` field the vendored JS SDK has no API for - * (resolved via POST /v1/im/tenants/resolve; see server/datamodel.go). The packet builder is private, - * so the only usable hook is wrapping the connection's raw sendText and injecting the field into the - * already-serialized JSON. Ported as-is from the old app's tnSetup/installTenantHiPatch -- this is a - * real backend protocol extension, not an old-codebase-specific workaround. + * Two backend protocol extensions the vendored JS SDK has no API for -- both fields the packet + * builders (#initPacket/login(), both private) never write, so the only usable hook for either is + * wrapping the connection's raw sendText and injecting the field into the already-serialized JSON: + * - {hi}.tenant (resolved via POST /v1/im/tenants/resolve; see server/datamodel.go). Ported as-is + * from the old app's tnSetup/installTenantHiPatch. + * - {login}.login_type: confirmed live (server/user.go validateLoginTenant, called from every + * session.login()) that scheme:"basic" logins now hard-require this field -- 0 enterprise / + * 1 personal -- and fail with a 422 LOGIN_TYPE_REQUIRED without it (Zentao bug #33). tenant_code + * is deliberately left unset: validateLoginTenant falls back to the session's own tenant (already + * established by the {hi} patch above) when {login} omits it, for both the personal and + * enterprise branches, so login_type is the only field this client needs to add. token-scheme + * logins (restoreSession/reconnect re-auth) are untouched -- validateLoginTenant only requires + * login_type when scheme is "basic". */ -function installTenantHiPatch(t: Tinode) { +function installOutgoingPacketPatches(t: Tinode) { const conn = t._connection; if (!conn || conn.__tenantPatched) return; const original = conn.sendText.bind(conn); @@ -160,6 +168,17 @@ function installTenantHiPatch(t: Tinode) { } catch { // Not valid JSON -- send as is. } + } else if (typeof msg === 'string' && msg.indexOf('"login":') >= 0) { + try { + const parsed = JSON.parse(msg); + if (parsed.login && parsed.login.scheme === 'basic') { + const tenantCode = useSessionStore.getState().tenant?.code || 'default'; + parsed.login.login_type = tenantCode.toLowerCase() === 'default' ? 1 : 0; + msg = JSON.stringify(parsed); + } + } catch { + // Not valid JSON -- send as is. + } } return original(msg as string); }; @@ -173,7 +192,7 @@ function createTinode(): Tinode { const t = new Tinode({ appName: APP_NAME, host, apiKey: API_KEY, transport: 'ws', secure, persist: false }); t.setHumanLanguage('zh'); t.enableLogging(LOGGING_ENABLED, true); - installTenantHiPatch(t); + installOutgoingPacketPatches(t); t.onConnect = () => { const wasReconnecting = reconnectState.retryInSeconds > 0 || reconnectState.attemptInFlight;