Skip to content

Commit 1b1bc89

Browse files
fix(codecatalyst): No solution for 'connection is invalid or expired'
Problem: When using an MDE + CodeCatalyst, if the BuilderId connection became expired, on attempts to use the CodeCatalyst client users would get an error 'Connection is invalid...' with no obvious way to resolve it. Solution: Provide the user the ability to re-authenticate their BuilderId. This commit verifies the BuilderId connection is valid before setting up the CodeCatalyst client. If it is not valid, users will be prompted to login again. They can click the login button on a pop up window, and be redirected through the BuilderId authentication process again. Signed-off-by: Nikolas Komonen <[email protected]>
1 parent d2983c2 commit 1b1bc89

File tree

3 files changed

+40
-5
lines changed

3 files changed

+40
-5
lines changed

src/codecatalyst/commands.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ import { DevEnvironmentId, getConnectedDevEnv, openDevEnv } from './model'
1818
import { showConfigureDevEnv } from './vue/configure/backend'
1919
import { showCreateDevEnv } from './vue/create/backend'
2020
import { CancellationError } from '../shared/utilities/timeoutUtils'
21-
import { ToolkitError } from '../shared/errors'
21+
import { ToolkitError, errorCode } from '../shared/errors'
2222
import { telemetry } from '../shared/telemetry/telemetry'
2323
import { showConfirmationMessage } from '../shared/utilities/messages'
2424
import { AccountStatus } from '../shared/telemetry/telemetryClient'
2525
import { CreateDevEnvironmentRequest, UpdateDevEnvironmentRequest } from 'aws-sdk/clients/codecatalyst'
26+
import { Auth, SsoConnection } from '../credentials/auth'
2627

2728
/** "List CodeCatalyst Commands" command. */
2829
export async function listCommands(): Promise<void> {
@@ -135,13 +136,43 @@ function createClientInjector(authProvider: CodeCatalystAuthenticationProvider):
135136

136137
await authProvider.restore()
137138
const conn = authProvider.activeConnection ?? (await authProvider.promptNotConnected())
138-
const client = await createClient(conn)
139+
const validatedConn = await validateConnection(conn, authProvider.auth)
140+
const client = await createClient(validatedConn)
139141
telemetry.record({ userId: client.identity.id })
140142

141143
return command(client, ...args)
142144
}
143145
}
144146

147+
/**
148+
* Returns a connection that is ensured to be authenticated.
149+
*
150+
* Provides the user the ability to re-authenticate if needed,
151+
* otherwise throwing an error.
152+
*/
153+
async function validateConnection(conn: SsoConnection, auth: Auth): Promise<SsoConnection> {
154+
if (auth.getConnectionState(conn) === 'valid') {
155+
return conn
156+
}
157+
158+
// Have user try to log in
159+
const loginMessage = localize('aws.auth.invalidConnection', 'Connection is invalid or expired, login again?')
160+
const result = await vscode.window.showErrorMessage(loginMessage, 'Login')
161+
162+
if (result !== 'Login') {
163+
throw new ToolkitError('User cancelled login.', { cancelled: true, code: errorCode.invalidConnection })
164+
}
165+
166+
conn = await auth.reauthenticate(conn)
167+
168+
// Log in attempt failed
169+
if (auth.getConnectionState(conn) !== 'valid') {
170+
throw new ToolkitError('Login failed.', { code: errorCode.invalidConnection })
171+
}
172+
173+
return conn
174+
}
175+
145176
function createCommandDecorator(commands: CodeCatalystCommands): CommandDecorator {
146177
return command =>
147178
(...args) =>

src/credentials/auth.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { Commands } from '../shared/vscode/commands2'
1717
import { createQuickPick, DataQuickPickItem, showQuickPick } from '../shared/ui/pickerPrompter'
1818
import { isValidResponse } from '../shared/wizards/wizard'
1919
import { CancellationError } from '../shared/utilities/timeoutUtils'
20-
import { formatError, ToolkitError, UnknownError } from '../shared/errors'
20+
import { errorCode, formatError, ToolkitError, UnknownError } from '../shared/errors'
2121
import { getCache } from './sso/cache'
2222
import { createFactoryFunction, Mutable } from '../shared/utilities/tsUtils'
2323
import { builderIdStartUrl, SsoToken } from './sso/model'
@@ -676,7 +676,7 @@ export class Auth implements AuthService, ConnectionManager {
676676

677677
if (previousState === 'invalid') {
678678
throw new ToolkitError('Connection is invalid or expired. Try logging in again.', {
679-
code: 'InvalidConnection',
679+
code: errorCode.invalidConnection,
680680
cause: this.#validationErrors.get(id),
681681
})
682682
}
@@ -687,7 +687,7 @@ export class Auth implements AuthService, ConnectionManager {
687687
if (resp !== localizedText.yes) {
688688
throw new ToolkitError('User cancelled login', {
689689
cancelled: true,
690-
code: 'InvalidConnection',
690+
code: errorCode.invalidConnection,
691691
cause: this.#validationErrors.get(id),
692692
})
693693
}

src/shared/errors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import { Result } from './telemetry/telemetry'
1111
import { CancellationError } from './utilities/timeoutUtils'
1212
import { isNonNullable } from './utilities/tsUtils'
1313

14+
export const errorCode = {
15+
invalidConnection: 'InvalidConnection',
16+
}
17+
1418
export interface ErrorInformation {
1519
/**
1620
* Error names are optional, but if provided they should be generic yet self-explanatory.

0 commit comments

Comments
 (0)