Skip to content

Commit 5381baf

Browse files
authored
feat(ui): show login prompt for split sessions (#5182)
**Separate sessions commit** - If user is logged out of toolkit due to extension session splitting, display a prompt that opens the sign in page when clicked. - Dismissing or clicking the button dismisses it permanently. - The prompt will continue to show across restarts until it is dismissed, the button is pressed, or the user signs into toolkit. - The prompt will display only once, regardless of how many connections are logged out (forgotten). - The sign in button will go to the login page with the proper scopes, i.e.g if codecatalyst was signed out, go to code catalyst login. Otherwise go to explorer only login.
1 parent 600ad26 commit 5381baf

File tree

4 files changed

+95
-5
lines changed

4 files changed

+95
-5
lines changed

packages/core/src/auth/auth.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import {
5959
scopesSsoAccountAccess,
6060
AwsConnection,
6161
} from './connection'
62-
import { isSageMaker, isCloud9 } from '../shared/extensionUtilities'
62+
import { isSageMaker, isCloud9, isAmazonQ } from '../shared/extensionUtilities'
6363
import { telemetry } from '../shared/telemetry/telemetry'
6464
import { randomUUID } from '../common/crypto'
6565

@@ -300,6 +300,11 @@ export class Auth implements AuthService, ConnectionManager {
300300
metadata: { connectionState: 'unauthenticated' },
301301
})
302302

