Skip to content

Commit 89c9dea

Browse files
Completes the cloud integration reauth flow by disconnecting cloud token (#3636)
* Completes the cloud integration reauth flow by disconnecting cloud token * Kicks out early if the disconnect failed
1 parent 7cd77ff commit 89c9dea

File tree

3 files changed

+58
-18
lines changed

3 files changed

+58
-18
lines changed

src/constants.telemetry.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ export type TelemetryEvents = {
6565
'integration.connected.ids': string | undefined;
6666
};
6767

68+
/** Sent when disconnecting a provider from the api fails*/
69+
'cloudIntegrations/disconnect/failed': {
70+
code: number | undefined;
71+
'integration.id': string | undefined;
72+
};
73+
6874
/** Sent when getting connected providers from the api fails*/
6975
'cloudIntegrations/getConnections/failed': {
7076
code: number | undefined;

src/plus/integrations/authentication/cloudIntegrationService.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,36 @@ export class CloudIntegrationService {
9090

9191
return (await tokenRsp.json())?.data as Promise<CloudIntegrationAuthenticationSession | undefined>;
9292
}
93+
94+
async disconnect(id: IntegrationId): Promise<boolean> {
95+
const scope = getLogScope();
96+
97+
const cloudIntegrationType = toCloudIntegrationType[id];
98+
if (cloudIntegrationType == null) {
99+
Logger.error(undefined, scope, `Unsupported cloud integration type: ${id}`);
100+
return false;
101+
}
102+
103+
const tokenRsp = await this.connection.fetchGkDevApi(
104+
`v1/provider-tokens/${cloudIntegrationType}`,
105+
{ method: 'DELETE' },
106+
{ organizationId: false },
107+
);
108+
if (!tokenRsp.ok) {
109+
const error = (await tokenRsp.json())?.error;
110+
const errorMessage = typeof error === 'string' ? error : (error?.message as string) ?? tokenRsp.statusText;
111+
if (error != null) {
112+
Logger.error(undefined, scope, `Failed to disconnect ${id} token from cloud: ${errorMessage}`);
113+
}
114+
if (this.container.telemetry.enabled) {
115+
this.container.telemetry.sendEvent('cloudIntegrations/disconnect/failed', {
116+
code: tokenRsp.status,
117+
'integration.id': id,
118+
});
119+
}
120+
return false;
121+
}
122+
123+
return true;
124+
}
93125
}

src/plus/integrations/authentication/integrationAuthentication.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ export abstract class CloudIntegrationAuthenticationProvider<
267267
}
268268

269269
protected override async fetchOrCreateSession(
270-
storedSession: ProviderAuthenticationSession | undefined,
270+
_storedSession: ProviderAuthenticationSession | undefined,
271271
descriptor?: IntegrationAuthenticationSessionDescriptor,
272272
options?:
273273
| {
@@ -285,6 +285,14 @@ export abstract class CloudIntegrationAuthenticationProvider<
285285
source?: Sources;
286286
},
287287
): Promise<ProviderAuthenticationSession | undefined> {
288+
if (options?.forceNewSession) {
289+
if (!(await this.disconnectSession())) {
290+
return undefined;
291+
}
292+
293+
void this.connectCloudIntegration(false, options?.source);
294+
return undefined;
295+
}
288296
// TODO: This is a stopgap to make sure we're not hammering the api on automatic calls to get the session.
289297
// Ultimately we want to timestamp calls to syncCloudIntegrations and use that to determine whether we should
290298
// make the call or not.
@@ -293,10 +301,7 @@ export abstract class CloudIntegrationAuthenticationProvider<
293301
? await this.fetchSession(descriptor)
294302
: undefined;
295303

296-
if (shouldForceNewSession(storedSession, session, options)) {
297-
// TODO: gk.dev doesn't yet support forcing a new session, so the user will need to disconnect and reconnect
298-
void this.connectCloudIntegration(false, options?.source);
299-
} else if (shouldCreateSession(session, options)) {
304+
if (shouldCreateSession(session, options)) {
300305
const connected = await this.connectCloudIntegration(true, options?.source);
301306
if (!connected) return undefined;
302307
session = await this.getSession(descriptor, { source: options?.source });
@@ -357,6 +362,16 @@ export abstract class CloudIntegrationAuthenticationProvider<
357362
};
358363
}
359364

365+
private async disconnectSession(): Promise<boolean> {
366+
const loggedIn = await this.container.subscription.getAuthenticationSession(false);
367+
if (!loggedIn) return false;
368+
369+
const cloudIntegrations = await this.container.cloudIntegrations;
370+
if (cloudIntegrations == null) return false;
371+
372+
return cloudIntegrations.disconnect(this.authProviderId);
373+
}
374+
360375
private async openCompletionInput(cancellationToken: CancellationToken) {
361376
const input = window.createInputBox();
362377
input.ignoreFocusOut = true;
@@ -569,19 +584,6 @@ function convertStoredSessionToSession(
569584
};
570585
}
571586

572-
function shouldForceNewSession(
573-
storedSession: ProviderAuthenticationSession | undefined,
574-
newSession: ProviderAuthenticationSession | undefined,
575-
options?: { createIfNeeded?: boolean; forceNewSession?: boolean },
576-
) {
577-
return (
578-
options?.forceNewSession &&
579-
storedSession != null &&
580-
newSession != null &&
581-
storedSession.accessToken === newSession.accessToken
582-
);
583-
}
584-
585587
function shouldCreateSession(
586588
storedSession: ProviderAuthenticationSession | undefined,
587589
options?: { createIfNeeded?: boolean; forceNewSession?: boolean },

0 commit comments

Comments
 (0)