Skip to content

Commit 203837c

Browse files
authored
fix(auth): address several edge-cases (#3323)
## Problem * Builder ID profiles are showing in the connection picker * Users can rarely be left in a bad auth state ## Solution * Hide builder ID profiles * Clean profiles left in a bad state on reload * Free invalid credentials lock after reauth
1 parent bf612d8 commit 203837c

File tree

3 files changed

+45
-3
lines changed

3 files changed

+45
-3
lines changed

src/credentials/auth.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { codicon, getIcon } from '../shared/icons'
1616
import { Commands } from '../shared/vscode/commands2'
1717
import { createQuickPick, DataQuickPickItem, showQuickPick } from '../shared/ui/pickerPrompter'
1818
import { isValidResponse } from '../shared/wizards/wizard'
19-
import { CancellationError } from '../shared/utilities/timeoutUtils'
19+
import { CancellationError, Timeout } from '../shared/utilities/timeoutUtils'
2020
import { errorCode, formatError, ToolkitError, UnknownError } from '../shared/errors'
2121
import { getCache } from './sso/cache'
2222
import { createFactoryFunction, Mutable } from '../shared/utilities/tsUtils'
@@ -306,6 +306,7 @@ export class Auth implements AuthService, ConnectionManager {
306306
readonly #validationErrors = new Map<Connection['id'], Error>()
307307
readonly #onDidChangeActiveConnection = new vscode.EventEmitter<StatefulConnection | undefined>()
308308
readonly #onDidChangeConnectionState = new vscode.EventEmitter<ConnectionStateChangeEvent>()
309+
readonly #invalidCredentialsTimeouts = new Map<Connection['id'], Timeout>()
309310
public readonly onDidChangeActiveConnection = this.#onDidChangeActiveConnection.event
310311
public readonly onDidChangeConnectionState = this.#onDidChangeConnectionState.event
311312

@@ -495,6 +496,7 @@ export class Auth implements AuthService, ConnectionManager {
495496
const profile = await this.store.updateProfile(id, { connectionState })
496497
if (connectionState !== 'invalid') {
497498
this.#validationErrors.delete(id)
499+
this.#invalidCredentialsTimeouts.get(id)?.dispose()
498500
}
499501

500502
if (this.#activeConnection?.id === id) {
@@ -680,10 +682,16 @@ export class Auth implements AuthService, ConnectionManager {
680682
cause: this.#validationErrors.get(id),
681683
})
682684
}
683-
// TODO: cancellable notification?
684685
if (previousState === 'valid') {
686+
const timeout = new Timeout(60000)
687+
this.#invalidCredentialsTimeouts.set(id, timeout)
688+
685689
const message = localize('aws.auth.invalidConnection', 'Connection is invalid or expired, login again?')
686-
const resp = await vscode.window.showInformationMessage(message, localizedText.yes, localizedText.no)
690+
const resp = await Promise.race([
691+
vscode.window.showInformationMessage(message, localizedText.yes, localizedText.no),
692+
timeout.promisify(),
693+
])
694+
687695
if (resp !== localizedText.yes) {
688696
throw new ToolkitError('User cancelled login', {
689697
cancelled: true,
@@ -701,6 +709,16 @@ export class Auth implements AuthService, ConnectionManager {
701709
return
702710
}
703711

712+
// Clear anything stuck in an 'authenticating...' state
713+
// This can rarely happen when closing VS Code during authentication
714+
await Promise.all(
715+
this.store.listProfiles().map(async ([id, profile]) => {
716+
if (profile.metadata.connectionState === 'authenticating') {
717+
await this.store.updateProfile(id, { connectionState: 'invalid' })
718+
}
719+
})
720+
)
721+
704722
// Use the environment token if available
705723
if (getCodeCatalystDevEnvId() !== undefined) {
706724
const profile = createBuilderIdProfile()

src/credentials/providers/sharedCredentialsProvider.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
Section,
3232
} from '../sharedCredentials'
3333
import { hasScopes, SsoProfile } from '../auth'
34+
import { builderIdStartUrl } from '../sso/model'
3435

3536
const sharedCredentialProperties = {
3637
AWS_ACCESS_KEY_ID: 'aws_access_key_id',
@@ -138,6 +139,20 @@ export class SharedCredentialsProvider implements CredentialsProvider {
138139
getLogger().error(`Profile ${this.profileName} is not a valid Credential Profile: ${validationMessage}`)
139140
return false
140141
}
142+
143+
// XXX: hide builder ID profiles until account linking is supported
144+
try {
145+
const ssoProfile = this.getSsoProfileFromProfile()
146+
if (ssoProfile.startUrl === builderIdStartUrl) {
147+
getLogger().verbose(
148+
`Profile ${this.profileName} uses Builder ID which is not supported for sigv4 auth.`
149+
)
150+
return false
151+
}
152+
} catch {
153+
// Swallow error. Continue as-if it were valid.
154+
}
155+
141156
return true
142157
}
143158

src/test/credentials/auth.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,15 @@ describe('Auth', function () {
214214
assert.notStrictEqual(t1.accessToken, t3.accessToken, 'Access tokens should change after `reauthenticate`')
215215
})
216216

217+
it('releases all notification locks after reauthenticating', async function () {
218+
const conn = await setupInvalidSsoConnection(auth, ssoProfile)
219+
const pendingToken = conn.getToken()
220+
await getTestWindow().waitForMessage(/connection is invalid or expired/i)
221+
await auth.reauthenticate(conn)
222+
await assert.rejects(pendingToken)
223+
assert.ok(await conn.getToken())
224+
})
225+
217226
describe('SSO Connections', function () {
218227
async function runExpiredGetTokenFlow(conn: SsoConnection, selection: string | RegExp) {
219228
const token = conn.getToken()

0 commit comments

Comments
 (0)