Skip to content

Commit 1985492

Browse files
authWebview: Bug Bash fixes (#3620)
* identityCenter: Have button only say 'sign in' You can't really sign up for builder id Signed-off-by: Nikolas Komonen <[email protected]> * authForm: Show error on submit but empty field If a user clicks the submit button on a form but they have not entered information in to a certain field we will now show an errror message under that field. Fixes IDE-11014 Signed-off-by: Nikolas Komonen <[email protected]> * builderId: Submit button text changes on context If we have a builder id already signed in on subsequent builder id forms we do not want to prompt the user to 'sign in' again since they already are and it may confuse them. Solution: If we see builder id is already signed in then we want to change the text of the submit button for other builder id forms to have a cta to 'connect builder id' with the other service Signed-off-by: Nikolas Komonen <[email protected]> * bug: Builder ID add scopes fix + missing Event Problem: - Sign in to builder id for CW - Start sign in for builder id for CC - On the prompt that asks to copy code, click cancel We are now in a weird state where in the Tree View we see CW asking for 'Reconnect' but in webview it is still Enabled. Solution: - I was missing an Auth Event to trigger on when a connection is updated. - During the addScopes() process it will update the connection, and if we then cancelled the webview would be wrong since it only refreshed on a successful addScopes() run. - Now I added in the webview event listener to listen to the connection update event and it works Additional: If we update scopes of a connection but during reauthenticate() the user cancels, we are stuck in a weird state where the scopes on the unauthenticated connection include the new scopes. As a fix, we catch if the user cancels and revert the scopes to what they previously were. The user will still need to reauth but at least they are shown the actual state of things. Signed-off-by: Nikolas Komonen <[email protected]> * refactor: Get CCAuth instance without race condition Previously the code attempted to handle a race condition for getting the CC auth instance. Solution: Instead we will create the instance if it does not exist and pass it in as an arg to the class that needed it. Additionally, remove the old code that waited for the object to exist since it is not needed anymore. Signed-off-by: Nikolas Komonen <[email protected]> * refactor: Include service names in text content In the tabs + status bar we will now include more specific information regarding the service Signed-off-by: Nikolas Komonen <[email protected]> * preloadImages: in auth webview This uses the <img> tag to preload the images on initial webview load Signed-off-by: Nikolas Komonen <[email protected]> * refactor: is first extension use logic Previously we had a flag in settings.json to indicate if the extension was first used. But this could be manipulated easily by users. Solution: Create a class ExtensionUse that manages this for you and instead uses the vscode global state to keep track of this. Remove the old way we tracked if this was the first use. Signed-off-by: Nikolas Komonen <[email protected]> --------- Signed-off-by: Nikolas Komonen <[email protected]>
1 parent 3dabdad commit 1985492

File tree

16 files changed

+170
-58
lines changed

16 files changed

+170
-58
lines changed

package.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,10 +220,6 @@
220220
"codeWhispererConnectionExpired": {
221221
"type": "boolean",
222222
"default": false
223-
},
224-
"manageConnections": {
225-
"type": "boolean",
226-
"default": false
227223
}
228224
},
229225
"additionalProperties": false

src/auth/activation.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ import { LoginManager } from './deprecated/loginManager'
1010
import { fromString } from './providers/credentials'
1111
import { registerCommandsWithVSCode } from '../shared/vscode/commands2'
1212
import { AuthCommandBackend, AuthCommandDeclarations } from './commands'
13-
import { dontShow } from '../shared/localizedText'
14-
import { DevSettings, PromptSettings } from '../shared/settings'
15-
import { waitUntil } from '../shared/utilities/timeoutUtils'
16-
import { CodeCatalystAuthenticationProvider } from '../codecatalyst/auth'
13+
import { DevSettings } from '../shared/settings'
14+
import { ExtensionUse } from '../shared/utilities/vsCodeUtils'
1715

