Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion app/internal_packages/onboarding/lib/oauth-signin-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default class OAuthSignInPage extends React.Component<
_startTimer: NodeJS.Timeout;
_warnTimer: NodeJS.Timeout;
_mounted = false;
_lastCodeReceived: string | null = null;

state: OAuthSignInPageState = {
authStage: 'initial',
Expand All @@ -79,7 +80,20 @@ export default class OAuthSignInPage extends React.Component<
if (!this._mounted) return;
const code = extractOAuthCodeFromUrl(request.url);
if (code) {
this._onReceivedCode(code);
// Browsers, security software, and link-preview tools can hit this URL more
// than once for the *same* code (retries, prefetching, back/forward replay).
// Authorization codes are single-use, so re-submitting an identical code just
// fails with an "invalid_grant" error from the provider — ignore repeats of a
// code we've already started exchanging. But the user can also legitimately
// go back in their browser and complete sign-in again with a different
// account, producing a genuinely new code; only ignore that if we're already
// mid-exchange (or have already succeeded) for the previous one.
const isNewCode = code !== this._lastCodeReceived;
const isBusy = this.state.authStage === 'buildingAccount' || this.state.authStage === 'accountSuccess';
if (isNewCode && !isBusy) {
this._lastCodeReceived = code;
this._onReceivedCode(code);
}
response.writeHead(302, { Location: 'https://id.getmailspring.com/oauth/finished' });
response.end();
} else {
Expand Down
Loading