Skip to content

Commit 75da796

Browse files
ctiddC Tidd
andauthored
fix(codecatalyst): ensure connection for Dev Environment URI handler flow #4063
Problem: When connecting to Dev Environment while not already authenticated locally, an error is displayed, which then requires manually working around the issue by entering startUrl through the main connect flow. Solution: Based on the URI handler params, attempt to connect or switch connection. Co-authored-by: C Tidd <[email protected]>
1 parent 3c990f4 commit 75da796

File tree

5 files changed

+124
-12
lines changed

5 files changed

+124
-12
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Fix a not connected error when starting connection to CodeCatalyst Dev Environment from link"
4+
}

src/codecatalyst/auth.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
isIdcSsoConnection,
2424
} from '../auth/connection'
2525
import { createBuilderIdConnection } from '../auth/utils'
26+
import { builderIdStartUrl } from '../auth/sso/model'
2627

2728
// Secrets stored on the macOS keychain appear as individual entries for each key
2829
// This is fine so long as the user has only a few accounts. Otherwise this should
@@ -261,6 +262,19 @@ export class CodeCatalystAuthenticationProvider {
261262
return this.secondaryAuth.useNewConnection(conn)
262263
}
263264

265+
/**
266+
* Try to ensure a specific connection is active.
267+
*/
268+
public async tryConnectTo(connection: { startUrl: string; region: string }) {
269+
if (!this.isConnectionValid() || connection.startUrl !== this.activeConnection!.startUrl) {
270+
if (connection.startUrl === builderIdStartUrl) {
271+
await this.connectToAwsBuilderId()
272+
} else {
273+
await this.connectToEnterpriseSso(connection.startUrl, connection.region)
274+
}
275+
}
276+
}
277+
264278
public async isConnectionOnboarded(conn: SsoConnection, recheck = false) {
265279
const mementoKey = 'codecatalyst.connections'
266280
const getState = () => this.memento.get(mementoKey, {} as Record<string, { onboarded: boolean }>)

src/codecatalyst/commands.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import { showConfirmationMessage } from '../shared/utilities/messages'
2424
import { AccountStatus } from '../shared/telemetry/telemetryClient'
2525
import { CreateDevEnvironmentRequest, UpdateDevEnvironmentRequest } from 'aws-sdk/clients/codecatalyst'
2626
import { Auth } from '../auth/auth'
27-
import { SsoConnection } from '../auth/connection'
27+
import { SsoConnection, defaultSsoRegion } from '../auth/connection'
28+
import { builderIdStartUrl } from '../auth/sso/model'
2829

2930
/** "List CodeCatalyst Commands" command. */
3031
export async function listCommands(): Promise<void> {
@@ -138,6 +139,7 @@ function createClientInjector(authProvider: CodeCatalystAuthenticationProvider):
138139
await authProvider.restore()
139140
const conn = authProvider.activeConnection
140141
if (!conn) {
142+
// TODO: In the future, it would be very nice to open a connection picker here.
141143
throw new ToolkitError('Not connected to CodeCatalyst', { code: 'NoConnectionBadState' })
142144
}
143145
const validatedConn = await validateConnection(conn, authProvider.auth)
@@ -283,11 +285,10 @@ export class CodeCatalystCommands {
283285
telemetry.record({ source: 'CommandPalette' })
284286
}
285287

286-
return this.withClient(openDevEnv, devenv, targetPath, async () => {
287-
if (connection) {
288-
await this.authProvider.connectToEnterpriseSso(connection.startUrl, connection.region)
289-
}
290-
})
288+
// Try to ensure active connection, defaulting to BuilderID if not specified:
289+
await this.authProvider.tryConnectTo(connection ?? { startUrl: builderIdStartUrl, region: defaultSsoRegion })
290+
291+
return this.withClient(openDevEnv, devenv, targetPath)
291292
}
292293

293294
public async openDevEnvSettings(): Promise<void> {

src/codecatalyst/model.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,8 @@ export async function prepareDevEnvConnection(
252252
export async function openDevEnv(
253253
client: CodeCatalystClient,
254254
devenv: DevEnvironmentId,
255-
targetPath?: string,
256-
onBeforeConnect?: () => Promise<void>
255+
targetPath?: string
257256
): Promise<void> {
258-
if (onBeforeConnect) {
259-
await onBeforeConnect()
260-
}
261-
262257
const env = await prepareDevEnvConnection(client, devenv, { topic: 'connect' })
263258

264259
if (!targetPath) {

src/test/codecatalyst/auth.test.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,102 @@ describe('CodeCatalystAuthenticationProvider', async function () {
115115
assert.deepStrictEqual(conn.scopes, [otherScope, ...defaultScopes])
116116
})
117117
})
118+
119+
describe('tryConnectTo', async () => {
120+
it('should do nothing if connection is already active', async function () {
121+
Sinon.stub(codecatalystAuth, 'isConnectionOnboarded').resolves(true)
122+
const connectToEnterpriseSso = Sinon.spy(codecatalystAuth, 'connectToEnterpriseSso')
123+
124+
getTestWindow().onDidShowQuickPick(async picker => {
125+
await picker.untilReady()
126+
picker.acceptItem(picker.items[1])
127+
})
128+
129+
await codecatalystAuth.connectToEnterpriseSso(enterpriseSsoStartUrl, 'us-east-1')
130+
let conn = codecatalystAuth.activeConnection
131+
assert.strictEqual(conn?.type, 'sso')
132+
assert.strictEqual(conn.label, 'IAM Identity Center (enterprise)')
133+
134+
await codecatalystAuth.tryConnectTo({ startUrl: enterpriseSsoStartUrl, region: 'us-east-1' })
135+
conn = codecatalystAuth.activeConnection
136+
assert.strictEqual(conn?.type, 'sso')
137+
assert.strictEqual(conn.label, 'IAM Identity Center (enterprise)')
138+
139+
assert.strictEqual(connectToEnterpriseSso.callCount, 1, 'Expected no extra calls on active connection')
140+
})
141+
142+
it('should switch to IAM Identity Center', async function () {
143+
Sinon.stub(codecatalystAuth, 'isConnectionOnboarded').resolves(true)
144+
const connectToEnterpriseSso = Sinon.spy(codecatalystAuth, 'connectToEnterpriseSso')
145+
146+
getTestWindow().onDidShowQuickPick(async picker => {
147+
await picker.untilReady()
148+
picker.acceptItem(picker.items[1])
149+
})
150+
151+
await codecatalystAuth.connectToEnterpriseSso(enterpriseSsoStartUrl, 'us-east-1')
152+
let conn = codecatalystAuth.activeConnection
153+
assert.strictEqual(conn?.type, 'sso')
154+
assert.strictEqual(conn.label, 'IAM Identity Center (enterprise)')
155+
assert.strictEqual(connectToEnterpriseSso.callCount, 1, 'Expected one call to connectToEnterpriseSso')
156+
157+
getTestWindow().onDidShowQuickPick(async picker => {
158+
await picker.untilReady()
159+
picker.acceptItem(picker.items[1])
160+
})
161+
162+
await codecatalystAuth.tryConnectTo({
163+
startUrl: 'https://other-enterprise.awsapps.com/start',
164+
region: 'us-east-1',
165+
})
166+
conn = codecatalystAuth.activeConnection
167+
assert.strictEqual(conn?.type, 'sso')
168+
assert.strictEqual(conn.label, 'IAM Identity Center (other-enterprise)')
169+
assert.strictEqual(conn.startUrl, 'https://other-enterprise.awsapps.com/start')
170+
171+
assert.strictEqual(
172+
connectToEnterpriseSso.callCount,
173+
2,
174+
'Expected two calls to complete switch for connectToEnterpriseSso'
175+
)
176+
})
177+
178+
it('should switch to Builder ID', async function () {
179+
Sinon.stub(codecatalystAuth, 'isConnectionOnboarded').resolves(true)
180+
const connectToAwsBuilderId = Sinon.spy(codecatalystAuth, 'connectToAwsBuilderId')
181+
const connectToEnterpriseSso = Sinon.spy(codecatalystAuth, 'connectToEnterpriseSso')
182+
183+
getTestWindow().onDidShowQuickPick(async picker => {
184+
await picker.untilReady()
185+
picker.acceptItem(picker.items[1])
186+
})
187+
188+
await codecatalystAuth.tryConnectTo({
189+
startUrl: 'https://other-enterprise.awsapps.com/start',
190+
region: 'us-east-1',
191+
})
192+
let conn = codecatalystAuth.activeConnection
193+
assert.strictEqual(conn?.type, 'sso')
194+
assert.strictEqual(conn.label, 'IAM Identity Center (other-enterprise)')
195+
assert.strictEqual(conn.startUrl, 'https://other-enterprise.awsapps.com/start')
196+
197+
assert.strictEqual(connectToEnterpriseSso.callCount, 1, 'Expected one call to connectToEnterpriseSso')
198+
199+
getTestWindow().onDidShowQuickPick(async picker => {
200+
await picker.untilReady()
201+
picker.acceptItem(picker.items[1])
202+
})
203+
204+
await codecatalystAuth.connectToAwsBuilderId()
205+
conn = codecatalystAuth.activeConnection
206+
assert.strictEqual(conn?.type, 'sso')
207+
assert.strictEqual(conn.label, 'AWS Builder ID')
208+
assert.strictEqual(connectToAwsBuilderId.callCount, 1, 'Expected one call to connectToAwsBuilderId')
209+
assert.strictEqual(
210+
connectToEnterpriseSso.callCount,
211+
1,
212+
'Expected no additional calls to connectToEnterpriseSso'
213+
)
214+
})
215+
})
118216
})

0 commit comments

Comments
 (0)