Skip to content

Commit 120864f

Browse files
Refreshes integration session before use (#4241)
Ensures that the integration session is refreshed if it has expired before making requests to the provider. Also improves error handling on provider API calls by throwing specific error types based on the response status code.
1 parent 01454ad commit 120864f

File tree

3 files changed

+105
-13
lines changed

3 files changed

+105
-13
lines changed

src/plus/integrations/authentication/cloudIntegrationService.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,21 @@ export class CloudIntegrationService {
8585
},
8686
);
8787
}
88+
89+
if (refresh) {
90+
// try once to just get the lastest token if the refresh fails, and give up if that fails too
91+
const newTokenRsp = await this.connection.fetchGkApi(
92+
`v1/provider-tokens/${cloudIntegrationType}`,
93+
{ method: 'GET' },
94+
{ organizationId: false },
95+
);
96+
if (newTokenRsp.ok) {
97+
return (await newTokenRsp.json())?.data as Promise<
98+
CloudIntegrationAuthenticationSession | undefined
99+
>;
100+
}
101+
}
102+
88103
return undefined;
89104
}
90105

src/plus/integrations/integration.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,18 @@ export abstract class IntegrationBase<
327327
return defaultValue;
328328
}
329329

330+
@gate()
331+
protected async refreshSessionIfExpired(scope?: LogScope): Promise<void> {
332+
if (this._session?.expiresAt != null && this._session.expiresAt < new Date()) {
333+
// The current session is expired, so get the latest from the cloud and refresh if needed
334+
try {
335+
await this.syncCloudConnection('connected', true);
336+
} catch (ex) {
337+
Logger.error(ex, scope);
338+
}
339+
}
340+
}
341+
330342
@debug()
331343
trackRequestException(): void {
332344
this.requestExceptionCount++;
@@ -433,6 +445,8 @@ export abstract class IntegrationBase<
433445
const connected = this.maybeConnected ?? (await this.isConnected());
434446
if (!connected) return undefined;
435447

448+
await this.refreshSessionIfExpired(scope);
449+
436450
try {
437451
const issues = await this.searchProviderMyIssues(
438452
this._session!,
@@ -463,6 +477,8 @@ export abstract class IntegrationBase<
463477
const connected = this.maybeConnected ?? (await this.isConnected());
464478
if (!connected) return undefined;
465479

480+
await this.refreshSessionIfExpired(scope);
481+
466482
const issueOrPR = this.container.cache.getIssueOrPullRequest(
467483
id,
468484
options?.type,
@@ -507,6 +523,8 @@ export abstract class IntegrationBase<
507523
const connected = this.maybeConnected ?? (await this.isConnected());
508524
if (!connected) return undefined;
509525

526+
await this.refreshSessionIfExpired(scope);
527+
510528
const issue = this.container.cache.getIssue(
511529
id,
512530
resource,
@@ -542,6 +560,8 @@ export abstract class IntegrationBase<
542560
const connected = this.maybeConnected ?? (await this.isConnected());
543561
if (!connected) return undefined;
544562

563+
await this.refreshSessionIfExpired(scope);
564+
545565
const { expiryOverride, ...opts } = options ?? {};
546566

547567
const currentAccount = await this.container.cache.getCurrentAccount(
@@ -574,6 +594,8 @@ export abstract class IntegrationBase<
574594
const connected = this.maybeConnected ?? (await this.isConnected());
575595
if (!connected) return undefined;
576596

597+
await this.refreshSessionIfExpired(scope);
598+
577599
const pr = await this.container.cache.getPullRequest(id, resource, this, () => ({
578600
value: (async () => {
579601
try {
@@ -604,9 +626,12 @@ export abstract class IssueIntegration<
604626
@gate()
605627
@debug()
606628
async getAccountForResource(resource: T): Promise<Account | undefined> {
629+
const scope = getLogScope();
607630
const connected = this.maybeConnected ?? (await this.isConnected());
608631
if (!connected) return undefined;
609632

633+
await this.refreshSessionIfExpired(scope);
634+
610635
try {
611636
const account = await this.getProviderAccountForResource(this._session!, resource);
612637
this.resetRequestExceptionCount();
@@ -624,9 +649,12 @@ export abstract class IssueIntegration<
624649
@gate()
625650
@debug()
626651
async getResourcesForUser(): Promise<T[] | undefined> {
652+
const scope = getLogScope();
627653
const connected = this.maybeConnected ?? (await this.isConnected());
628654
if (!connected) return undefined;
629655

656+
await this.refreshSessionIfExpired(scope);
657+
630658
try {
631659
const resources = await this.getProviderResourcesForUser(this._session!);
632660
this.resetRequestExceptionCount();
@@ -640,9 +668,12 @@ export abstract class IssueIntegration<
640668

641669
@debug()
642670
async getProjectsForResources(resources: T[]): Promise<T[] | undefined> {
671+
const scope = getLogScope();
643672
const connected = this.maybeConnected ?? (await this.isConnected());
644673
if (!connected) return undefined;
645674

675+
await this.refreshSessionIfExpired(scope);
676+
646677
try {
647678
const projects = await this.getProviderProjectsForResources(this._session!, resources);
648679
this.resetRequestExceptionCount();
@@ -669,9 +700,12 @@ export abstract class IssueIntegration<
669700
project: T,
670701
options?: { user?: string; filters?: IssueFilter[] },
671702
): Promise<IssueShape[] | undefined> {
703+
const scope = getLogScope();
672704
const connected = this.maybeConnected ?? (await this.isConnected());
673705
if (!connected) return undefined;
674706

707+
await this.refreshSessionIfExpired(scope);
708+
675709
try {
676710
const issues = await this.getProviderIssuesForProject(this._session!, project, options);
677711
this.resetRequestExceptionCount();
@@ -708,6 +742,8 @@ export abstract class HostingIntegration<
708742
const connected = this.maybeConnected ?? (await this.isConnected());
709743
if (!connected) return undefined;
710744

745+
await this.refreshSessionIfExpired(scope);
746+
711747
try {
712748
const author = await this.getProviderAccountForEmail(this._session!, repo, email, options);
713749
this.resetRequestExceptionCount();
@@ -740,6 +776,8 @@ export abstract class HostingIntegration<
740776
const connected = this.maybeConnected ?? (await this.isConnected());
741777
if (!connected) return undefined;
742778

779+
await this.refreshSessionIfExpired(scope);
780+
743781
try {
744782
const author = await this.getProviderAccountForCommit(this._session!, repo, rev, options);
745783
this.resetRequestExceptionCount();
@@ -768,6 +806,8 @@ export abstract class HostingIntegration<
768806
const connected = this.maybeConnected ?? (await this.isConnected());
769807
if (!connected) return undefined;
770808

809+
await this.refreshSessionIfExpired(scope);
810+
771811
const defaultBranch = this.container.cache.getRepositoryDefaultBranch(
772812
repo,
773813
this,
@@ -805,6 +845,8 @@ export abstract class HostingIntegration<
805845
const connected = this.maybeConnected ?? (await this.isConnected());
806846
if (!connected) return undefined;
807847

848+
await this.refreshSessionIfExpired(scope);
849+
808850
const metadata = this.container.cache.getRepositoryMetadata(
809851
repo,
810852
this,
@@ -845,6 +887,8 @@ export abstract class HostingIntegration<
845887
const connected = this.maybeConnected ?? (await this.isConnected());
846888
if (!connected) return false;
847889

890+
await this.refreshSessionIfExpired(scope);
891+
848892
try {
849893
const result = await this.mergeProviderPullRequest(this._session!, pr, options);
850894
this.resetRequestExceptionCount();
@@ -877,6 +921,8 @@ export abstract class HostingIntegration<
877921
const connected = this.maybeConnected ?? (await this.isConnected());
878922
if (!connected) return undefined;
879923

924+
await this.refreshSessionIfExpired(scope);
925+
880926
const { expiryOverride, ...opts } = options ?? {};
881927

882928
const pr = this.container.cache.getPullRequestForBranch(
@@ -920,6 +966,8 @@ export abstract class HostingIntegration<
920966
const connected = this.maybeConnected ?? (await this.isConnected());
921967
if (!connected) return undefined;
922968

969+
await this.refreshSessionIfExpired(scope);
970+
923971
const pr = this.container.cache.getPullRequestForSha(
924972
rev,
925973
repo,
@@ -954,10 +1002,13 @@ export abstract class HostingIntegration<
9541002
customUrl?: string;
9551003
},
9561004
): Promise<PagedResult<ProviderIssue> | undefined> {
1005+
const scope = getLogScope();
9571006
const providerId = this.authProvider.id;
9581007
const connected = this.maybeConnected ?? (await this.isConnected());
9591008
if (!connected) return undefined;
9601009

1010+
await this.refreshSessionIfExpired(scope);
1011+
9611012
const api = await this.getProvidersApi();
9621013
if (
9631014
providerId !== HostingIntegrationId.GitLab &&
@@ -1157,10 +1208,13 @@ export abstract class HostingIntegration<
11571208
customUrl?: string;
11581209
},
11591210
): Promise<PagedResult<ProviderPullRequest> | undefined> {
1211+
const scope = getLogScope();
11601212
const providerId = this.authProvider.id;
11611213
const connected = this.maybeConnected ?? (await this.isConnected());
11621214
if (!connected) return undefined;
11631215

1216+
await this.refreshSessionIfExpired(scope);
1217+
11641218
const api = await this.getProvidersApi();
11651219
if (
11661220
providerId !== HostingIntegrationId.GitLab &&
@@ -1319,6 +1373,8 @@ export abstract class HostingIntegration<
13191373
const connected = this.maybeConnected ?? (await this.isConnected());
13201374
if (!connected) return undefined;
13211375

1376+
await this.refreshSessionIfExpired(scope);
1377+
13221378
const start = Date.now();
13231379
try {
13241380
const pullRequests = await this.searchProviderMyPullRequests(
@@ -1361,6 +1417,8 @@ export abstract class HostingIntegration<
13611417
const connected = this.maybeConnected ?? (await this.isConnected());
13621418
if (!connected) return undefined;
13631419

1420+
await this.refreshSessionIfExpired(scope);
1421+
13641422
try {
13651423
const prs = await this.searchProviderPullRequests?.(
13661424
this._session!,

src/plus/integrations/providers/providersApi.ts

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
AuthenticationError,
1111
AuthenticationErrorReason,
1212
RequestClientError,
13+
RequestNotFoundError,
1314
RequestRateLimitError,
1415
} from '../../../errors';
1516
import type { PagedResult } from '../../../git/gitProvider';
@@ -378,15 +379,17 @@ export class ProvidersApi {
378379
throw new Error(`Provider with id ${providerId} not registered`);
379380
}
380381

381-
switch (providerId) {
382-
case IssueIntegrationId.Jira: {
383-
if (error?.response?.status != null) {
384-
if (error.response.status === 401) {
385-
throw new AuthenticationError(providerId, AuthenticationErrorReason.Forbidden, error);
386-
} else if (error.response.status === 429) {
382+
if (error?.response?.status != null) {
383+
switch (error.response.status) {
384+
case 404: // Not found
385+
case 410: // Gone
386+
case 422: // Unprocessable Entity
387+
throw new RequestNotFoundError(error);
388+
case 401: // Unauthorized
389+
if (error.message?.includes('rate limit')) {
387390
let resetAt: number | undefined;
388391

389-
const reset = error.response.headers?.['x-ratelimit-reset'];
392+
const reset = error.response?.headers?.['x-ratelimit-reset'];
390393
if (reset != null) {
391394
resetAt = parseInt(reset, 10);
392395
if (Number.isNaN(resetAt)) {
@@ -395,16 +398,32 @@ export class ProvidersApi {
395398
}
396399

397400
throw new RequestRateLimitError(error, token, resetAt);
398-
} else if (error.response.status >= 400 && error.response.status < 500) {
399-
throw new RequestClientError(error);
400401
}
402+
throw new AuthenticationError(providerId, AuthenticationErrorReason.Unauthorized, error);
403+
case 403: // Forbidden
404+
throw new AuthenticationError(providerId, AuthenticationErrorReason.Forbidden, error);
405+
case 429: {
406+
// Too Many Requests
407+
let resetAt: number | undefined;
408+
409+
const reset = error.response.headers?.['x-ratelimit-reset'];
410+
if (reset != null) {
411+
resetAt = parseInt(reset, 10);
412+
if (Number.isNaN(resetAt)) {
413+
resetAt = undefined;
414+
}
415+
}
416+
417+
throw new RequestRateLimitError(error, token, resetAt);
401418
}
402-
throw error;
403-
}
404-
default: {
405-
throw error;
419+
default:
420+
if (error.response.status >= 400 && error.response.status < 500) {
421+
throw new RequestClientError(error);
422+
}
406423
}
407424
}
425+
426+
throw error;
408427
}
409428

410429
async getPagedResult<T>(

0 commit comments

Comments
 (0)