1816
export async function initialize(
1917
extensionContext: vscode.ExtensionContext,
@@ -49,20 +47,9 @@ export async function initialize(
4947
* again on next startup.
5048
*/
5149
async function showManageConnectionsOnStartup() {
52-
await waitUntil(() => Promise.resolve(CodeCatalystAuthenticationProvider.instance), {
53-
interval: 500,
54-
timeout: 10000,
55-
})
56-
const settings = PromptSettings.instance
57-
58-
if (!(await settings.isPromptEnabled('manageConnections'))) {
50+
if (!ExtensionUse.instance.isFirstUse()) {
5951
return
6052
}
6153

6254
AuthCommandDeclarations.instance.declared.showConnectionsPage.execute()
63-
vscode.window.showInformationMessage("Don't show Add Connections page on startup?", dontShow).then(selection => {
64-
if (selection === dontShow) {
65-
settings.disablePrompt('manageConnections')
66-
}
67-
})
6855
}

src/auth/secondaryAuth.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -235,15 +235,30 @@ export class SecondaryAuth<T extends Connection = Connection> {
235235

236236
public async addScopes(conn: T & SsoConnection, extraScopes: string[]) {
237237
await promptForRescope(conn, this.toolLabel)
238-
const scopes = Array.from(new Set([...(conn.scopes ?? []), ...extraScopes]))
239-
const updatedConn = await this.auth.updateConnection(conn, {
240-
type: 'sso',
241-
scopes,
242-
startUrl: conn.startUrl,
243-
ssoRegion: conn.ssoRegion,
244-
})
238+
const oldScopes = conn.scopes ?? []
239+
const newScopes = Array.from(new Set([...oldScopes, ...extraScopes]))
240+
241+
const updateConnectionScopes = (scopes: string[]) => {
242+
return this.auth.updateConnection(conn, {
243+
type: 'sso',
244+
scopes,
245+
startUrl: conn.startUrl,
246+
ssoRegion: conn.ssoRegion,
247+
})
248+
}
249+
250+
const updatedConn = await updateConnectionScopes(newScopes)
245251

246-
return this.auth.reauthenticate(updatedConn)
252+
try {
253+
return await this.auth.reauthenticate(updatedConn)
254+
} catch (e) {
255+
if (CancellationError.isUserCancelled(e)) {
256+
// We updated the connection scopes, but the user cancelled reauth.
257+
// Revert to old connection scopes, otherwise the new scopes persist.
258+
await updateConnectionScopes(oldScopes)
259+
}
260+
throw e
261+
}
247262
}
248263

249264
// Used to lazily restore persisted connections.

src/auth/ui/vue/authForms/manageBuilderId.vue

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</div>
1313

1414
<div class="form-section">
15-
<button v-on:click="startSignIn()">Sign up or Sign in</button>
15+
<button v-on:click="startSignIn()">{{ submitButtonText }}</button>
1616
<div class="small-description error-text">{{ error }}</div>
1717
</div>
1818
</div>
@@ -67,6 +67,7 @@ export default defineComponent({
6767
name: this.state.name,
6868
error: '' as string,
6969
signUpUrl: '' as string,
70+
submitButtonText: '' as string,
7071
}
7172
},
7273
async created() {
@@ -84,6 +85,7 @@ export default defineComponent({
8485
}
8586
},
8687
async update(cause?: ConnectionUpdateCause) {
88+
await this.updateSubmitButtonText()
8789
this.stage = await this.state.stage()
8890
this.isConnected = await this.state.isAuthConnected()
8991
this.emitAuthConnectionUpdated({ id: this.state.id, isConnected: this.isConnected, cause })
@@ -98,6 +100,13 @@ export default defineComponent({
98100
getSignUpUrl() {
99101
return this.state.getSignUpUrl()
100102
},
103+
async updateSubmitButtonText() {
104+
if (!(await isBuilderIdConnected())) {
105+
this.submitButtonText = 'Sign up or Sign in'
106+
} else {
107+
this.submitButtonText = `Connect AWS Builder ID with ${this.state.name}`
108+
}
109+
},
101110
},
102111
})
103112
@@ -113,6 +122,8 @@ abstract class BaseBuilderIdState implements AuthStatus {
113122
abstract isAuthConnected(): Promise<boolean>
114123
abstract showNodeInView(): Promise<void>
115124
125+
protected constructor() {}
126+
116127
async startBuilderIdSetup(): Promise<string> {
117128
this._stage = 'WAITING_ON_USER'
118129
return this._startBuilderIdSetup()
@@ -157,6 +168,12 @@ export class CodeWhispererBuilderIdState extends BaseBuilderIdState {
157168
override getSignUpUrl(): string {
158169
return 'https://docs.aws.amazon.com/codewhisperer/latest/userguide/whisper-setup-indv-devs.html'
159170
}
171+
172+
static #instance: CodeWhispererBuilderIdState | undefined
173+
174+
static get instance(): CodeWhispererBuilderIdState {
175+
return (this.#instance ??= new CodeWhispererBuilderIdState())
176+
}
160177
}
161178
162179
export class CodeCatalystBuilderIdState extends BaseBuilderIdState {
@@ -179,14 +196,31 @@ export class CodeCatalystBuilderIdState extends BaseBuilderIdState {
179196
override showNodeInView(): Promise<void> {
180197
return client.showCodeCatalystNode()
181198
}
199+
200+
static #instance: CodeCatalystBuilderIdState | undefined
201+
202+
static get instance(): CodeCatalystBuilderIdState {
203+
return (this.#instance ??= new CodeCatalystBuilderIdState())
204+
}
205+
}
206+
207+
/**
208+
* Returns true if any Builder Id is connected
209+
*/
210+
export async function isBuilderIdConnected(): Promise<boolean> {
211+
const results = await Promise.all([
212+
CodeWhispererBuilderIdState.instance.isAuthConnected(),
213+
CodeCatalystBuilderIdState.instance.isAuthConnected(),
214+
])
215+
return results.some(isConnected => isConnected)
182216
}
183217
</script>
184218
<style>
185219
@import './sharedAuthForms.css';
186220
@import '../shared.css';
187221
188222
#builder-id-form {
189-
width: 250px;
223+
width: 300px;
190224
height: fit-content;
191225
}
192226
</style>

src/auth/ui/vue/authForms/manageCredentials.vue

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
</div>
5050

5151
<div class="form-section">
52-
<button :disabled="!canSubmit" v-on:click="submitData()">Add Profile</button>
52+
<button v-on:click="submitData()">Add Profile</button>
5353
<div class="small-description error-text">{{ errors.submit }}</div>
5454
</div>
5555
</div>
@@ -150,6 +150,9 @@ export default defineComponent({
150150
})
151151
},
152152
async submitData() {
153+
if (this.setCannotBeEmptyErrors()) {
154+
return
155+
}
153156
// pre submission
154157
this.canSubmit = false // disable submit button
155158
@@ -168,6 +171,18 @@ export default defineComponent({
168171
this.canSubmit = true // enable submit button
169172
await this.updateConnectedStatus('signIn')
170173
},
174+
/**
175+
* Sets the 'cannot be empty' error for each empty field
176+
*
177+
* @returns true if there was an empty field, otherwise false
178+
*/
179+
setCannotBeEmptyErrors() {
180+
const emptyFields = Object.keys(this.data).filter(key => !this.data[key as keyof typeof this.data])
181+
emptyFields.forEach(fieldName => {
182+
this.errors[fieldName as keyof typeof this.data] = 'Cannot be empty.'
183+
})
184+
return emptyFields.length > 0
185+
},
171186
toggleShowForm() {
172187
this.isFormShown = !this.isFormShown
173188
},

src/auth/ui/vue/authForms/manageIdentityCenter.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
</div>
4040

4141
<div class="form-section">
42-
<button v-on:click="signin()" :disabled="!canSubmit">Sign up or Sign in</button>
42+
<button v-on:click="signin()">Sign in</button>
4343
<div class="small-description error-text">{{ errors.submit }}</div>
4444
</div>
4545
</div>
@@ -126,6 +126,12 @@ export default defineComponent({
126126
computed: {},
127127
methods: {
128128
async signin(): Promise<void> {
129+
console.log(this.data.startUrl)
130+
if (!this.data.startUrl) {
131+
this.errors.startUrl = 'Cannot be empty.'
132+
return
133+
}
134+
129135
this.stage = 'WAITING_ON_USER'
130136
this.errors.submit = await this.state.startIdentityCenterSetup()
131137

src/auth/ui/vue/authForms/shared.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import { CodeWhispererIdentityCenterState, ExplorerIdentityCenterState } from '.
88
*/
99
const authFormsState = {
1010
credentials: new CredentialsState() as CredentialsState,
11-
builderIdCodeWhisperer: new CodeWhispererBuilderIdState(),
12-
builderIdCodeCatalyst: new CodeCatalystBuilderIdState(),
11+
builderIdCodeWhisperer: CodeWhispererBuilderIdState.instance,
12+
builderIdCodeCatalyst: CodeCatalystBuilderIdState.instance,
1313
identityCenterCodeWhisperer: new CodeWhispererIdentityCenterState(),
14-
identityCenterExplorer: new ExplorerIdentityCenterState()
14+
identityCenterExplorer: new ExplorerIdentityCenterState(),
1515
} as const
1616
1717
export interface AuthStatus {

src/auth/ui/vue/authForms/types.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ export type AuthFormId =
1313

1414
export const AuthFormDisplayName: Record<AuthFormId, string> = {
1515
credentials: 'IAM Credentials',
16-
builderIdCodeCatalyst: 'Builder ID',
17-
builderIdCodeWhisperer: 'Builder ID',
18-
identityCenterCodeWhisperer: 'IAM Identity Center',
19-
identityCenterExplorer: 'IAM Identity Center',
20-
aggregateExplorer: ''
16+
builderIdCodeCatalyst: 'CodeCatalyst with AWS Builder ID',
17+
builderIdCodeWhisperer: 'CodeWhisperer with AWS Builder ID',
18+
identityCenterCodeWhisperer: 'CodeWhisperer with IAM Identity Center',
19+
identityCenterExplorer: 'Explorer with IAM Identity Center',
20+
aggregateExplorer: '',
2121
} as const

src/auth/ui/vue/root.vue

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
<template>
2+
<!--
3+
HACK: Want to prefetch images but <link ref="prefetch"> does not work.
4+
We use <img> instead but hide it.
5+
-->
6+
<img
7+
v-show="false"
8+
src="https://github.com/aws/aws-toolkit-vscode/raw/HEAD/docs/marketplace/vscode/CC_dev_env.gif"
9+
/>
10+
<link v-show="false" src="https://github.com/aws/aws-toolkit-vscode/raw/HEAD/docs/marketplace/vscode/S3.gif" />
11+
212
<div style="display: flex; flex-direction: column; gap: 20px; padding-top: 20px">
313
<!-- Status Bars -->
414
<div
@@ -21,11 +31,12 @@
2131
<div class="icon icon-lg icon-vscode-check"></div>
2232
&nbsp; &nbsp;
2333
<div style="display: flex; flex-direction: row">
24-
You're connected to {{ authFormDisplayName }}! Switch between connections in the&nbsp;<a
34+
Connected to&nbsp;<span style="font-weight: bold">{{ authFormDisplayName }}</span
35+
>! Switch between existing connections in the&nbsp;<a
2536
v-on:click="showConnectionQuickPick()"
2637
style="cursor: pointer"
2738
>Toolkit panel</a
28-
>&nbsp;or add additional connections below.
39+
>.
2940
</div>
3041
&nbsp;&nbsp;
3142
<div
@@ -54,7 +65,7 @@
5465
v-on:click="showConnectionQuickPick()"
5566
style="cursor: pointer"
5667
>Toolkit panel</a
57-
>&nbsp;.
68+
>.
5869
</div>
5970
&nbsp;&nbsp;
6071
<div

src/auth/ui/vue/serviceItem.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,15 @@ export default defineComponent({
128128
129129
const staticServiceItemProps: Readonly<Record<ServiceItemId, { title: string; description: string }>> = {
130130
resourceExplorer: {
131-
title: 'View, modify, and deploy AWS Resources',
131+
title: 'Explorer: View, modify, and deploy AWS Resources',
132132
description: 'Work with S3, CloudWatch, and more.',
133133
},
134134
codewhisperer: {
135-
title: 'AI-powered code suggestions from CodeWhisperer',
135+
title: 'CodeWhisperer: AI-powered code suggestions',
136136
description: 'Build applications faster with your AI coding companion.',
137137
},
138138
codecatalyst: {
139-
title: 'Launch CodeCatalyst Cloud-based Dev Environments',
139+
title: 'CodeCatalytst: Launch Cloud-based Dev Environments',
140140
description: 'Spark a faster planning, development, and delivery lifecycle on AWS.',
141141
},
142142
}

0 commit comments

Comments
 (0)