Skip to content

Commit d80e700

Browse files
authWebview: minor changes (#3599)
* links: Add in missing links Signed-off-by: Nikolas Komonen <[email protected]> * ssoError: Show SSO submission error message No when there is an error with SSO submission, an error message will appear under the submit button with an explanation why. Signed-off-by: Nikolas Komonen <[email protected]> * builderIdError: Show error if builder id setup cancelled During CW or CC builder id setup the user may cancel and end the process. We will show an error to indicate they cancelled under the sign up/sign in button. Signed-off-by: Nikolas Komonen <[email protected]> * alignForms: Aligns the auth forms to the left Signed-off-by: Nikolas Komonen <[email protected]> * codewhisperer: use connection after adding scopes In the scenario where an sso profile already existed and we wanted to add codewhisperer scopes to it, we would add scopes but not use the new sso connection. This change uses the new connection after adding scopes to it. Signed-off-by: Nikolas Komonen <[email protected]> * identityCenter: Allow auth setup of existing sso In the edge case where we set up the resource explorer with Identity Center, then setup CodeWhisperer with IdentityCenter we are not able to since the Identity Center setup for CW sees that the sso profile already exists. Solution: Do not check SSO profile already exists in CW, instead allow the user to proceed so the existing backend CW auth can add scopes to the connection. Signed-off-by: Nikolas Komonen <[email protected]> * typo: remove plural Signed-off-by: Nikolas Komonen <[email protected]> * identityCenter: Update form field subtext Signed-off-by: Nikolas Komonen <[email protected]> * identityCenter: Edit button to select region Instead of a dropdown menu UI to select the region, this replaces it with an edit button to open the region prompter Signed-off-by: Nikolas Komonen <[email protected]> * authWebview: Show on startup w/ option to disable On startup of the extension we will show the webview, and in the corner we will show an information message to not show the window again if they select the option Signed-off-by: Nikolas Komonen <[email protected]> * identityCenter: Clear data on successful submission In a certain scenario when we successfully submit, then try to add another IC connection we will see the old data. This commit clears the old data one a successful submission. Signed-off-by: Nikolas Komonen <[email protected]> * codewhisperer: refactor sso connection function We want to call the sso connection function which connects to the enterprise sso then calls some additional functionality (refresh and enable code suggestions), but want to skip the prompt to input the sso data. Solution: Extract the sso connection code from getStartUrl() so we can reuse it in our webview Signed-off-by: Nikolas Komonen <[email protected]> * greenBar: show credentials exist but none are selected Problem: In the scenario where a brand new user comes in with credentials already in their credentials file we are able to pick those up, but currently don't indicate this to the user. They must know to open the connection quick pick and then select the credential we found. Solution: in the auth webview, if we detect credentials and none are yet active/selected, we will show a status bar message directing them to select a credentials from the status bar. Signed-off-by: Nikolas Komonen <[email protected]> * greenBar: quick pick did not select I chose the wrong prompter which only returned the connection I chose, not actually use it. This updates to the correct one. Signed-off-by: Nikolas Komonen <[email protected]> --------- Signed-off-by: Nikolas Komonen <[email protected]>
1 parent 8fe6da0 commit d80e700

File tree

15 files changed

+262
-86
lines changed

15 files changed

+262
-86
lines changed

package.json

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

src/auth/activation.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ 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'
1317

1418
export async function initialize(
1519
extensionContext: vscode.ExtensionContext,
@@ -32,4 +36,33 @@ export async function initialize(
3236
AuthCommandDeclarations.instance,
3337
new AuthCommandBackend(extensionContext)
3438
)
39+
40+
if (DevSettings.instance.isDevMode()) {
41+
showManageConnectionsOnStartup()
42+
}
43+
}
44+
45+
/**
46+
* Show the Manage Connections page when the extension starts up.
47+
*
48+
* Additionally, we provide an information message with a button for users to not show it
49+
* again on next startup.
50+
*/
51+
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'))) {
59+
return
60+
}
61+
62+
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+
})
3568
}

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
<div class="form-section">
1717
<button v-on:click="startSignIn()">Sign up or Sign in</button>
18+
<div class="small-description error-text">{{ error }}</div>
1819
</div>
1920
</div>
2021

