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 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-22 14:20:08 +08:00
co-authored by Claude Sonnet 5
parent ed029c59ef
commit 1127680425
+26 -7
View File
@@ -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;