Skip to content

Commit 5be242b

Browse files
committed
fix: Improve OAuth token refresh error handling and state management
Enhanced error handling for OAuth token refresh failures across all email clients (Gmail, Outlook, IMAP) with better state management and notification controls.
1 parent 933a4f7 commit 5be242b

File tree

8 files changed

+99
-17
lines changed

8 files changed

+99
-17
lines changed

lib/email-client/gmail-client.js

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ class GmailClient extends BaseClient {
295295
async init(opts) {
296296
opts = opts || {};
297297

298+
this.state = 'connecting';
299+
await this.setStateVal();
300+
298301
await this.getAccount();
299302
await this.getClient(true);
300303

@@ -349,10 +352,13 @@ class GmailClient extends BaseClient {
349352

350353
err.authenticationFailed = true;
351354

352-
await this.notify(false, AUTH_ERROR_NOTIFY, {
353-
response: err.oauthRequest?.response?.error?.message || err.response,
354-
serverResponseCode: 'ApiRequestError'
355-
});
355+
if (!err.errorNotified) {
356+
err.errorNotified = true;
357+
await this.notify(false, AUTH_ERROR_NOTIFY, {
358+
response: err.oauthRequest?.response?.error?.message || err.response,
359+
serverResponseCode: 'ApiRequestError'
360+
});
361+
}
356362

357363
throw err;
358364
}
@@ -1809,7 +1815,34 @@ class GmailClient extends BaseClient {
18091815
}
18101816

18111817
async getToken() {
1812-
const tokenData = await this.accountObject.getActiveAccessTokenData();
1818+
let tokenData;
1819+
try {
1820+
tokenData = await this.accountObject.getActiveAccessTokenData();
1821+
if (!['init', 'connecting', 'connected'].includes(this.state)) {
1822+
// We're in an error state (authenticationError, disconnected, etc.)
1823+
// But we just got a valid token, so we've recovered
1824+
this.state = 'connected';
1825+
await this.setStateVal();
1826+
}
1827+
} catch (E) {
1828+
if (E.code === 'ETokenRefresh') {
1829+
// treat as authentication failure
1830+
this.state = 'authenticationError';
1831+
await this.setStateVal();
1832+
1833+
E.authenticationFailed = true;
1834+
1835+
if (!E.errorNotified) {
1836+
E.errorNotified = true;
1837+
await this.notify(false, AUTH_ERROR_NOTIFY, {
1838+
response: E.oauthRequest?.response?.error?.message || E.response,
1839+
serverResponseCode: 'TokenGenerationError'
1840+
});
1841+
}
1842+
}
1843+
1844+
throw E;
1845+
}
18131846
return tokenData.accessToken;
18141847
}
18151848

lib/email-client/imap-client.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,9 @@ class IMAPClient extends BaseClient {
666666
return false;
667667
}
668668

669+
this.state = 'connecting';
670+
await this.setStateVal();
671+
669672
let imapClient = this.imapClient;
670673

671674
let accountData = await this.accountObject.loadAccountData();
@@ -940,7 +943,6 @@ class IMAPClient extends BaseClient {
940943

941944
// Update state to connected
942945
this.state = 'connected';
943-
944946
await this.setStateVal();
945947

946948
// Store IMAP server capabilities for reference

lib/email-client/outlook-client.js

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ class OutlookClient extends BaseClient {
165165
* Sets up OAuth2 authentication, validates access, and starts background processes
166166
*/
167167
async init() {
168+
this.state = 'connecting';
169+
await this.setStateVal();
170+
168171
await this.getAccount();
169172
await this.prepareDelegatedAccount();
170173
await this.getClient(true);
@@ -199,10 +202,13 @@ class OutlookClient extends BaseClient {
199202

200203
err.authenticationFailed = true;
201204

202-
await this.notify(false, AUTH_ERROR_NOTIFY, {
203-
response: err.oauthRequest?.response?.error?.message || err.response,
204-
serverResponseCode: 'ApiRequestError'
205-
});
205+
if (!err.errorNotified) {
206+
err.errorNotified = true;
207+
await this.notify(false, AUTH_ERROR_NOTIFY, {
208+
response: err.oauthRequest?.response?.error?.message || err.response,
209+
serverResponseCode: 'ApiRequestError'
210+
});
211+
}
206212

207213
throw err;
208214
}
@@ -2392,7 +2398,35 @@ class OutlookClient extends BaseClient {
23922398
* Get OAuth2 access token from account or delegated account
23932399
*/
23942400
async getToken() {
2395-
const tokenData = await (this.delegatedAccountObject || this.accountObject).getActiveAccessTokenData();
2401+
let tokenData;
2402+
try {
2403+
tokenData = await (this.delegatedAccountObject || this.accountObject).getActiveAccessTokenData();
2404+
if (!['init', 'connecting', 'connected'].includes(this.state)) {
2405+
// We're in an error state (authenticationError, disconnected, etc.)
2406+
// But we just got a valid token, so we've recovered
2407+
this.state = 'connected';
2408+
await this.setStateVal();
2409+
}
2410+
} catch (E) {
2411+
if (E.code === 'ETokenRefresh') {
2412+
// treat as authentication failure
2413+
this.state = 'authenticationError';
2414+
await this.setStateVal();
2415+
2416+
E.authenticationFailed = true;
2417+
2418+
if (!E.errorNotified) {
2419+
E.errorNotified = true;
2420+
await this.notify(false, AUTH_ERROR_NOTIFY, {
2421+
response: E.oauthRequest?.response?.error?.message || E.response,
2422+
serverResponseCode: 'TokenGenerationError'
2423+
});
2424+
}
2425+
}
2426+
2427+
throw E;
2428+
}
2429+
23962430
return tokenData.accessToken;
23972431
}
23982432

lib/oauth/gmail.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ class GmailOauth {
309309

310310
if (!res.ok) {
311311
let err = new Error('Token request failed');
312+
err.code = 'ETokenRefresh';
313+
312314
err.statusCode = res.status;
313315
err.tokenRequest = {
314316
url: requestUrl,
@@ -420,6 +422,8 @@ class GmailOauth {
420422

421423
if (!res.ok) {
422424
let err = new Error('Token request failed');
425+
err.code = 'ETokenRefresh';
426+
423427
err.statusCode = res.status;
424428
err.tokenRequest = {
425429
url: requestUrl,

lib/oauth/mail-ru.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ class MailRuOauth {
140140

141141
if (!res.ok) {
142142
let err = new Error('Token request failed');
143+
err.code = 'ETokenRefresh';
144+
143145
err.tokenRequest = {
144146
url: requestUrl,
145147
method,
@@ -221,6 +223,8 @@ class MailRuOauth {
221223

222224
if (!res.ok) {
223225
let err = new Error('Token request failed');
226+
err.code = 'ETokenRefresh';
227+
224228
err.tokenRequest = {
225229
url: requestUrl,
226230
method,

lib/oauth/outlook.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ class OutlookOauth {
289289

290290
if (!res.ok) {
291291
let err = new Error('Token request failed');
292+
err.code = 'ETokenRefresh';
293+
292294
err.statusCode = res.status;
293295
err.tokenRequest = {
294296
url: requestUrl,
@@ -382,6 +384,8 @@ class OutlookOauth {
382384

383385
if (!res.ok) {
384386
let err = new Error('Token request failed');
387+
err.code = 'ETokenRefresh';
388+
385389
err.statusCode = res.status;
386390
err.tokenRequest = {
387391
url: requestUrl,

lib/tools.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2110,7 +2110,6 @@ C8MNYeCjyQjdvxim
21102110
aZBE0lOjBe4XuK5E
21112111
SJVPHpfUX3kpFpLi
21122112
J5hGmNpIvL7cc4Jn
2113-
5MisnkMNa2gQJQ1U
21142113
3arFOanxm/RO8U5c
21152114
VNRO9yqTV90pZSsm
21162115
fvZjf0oEWxQhZR1+

views/accounts/account.hbs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,8 @@
243243
<dd class="col-sm-9">
244244
{{#if account.isApi}}
245245
{{#if account.sendOnly}}
246-
API <span class="badge badge-info" style="cursor:default;" data-toggle="popover" data-trigger="hover" data-content="This account can only send emails. It does not have read access to the mailbox.">Send-only</span>
246+
API <span class="badge badge-info" style="cursor:default;" data-toggle="popover" data-trigger="hover"
247+
data-content="This account can only send emails. It does not have read access to the mailbox.">Send-only</span>
247248
{{else}}
248249
API
249250
{{/if}}
@@ -404,11 +405,12 @@
404405
<div class="card-body">
405406

406407
{{#if account.lastErrorState}}
408+
{{#if account.lastErrorState.description}}
409+
<div class="alert alert-danger">{{account.lastErrorState.description}}</div>
410+
{{/if}}
407411
<dl class="row">
408-
{{#if account.lastErrorState.description}}
409-
<dt class="col-sm-3">Error</dt>
410-
<dd class="col-sm-9">{{account.lastErrorState.description}}</dd>
411-
{{/if}}
412+
413+
412414

413415
{{#if account.lastErrorState.response}}
414416
<dt class="col-sm-3">IMAP response</dt>

0 commit comments

Comments
 (0)