@@ -66,6 +67,7 @@ export default defineComponent({
6667
isConnected: false,
6768
builderIdCode: '',
6869
name: this.state.name,
70+
error: '' as string,
6971
}
7072
},
7173
async created() {
@@ -74,8 +76,12 @@ export default defineComponent({
7476
methods: {
7577
async startSignIn() {
7678
this.stage = 'WAITING_ON_USER'
77-
await this.state.startBuilderIdSetup()
78-
await this.update('signIn')
79+
this.error = await this.state.startBuilderIdSetup()
80+
if (this.error) {
81+
this.stage = await this.state.stage()
82+
} else {
83+
await this.update('signIn')
84+
}
7985
},
8086
async update(cause?: ConnectionUpdateCause) {
8187
this.stage = await this.state.stage()
@@ -100,11 +106,11 @@ abstract class BaseBuilderIdState implements AuthStatus {
100106
101107
abstract get name(): string
102108
abstract get id(): AuthFormId
103-
protected abstract _startBuilderIdSetup(): Promise<void>
109+
protected abstract _startBuilderIdSetup(): Promise<string>
104110
abstract isAuthConnected(): Promise<boolean>
105111
abstract showNodeInView(): Promise<void>
106112
107-
async startBuilderIdSetup(): Promise<void> {
113+
async startBuilderIdSetup(): Promise<string> {
108114
this._stage = 'WAITING_ON_USER'
109115
return this._startBuilderIdSetup()
110116
}
@@ -133,7 +139,7 @@ export class CodeWhispererBuilderIdState extends BaseBuilderIdState {
133139
return client.isCodeWhispererBuilderIdConnected()
134140
}
135141
136-
protected override _startBuilderIdSetup(): Promise<void> {
142+
protected override _startBuilderIdSetup(): Promise<string> {
137143
return client.startCodeWhispererBuilderIdSetup()
138144
}
139145
@@ -155,7 +161,7 @@ export class CodeCatalystBuilderIdState extends BaseBuilderIdState {
155161
return client.isCodeCatalystBuilderIdConnected()
156162
}
157163
158-
protected override _startBuilderIdSetup(): Promise<void> {
164+
protected override _startBuilderIdSetup(): Promise<string> {
159165
return client.startCodeCatalystBuilderIdSetup()
160166
}
161167

src/auth/ui/vue/authForms/manageExplorer.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="auth-form container-background border-common" id="identity-center-form">
2+
<div class="auth-form container-background border-common" id="explorer-form">
33
<div>
44
<FormTitle :isConnected="isConnected">{{ connectionName }}</FormTitle>
55
<div v-if="!isConnected">Successor to AWS Single Sign-on</div>
@@ -85,7 +85,7 @@ export default defineComponent({
8585
@import './sharedAuthForms.css';
8686
@import '../shared.css';
8787
88-
#identity-center-form {
88+
#explorer-form {
8989
width: 280px;
9090
height: fit-content;
9191
}

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

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,46 @@
11
<template>
22
<div class="auth-form container-background border-common" id="identity-center-form">
33
<div v-if="checkIfConnected">
4-
<FormTitle :isConnected="isConnected">IAM Identity Center</FormTitle>
4+
<FormTitle :isConnected="isConnected"
5+
>IAM Identity Center&nbsp;<a
6+
class="icon icon-lg icon-vscode-info"
7+
href="https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/sso-credentials.html"
8+
></a
9+
></FormTitle>
510
<div v-if="!isConnected">Successor to AWS Single Sign-on</div>
611
</div>
712
<div v-else>
813
<!-- In this scenario we do not care about the active IC connection -->
9-
<FormTitle :isConnected="false">IAM Identity Center</FormTitle>
14+
<FormTitle :isConnected="false"
15+
>IAM Identity Center&nbsp;<a
16+
class="icon icon-lg icon-vscode-info"
17+
href="https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/sso-credentials.html"
18+
></a
19+
></FormTitle>
1020
<div>Successor to AWS Single Sign-on</div>
1121
</div>
1222

1323
<div v-if="stage === 'START'">
14-
<div class="form-section">
15-
<a href="https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/sso-credentials.html"
16-
>Learn more about IAM Identity Center.</a
17-
>
18-
</div>
19-
2024
<div class="form-section">
2125
<label class="input-title">Start URL</label>
22-
<label class="small-description">The Start URL</label>
26+
<label class="small-description">URL for your organization, provided by an admin or help desk.</label>
2327
<input v-model="data.startUrl" type="text" :data-invalid="!!errors.startUrl" />
2428
<div class="small-description error-text">{{ errors.startUrl }}</div>
2529
</div>
2630

2731
<div class="form-section">
2832
<label class="input-title">Region</label>
29-
<label class="small-description">The Region</label>
33+
<label class="small-description">AWS Region that hosts Identity directory</label>
3034

31-
<select v-on:click="getRegion()">
32-
<option v-if="!!data.region" :selected="true">{{ data.region }}</option>
33-
</select>
35+
<div style="display: flex; flex-direction: row; gap: 10px">
36+
<div v-on:click="getRegion()" class="icon icon-lg icon-vscode-edit edit-icon"></div>
37+
<div style="width: 100%">{{ data.region ? data.region : 'Not Selected' }}</div>
38+
</div>
3439
</div>
3540

3641
<div class="form-section">
3742
<button v-on:click="signin()" :disabled="!canSubmit">Sign up or Sign in</button>
43+
<div class="small-description error-text">{{ errors.submit }}</div>
3844
</div>
3945
</div>
4046

@@ -85,6 +91,11 @@ export default defineComponent({
8591
// but not care about any current identity center auth connections
8692
// and if they are connected or not.
8793
},
94+
/** If we don't care about the start url already existing locally */
95+
allowExistingStartUrl: {
96+
type: Boolean,
97+
default: false,
98+
},
8899
},
89100
data() {
90101
return {
@@ -94,6 +105,7 @@ export default defineComponent({
94105
},
95106
errors: {
96107
startUrl: '',
108+
submit: '',
97109
},
98110
canSubmit: false,
99111
isConnected: false,
@@ -115,8 +127,16 @@ export default defineComponent({
115127
methods: {
116128
async signin(): Promise<void> {
117129
this.stage = 'WAITING_ON_USER'
118-
await this.state.startIdentityCenterSetup()
119-
await this.update('signIn')
130+
this.errors.submit = await this.state.startIdentityCenterSetup()
131+
132+
if (this.errors.submit) {
133+
// We do not run update() when there is a submission error
134+
// so we do not trigger a full re-render, instead
135+
// only updating this form
136+
this.stage = await this.state.stage()
137+
} else {
138+
await this.update('signIn')
139+
}
120140
},
121141
async signout(): Promise<void> {
122142
await this.state.signout()
@@ -133,13 +153,14 @@ export default defineComponent({
133153
this.data.region = region.id
134154
},
135155
async updateData(key: IdentityCenterKey, value: string) {
156+
this.errors.submit = '' // If previous submission error, we clear it when user starts typing
136157
this.state.setValue(key, value)
137158
138159
if (key === 'startUrl') {
139-
this.errors.startUrl = await this.state.getStartUrlError()
160+
this.errors.startUrl = await this.state.getStartUrlError(this.allowExistingStartUrl)
140161
}
141162
142-
this.canSubmit = await this.state.canSubmit()
163+
this.canSubmit = await this.state.canSubmit(this.allowExistingStartUrl)
143164
},
144165
showView() {
145166
this.state.showView()
@@ -174,7 +195,7 @@ abstract class BaseIdentityCenterState implements AuthStatus {
174195
175196
abstract get id(): AuthFormId
176197
abstract get name(): string
177-
protected abstract _startIdentityCenterSetup(): Promise<void>
198+
protected abstract _startIdentityCenterSetup(): Promise<string>
178199
abstract isAuthConnected(): Promise<boolean>
179200
abstract showView(): Promise<void>
180201
abstract signout(): Promise<void>
@@ -187,9 +208,24 @@ abstract class BaseIdentityCenterState implements AuthStatus {
187208
return this._data[key]
188209
}
189210
190-
async startIdentityCenterSetup(): Promise<void> {
211+
/**
212+
* Runs the Identity Center setup.
213+
*
214+
* @returns An error message if it exist, otherwise empty string if no error.
215+
*/
216+
async startIdentityCenterSetup(): Promise<string> {
191217
this._stage = 'WAITING_ON_USER'
192-
return this._startIdentityCenterSetup()
218+
const error = await this._startIdentityCenterSetup()
219+
220+
// Successful submission, so we can clear
221+
// old data.
222+
if (!error) {
223+
this._data = {
224+
startUrl: '',
225+
region: '',
226+
}
227+
}
228+
return error
193229
}
194230
195231
async stage(): Promise<IdentityCenterStage> {
@@ -202,14 +238,14 @@ abstract class BaseIdentityCenterState implements AuthStatus {
202238
return client.getIdentityCenterRegion()
203239
}
204240
205-
async getStartUrlError() {
206-
const error = await client.getSsoUrlError(this._data.startUrl)
241+
async getStartUrlError(canUrlExist: boolean) {
242+
const error = await client.getSsoUrlError(this._data.startUrl, canUrlExist)
207243
return error ?? ''
208244
}
209245
210-
async canSubmit() {
246+
async canSubmit(canUrlExist: boolean) {
211247
const allFieldsFilled = Object.values(this._data).every(val => !!val)
212-
const hasErrors = await this.getStartUrlError()
248+
const hasErrors = await this.getStartUrlError(canUrlExist)
213249
return allFieldsFilled && !hasErrors
214250
}
215251
@@ -227,7 +263,7 @@ export class CodeWhispererIdentityCenterState extends BaseIdentityCenterState {
227263
return 'CodeWhisperer'
228264
}
229265
230-
protected override async _startIdentityCenterSetup(): Promise<void> {
266+
protected override async _startIdentityCenterSetup(): Promise<string> {
231267
const data = await this.getSubmittableDataOrThrow()
232268
return client.startCWIdentityCenterSetup(data.startUrl, data.region)
233269
}
@@ -268,7 +304,7 @@ export class ExplorerIdentityCenterState extends BaseIdentityCenterState {
268304
return 'START'
269305
}
270306
271-
protected override async _startIdentityCenterSetup(): Promise<void> {
307+
protected override async _startIdentityCenterSetup(): Promise<string> {
272308
const data = await this.getSubmittableDataOrThrow()
273309
return client.createIdentityCenterConnection(data.startUrl, data.region)
274310
}
@@ -291,7 +327,7 @@ export class ExplorerIdentityCenterState extends BaseIdentityCenterState {
291327
@import '../shared.css';
292328
293329
#identity-center-form {
294-
width: 280px;
330+
width: 300px;
295331
height: fit-content;
296332
}
297333
</style>

0 commit comments

Comments
 (0)