Skip to content

Commit 26cebbe

Browse files
committed
fix(sdk): defer loginWithToken auth-error cleanup to avoid racing fresh logins
The previous wrap cleared local credentials synchronously when DDPSDK's auto-relogin on `connected` came back with an auth error. That fixed the e2ee-key-reset flow (server force-logs out, SDK reconnects with dead token, wrap clears creds, user falls back to Login) but raced with concurrent fresh logins on: - e2ee-passphrase-management :76/:87 (loginByUserState + _pollStoredLoginToken inject a fresh token while the auto-retry with the dead one is still in flight) - saml :307 SLO (post-logout redirect chain rotates state under us) Defer the cleanup by 500ms and re-verify the guards at the deadline. If a concurrent fresh login completed in the meantime it will have rotated either the stored token or sdk.account.uid; the deferred check then bails out instead of nuking the just-stored credentials. For genuine force-logout flows nothing else touches the state, so the cleanup runs as before — just half a second later, well within test timeouts.
1 parent a940e3f commit 26cebbe

1 file changed

Lines changed: 15 additions & 4 deletions

File tree

apps/meteor/client/lib/sdk/ddpSdk.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,20 @@ if (typeof window !== 'undefined') {
236236
try {
237237
return await originalLogin(token);
238238
} catch (error) {
239-
const stillSameStored = readStoredLoginToken() === token;
240-
const accountStillReflectsThisToken = sdk.account.uid === triedWithUid;
241-
if (isAuthError(error) && stillSameStored && accountStillReflectsThisToken) {
239+
if (!isAuthError(error)) throw error;
240+
// Defer the cleanup so a concurrent fresh login (e.g. SAML's
241+
// post-redirect resume, e2ee-passphrase-management's
242+
// loginByUserState/_pollStoredLoginToken, password login from
243+
// the form) has a chance to rotate the stored token and SDK
244+
// account state. After the delay, only clear creds if the
245+
// state is still stuck on the same token+uid we tried with —
246+
// meaning no concurrent flow rescued it. For real
247+
// force-logout (no concurrent login), nothing else will
248+
// touch localStorage and we end up clearing on schedule.
249+
setTimeout(() => {
250+
const stillSameStored = readStoredLoginToken() === token;
251+
const accountStillReflectsThisToken = sdk.account.uid === triedWithUid;
252+
if (!stillSameStored || !accountStillReflectsThisToken) return;
242253
try {
243254
Accounts._unstoreLoginToken();
244255
} catch {
@@ -251,7 +262,7 @@ if (typeof window !== 'undefined') {
251262
}
252263
sdk.account.uid = undefined;
253264
sdk.account.user = undefined;
254-
}
265+
}, 500);
255266
throw error;
256267
}
257268
};

0 commit comments

Comments
 (0)