303+
// Remove the split session logout prompt, if it exists.
304+
if (!isAmazonQ()) {
305+
await globals.context.globalState.update(SessionSeparationPrompt.instance.dismissKey, true)
306+
}
307+
303308
try {
304309
;(await tokenProvider.getToken()) ?? (await tokenProvider.createToken())
305310
const storedProfile = await this.store.addProfile(id, profile)
@@ -1000,6 +1005,7 @@ export class Auth implements AuthService, ConnectionManager {
10001005
delete this._declaredConnections[conn.startUrl]
10011006
}
10021007
}
1008+
10031009
/**
10041010
* Returns true if credentials are provided by the environment (ex. via ~/.aws/)
10051011
*
@@ -1012,3 +1018,70 @@ export function hasVendedIamCredentials(isC9?: boolean, isSM?: boolean) {
10121018
isSM ??= isSageMaker()
10131019
return isSM || isC9
10141020
}
1021+
1022+
type LoginCommand = 'aws.toolkit.auth.manageConnections' | 'aws.codecatalyst.manageConnections'
1023+
/**
1024+
* Temporary class that handles notifiting users who were logged out as part of
1025+
* splitting auth sessions between extensions.
1026+
*
1027+
* TODO: Remove after some time.
1028+
*/
1029+
export class SessionSeparationPrompt {
1030+
public readonly dismissKey = 'aws.toolkit.separationPromptDismissed'
1031+
private readonly loginCmdKey = 'aws.toolkit.separationPromptCommand'
1032+
1033+
// Local variable handles per session displays, e.g. we forgot a CodeCatalyst connection AND
1034+
// an Explorer only connection. We only want to display once in this case.
1035+
// However, we don't want to set this at the global state level until a user interacts with the
1036+
// notification in case they miss it the first time.
1037+
#separationPromptDisplayed = false
1038+
1039+
/**
1040+
* Open a prompt for that last used command name (or do nothing if no command name has ever been passed),
1041+
* which is useful to redisplay the prompt after reloads in case a user misses it.
1042+
*/
1043+
public async showAnyPreviousPrompt() {
1044+
const cmd = globals.context.globalState.get<string>(this.loginCmdKey)
1045+
return cmd ? await this.showForCommand(cmd as LoginCommand) : undefined
1046+
}
1047+
1048+
/**
1049+
* Displays a sign in prompt to the user if they have been logged out of the Toolkit as part of
1050+
* separating auth sessions between extensions. It will executed the passed command for sign in,
1051+
* (e.g. codecatalyst sign in vs explorer)
1052+
*/
1053+
public async showForCommand(cmd: LoginCommand) {
1054+
if (this.#separationPromptDisplayed || globals.context.globalState.get<boolean>(this.dismissKey)) {
1055+
return
1056+
}
1057+
1058+
await globals.context.globalState.update(this.loginCmdKey, cmd)
1059+
1060+
await telemetry.toolkit_showNotification.run(async () => {
1061+
telemetry.record({ id: 'sessionSeparation' })
1062+
this.#separationPromptDisplayed = true
1063+
void vscode.window
1064+
.showWarningMessage(
1065+
'Amazon Q and AWS Toolkit no longer share connections. Please sign in again to use AWS Toolkit.',
1066+
'Sign In'
1067+
)
1068+
.then(async resp => {
1069+
await telemetry.toolkit_invokeAction.run(async () => {
1070+
if (resp === 'Sign In') {
1071+
telemetry.record({ action: 'signIn' })
1072+
await vscode.commands.executeCommand(cmd)
1073+
} else {
1074+
telemetry.record({ action: 'dismiss' })
1075+
}
1076+
1077+
await globals.context.globalState.update(this.dismissKey, true)
1078+
})
1079+
})
1080+
})
1081+
}
1082+
1083+
static #instance: SessionSeparationPrompt
1084+
public static get instance() {
1085+
return (this.#instance ??= new SessionSeparationPrompt())
1086+
}
1087+
}

packages/core/src/codecatalyst/activation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { DevEnvActivityStarter } from './devEnv'
2626
import { learnMoreCommand, onboardCommand, reauth } from './explorer'
2727
import { isInDevEnv } from '../shared/vscode/env'
2828
import { hasExactScopes } from '../auth/connection'
29+
import { SessionSeparationPrompt } from '../auth/auth'
2930

3031
const localize = nls.loadMessageBundle()
3132

@@ -49,6 +50,7 @@ export async function activate(ctx: ExtContext): Promise<void> {
4950
// TODO: Remove after some time?
5051
if (authProvider.isConnected() && !hasExactScopes(authProvider.activeConnection!, defaultScopes)) {
5152
await authProvider.secondaryAuth.forgetConnection()
53+
await SessionSeparationPrompt.instance.showForCommand('aws.codecatalyst.manageConnections')
5254
}
5355

5456
ctx.extensionContext.subscriptions.push(

packages/core/src/extension.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import globals from './shared/extensionGlobals'
4848
import { Experiments, Settings, showSettingsFailedMsg } from './shared/settings'
4949
import { isReleaseVersion } from './shared/vscode/env'
5050
import { telemetry } from './shared/telemetry/telemetry'
51-
import { Auth } from './auth/auth'
51+
import { Auth, SessionSeparationPrompt } from './auth/auth'
5252
import { registerSubmitFeedback } from './feedback/vue/submitFeedback'
5353
import { activateCommon, deactivateCommon, emitUserState } from './extensionCommon'
5454
import { learnMoreAmazonQCommand, qExtensionPageCommand, dismissQTree } from './amazonq/explorer/amazonQChildrenNodes'
@@ -129,9 +129,14 @@ export async function activate(context: vscode.ExtensionContext) {
129129
for (const conn of await Auth.instance.listConnections()) {
130130
if (isSsoConnection(conn) && hasScopes(conn, codeWhispererCoreScopes)) {
131131
await Auth.instance.forgetConnection(conn)
132+
await SessionSeparationPrompt.instance.showForCommand('aws.toolkit.auth.manageConnections')
132133
}
133134
}
134135

136+
// Display last prompt if connections were forgotten in prior sessions
137+
// but the user did not interact or sign in again. Useful in case the user misses it the first time.
138+
await SessionSeparationPrompt.instance.showAnyPreviousPrompt()
139+
135140
await activateCloudFormationTemplateRegistry(context)
136141

137142
// MUST restore CW/Q auth so that we can see if this user is already a Q user.

packages/core/src/test/techdebt.test.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import * as env from '../shared/vscode/env'
1010
// Checks project config and dependencies, to remind us to remove old things
1111
// when possible.
1212
describe('tech debt', function () {
13+
function fixByDate(date: string, msg: string) {
14+
const now = Date.now()
15+
const cutoffDate = Date.parse(date)
16+
assert.ok(now <= cutoffDate, msg)
17+
}
18+
1319
it('vscode minimum version', async function () {
1420
const minVscode = env.getMinVscodeVersion()
1521

@@ -35,8 +41,12 @@ describe('tech debt', function () {
3541
})
3642

3743
it('remove missing Amazon Q scopes edge case handling', async function () {
38-
const now = Date.now()
39-
const cutoffDate = Date.parse('2024-06-30')
40-
assert.ok(now <= cutoffDate, 'Remove the edge case code from the commit that this test is a part of.')
44+
fixByDate('2024-06-30', 'Remove the edge case code from the commit that this test is a part of.')
45+
})
46+
47+
it('remove separate sessions login edge cases', async function () {
48+
// src/auth/auth.ts:SessionSeparationPrompt
49+
// forgetConnection() function and calls
50+
fixByDate('2024-07-30', 'Remove the edge case code from the commit that this test is a part of.')
4151
})
4252
})

0 commit comments

Comments
 (0)