mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-27 23:14:36 +08:00
[local-sync] Refresh Google OAuth2 tokens when Invalid Credentials occurs in sync loop
Summary: Previously, we would only refresh Google OAuth2 access tokens at the beginning of the sync loop, and _only_ if the access token had already expired. This meant that if an access token expired in the middle of a sync loop iteration, the user would get prompted with the reauth red box for their account and would have to either go through the oauth flow again or restart the app for sync to continue. This diff makes two changes: 1. Adds 5min of padding to the refresh window, so if a token will expire in <5min, we'll go ahead and refresh the token. This will reduce the possibility that an access token can expire during a sync loop iteration. 2. Catches Invalid Credentials IMAPAuthenticationErrors for Gmail accounts and forces a token refresh on the next sync loop. These should prevent a user from _ever_ having to reauth their Gmail account unless the refresh token is revoked, or we encounter some other permanent error trying to refresh the token. Fixes T7775 (at least some cases) Test Plan: manual Reviewers: khamidou, evan, juan Reviewed By: juan Maniphest Tasks: T7775, T7755 Differential Revision: https://phab.nylas.com/D3908
This commit is contained in:
parent
56422eee10
commit
dce9743283
1 changed files with 25 additions and 2 deletions
|
@ -45,6 +45,7 @@ class SyncWorker {
|
|||
this._numRetries = 0;
|
||||
this._numTimeoutErrors = 0;
|
||||
this._socketTimeout = IMAPConnection.DefaultSocketTimeout;
|
||||
this._requireTokenRefresh = false
|
||||
|
||||
this._syncTimer = setTimeout(() => {
|
||||
// TODO this is currently a hack to keep N1's account in sync and notify of
|
||||
|
@ -144,7 +145,12 @@ class SyncWorker {
|
|||
}
|
||||
|
||||
const currentUnixDate = Math.floor(Date.now() / 1000);
|
||||
if (currentUnixDate > credentials.expiry_date) {
|
||||
if (this._requireTokenRefresh && (credentials.expiry_date > currentUnixDate)) {
|
||||
console.warn("ensureAccessToken: got Invalid Credentials from server but token is not expired");
|
||||
}
|
||||
// try to avoid tokens expiring during the sync loop
|
||||
const expiryDatePlusSlack = credentials.expiry_date - (5 * 60);
|
||||
if (this._requireTokenRefresh || (currentUnixDate > expiryDatePlusSlack)) {
|
||||
const req = new NylasAPIRequest({
|
||||
api: N1CloudAPI,
|
||||
options: {
|
||||
|
@ -157,6 +163,7 @@ class SyncWorker {
|
|||
const newCredentials = await req.run()
|
||||
this._account.setCredentials(newCredentials);
|
||||
await this._account.save();
|
||||
this._requireTokenRefresh = false
|
||||
return newCredentials;
|
||||
}
|
||||
return null
|
||||
|
@ -181,6 +188,8 @@ class SyncWorker {
|
|||
if (isNonPermanentError) {
|
||||
throw new IMAPErrors.IMAPTransientAuthenticationError(`Server error when trying to refresh token.`);
|
||||
} else {
|
||||
// sync worker is persistent across reauths, so need to clear this flag
|
||||
this._requireTokenRefresh = false
|
||||
throw new IMAPErrors.IMAPAuthenticationError(`Unable to refresh access token`);
|
||||
}
|
||||
}
|
||||
|
@ -312,6 +321,19 @@ class SyncWorker {
|
|||
}
|
||||
|
||||
async _onSyncError(error) {
|
||||
// We try to refresh Google OAuth2 access tokens in advance, but sometimes
|
||||
// it doesn't work (e.g. the token expires during the sync loop). In this
|
||||
// case, we need to immediately restart the sync loop & refresh the token.
|
||||
//
|
||||
// These error messages look like "Error: Invalid credentials (Failure)"
|
||||
const isExpiredTokenError = (this._account.provider === "gmail" &&
|
||||
error instanceof IMAPErrors.IMAPAuthenticationError &&
|
||||
/invalid credentials/i.test(error.message))
|
||||
if (isExpiredTokenError) {
|
||||
this._requireTokenRefresh = true
|
||||
return
|
||||
}
|
||||
|
||||
this._closeConnections()
|
||||
const errorJSON = error.toJSON()
|
||||
|
||||
|
@ -398,7 +420,8 @@ class SyncWorker {
|
|||
// interrupted or a sync was requested
|
||||
const shouldSyncImmediately = (
|
||||
moreToSync ||
|
||||
this._interrupted
|
||||
this._interrupted ||
|
||||
this._requireTokenRefresh
|
||||
)
|
||||
interval = shouldSyncImmediately ? 1 : SYNC_LOOP_INTERVAL_MS;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue