fix: 修复登录时因租户不一致导致的422策略违规错误
在连接建立时记录服务器实际使用的租户代码,而非直接从存储中读取,避免登录时的租户类型判断与会话租户不一致导致的TENANT_CODE_FORBIDDEN错误。新增登录失败后的自修复重试逻辑,当收到422错误且原因是租户代码被禁止时,临时覆盖登录类型并重试一次,同时重置相关状态避免影响后续连接。
This commit is contained in:
@@ -150,6 +150,16 @@ export function clearKickedInfo() {
|
|||||||
* logins (restoreSession/reconnect re-auth) are untouched -- validateLoginTenant only requires
|
* logins (restoreSession/reconnect re-auth) are untouched -- validateLoginTenant only requires
|
||||||
* login_type when scheme is "basic".
|
* login_type when scheme is "basic".
|
||||||
*/
|
*/
|
||||||
|
/** The tenant the server session actually resolves to, captured when {hi} is sent (the server
|
||||||
|
* falls back to 'default' when {hi}.tenant is absent). login_type is decided against THIS value,
|
||||||
|
* not a fresh store read: the two decisions happen at different moments (hi on connect, login on
|
||||||
|
* submit), and a store change in between would make login_type disagree with the session tenant,
|
||||||
|
* which the server rejects with 422 policy violation / TENANT_CODE_FORBIDDEN -- rendered by
|
||||||
|
* clients as "当前操作不符合安全策略要求". */
|
||||||
|
let sessionTenantCode: string | null = null;
|
||||||
|
/** One-shot override used by loginBasic's FORBIDDEN self-heal retry (see there). */
|
||||||
|
let forcedLoginType: 0 | 1 | null = null;
|
||||||
|
|
||||||
function installOutgoingPacketPatches(t: Tinode) {
|
function installOutgoingPacketPatches(t: Tinode) {
|
||||||
const conn = t._connection;
|
const conn = t._connection;
|
||||||
if (!conn || conn.__tenantPatched) return;
|
if (!conn || conn.__tenantPatched) return;
|
||||||
@@ -160,10 +170,13 @@ function installOutgoingPacketPatches(t: Tinode) {
|
|||||||
const parsed = JSON.parse(msg);
|
const parsed = JSON.parse(msg);
|
||||||
if (parsed.hi) {
|
if (parsed.hi) {
|
||||||
const tenant = useSessionStore.getState().tenant;
|
const tenant = useSessionStore.getState().tenant;
|
||||||
|
// Whichever tenant (or absence of one) this {hi} declares, the server fixes the session
|
||||||
|
// tenant from it right now -- so remember exactly that, for login_type decisions later.
|
||||||
|
sessionTenantCode = (tenant?.code || 'default').trim().toLowerCase();
|
||||||
if (tenant?.code) {
|
if (tenant?.code) {
|
||||||
parsed.hi.tenant = tenant.code;
|
parsed.hi.tenant = tenant.code;
|
||||||
msg = JSON.stringify(parsed);
|
|
||||||
}
|
}
|
||||||
|
msg = JSON.stringify(parsed);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Not valid JSON -- send as is.
|
// Not valid JSON -- send as is.
|
||||||
@@ -172,8 +185,12 @@ function installOutgoingPacketPatches(t: Tinode) {
|
|||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(msg);
|
const parsed = JSON.parse(msg);
|
||||||
if (parsed.login && parsed.login.scheme === 'basic') {
|
if (parsed.login && parsed.login.scheme === 'basic') {
|
||||||
const tenantCode = useSessionStore.getState().tenant?.code || 'default';
|
if (forcedLoginType !== null) {
|
||||||
parsed.login.login_type = tenantCode.trim().toLowerCase() === 'default' ? 1 : 0;
|
parsed.login.login_type = forcedLoginType;
|
||||||
|
} else {
|
||||||
|
const tenantCode = sessionTenantCode ?? (useSessionStore.getState().tenant?.code || 'default');
|
||||||
|
parsed.login.login_type = tenantCode.trim().toLowerCase() === 'default' ? 1 : 0;
|
||||||
|
}
|
||||||
msg = JSON.stringify(parsed);
|
msg = JSON.stringify(parsed);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -292,6 +309,11 @@ export function resetTinode() {
|
|||||||
connectPromise = null;
|
connectPromise = null;
|
||||||
restorePromise = null;
|
restorePromise = null;
|
||||||
wasAuthenticated = false;
|
wasAuthenticated = false;
|
||||||
|
// Both belong to the torn-down connection's session: the next getTinode() re-derives them from
|
||||||
|
// the freshly-sent {hi}, and a stale non-null sessionTenantCode here would misclassify the very
|
||||||
|
// first login_type of the rebuilt connection (which sends hi before any store read matters).
|
||||||
|
sessionTenantCode = null;
|
||||||
|
forcedLoginType = null;
|
||||||
setStatus('disconnected');
|
setStatus('disconnected');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +376,24 @@ export function loginBasic(login: string, password: string): Promise<LoginCtrl>
|
|||||||
loginPromise = (async () => {
|
loginPromise = (async () => {
|
||||||
const t = getTinode();
|
const t = getTinode();
|
||||||
if (!t.isConnected()) await connect();
|
if (!t.isConnected()) await connect();
|
||||||
return t.loginBasic(login, password);
|
const ctrl = await t.loginBasic(login, password);
|
||||||
|
// Self-heal the exact failure mode observed live (server validateLoginTenant): an enterprise-
|
||||||
|
// meaning login_type=0 against a session whose tenant is 'default' is rejected with 422 policy
|
||||||
|
// violation, reason=TENANT_CODE_FORBIDDEN -- users see "当前操作不符合安全策略要求". That can
|
||||||
|
// only mean the session tenant is 'default', so retry once with the personal meaning (1) before
|
||||||
|
// giving up; a success also re-syncs sessionTenantCode so later logins on this connection don't
|
||||||
|
// repeat the mistake.
|
||||||
|
if (ctrl.code === 422 && (ctrl.params as { reason?: string } | undefined)?.reason === 'TENANT_CODE_FORBIDDEN') {
|
||||||
|
forcedLoginType = 1;
|
||||||
|
try {
|
||||||
|
const retry = await t.loginBasic(login, password);
|
||||||
|
if (retry.code < 300) sessionTenantCode = 'default';
|
||||||
|
return retry;
|
||||||
|
} finally {
|
||||||
|
forcedLoginType = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctrl;
|
||||||
})().finally(() => {
|
})().finally(() => {
|
||||||
loginPromise = null;
|
loginPromise = null;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user