diff --git a/packages/core/src/auth/sso/model.ts b/packages/core/src/auth/sso/model.ts index 6cc462d8a57..b2e20b14624 100644 --- a/packages/core/src/auth/sso/model.ts +++ b/packages/core/src/auth/sso/model.ts @@ -14,6 +14,7 @@ import { getLogger } from '../../shared/logger/logger' import { CancellationError } from '../../shared/utilities/timeoutUtils' import { ssoAuthHelpUrl } from '../../shared/constants' import { openUrl } from '../../shared/utilities/vsCodeUtils' +import { copyToClipboard } from '../../shared/utilities/messages' import { ToolkitError } from '../../shared/errors' import { builderIdStartUrl } from './constants' @@ -100,6 +101,7 @@ export function truncateStartUrl(startUrl: string) { type Authorization = { readonly verificationUri: string; readonly userCode: string } export const proceedToBrowser = localize('AWS.auth.loginWithBrowser.proceedToBrowser', 'Proceed To Browser') +export const copyUrl = localize('AWS.auth.loginWithBrowser.copyLoginUrl', 'Copy authentication URL') export async function openSsoPortalLink(startUrl: string, authorization: Authorization): Promise { /** @@ -122,13 +124,18 @@ export async function openSsoPortalLink(startUrl: string, authorization: Authori authorization.userCode ) + const options = [proceedToBrowser, copyUrl] + while (true) { // TODO: add the 'Help' item back once we have a suitable URL // const resp = await vscode.window.showInformationMessage(title, options, copyCode, localizedText.help) - const resp = await vscode.window.showInformationMessage(title, { modal: true, detail }, proceedToBrowser) + const resp = await vscode.window.showInformationMessage(title, { modal: true, detail }, ...options) switch (resp) { case proceedToBrowser: return openSsoUrl(makeConfirmCodeUrl(authorization)) + case copyUrl: + await copyToClipboard(makeConfirmCodeUrl(authorization).toString(true)) + return true case localizedText.help: await tryOpenHelpUrl(ssoAuthHelpUrl) continue diff --git a/packages/core/src/auth/sso/ssoAccessTokenProvider.ts b/packages/core/src/auth/sso/ssoAccessTokenProvider.ts index e753fb2ef90..e225f970cfa 100644 --- a/packages/core/src/auth/sso/ssoAccessTokenProvider.ts +++ b/packages/core/src/auth/sso/ssoAccessTokenProvider.ts @@ -295,9 +295,15 @@ export abstract class SsoAccessTokenProvider { * Device code flow is neccessary when: * 1. We are in a workspace connected through ssh (codecatalyst, etc) * 2. We are connected to a remote backend through the web browser (code server, openshift dev spaces) + * 3. User has explicitly requested device code flow via the login page toggle * - * Since we are unable to serve the final authorization page + * Since we are unable to serve the final authorization page in cases 1 and 2, + * or the user may not have federation access in case 3 */ + // Check for user preference first, then fall back to the current logic + if (globals.globalState.get('aws.forceDeviceCodeFlow')) { + return true + } return getExtRuntimeContext().extensionHost === 'remote' } ) { @@ -458,7 +464,8 @@ export class DeviceFlowAuthorization extends SsoAccessTokenProvider { clientId: registration.clientId, clientSecret: registration.clientSecret, }) - + // Reset forceDeviceCodeFlow if toggled + await globals.globalState.update('aws.forceDeviceCodeFlow', undefined) const openBrowserAndWaitUntilComplete = async () => { if (!(await openSsoPortalLink(this.profile.startUrl, authorization))) { throw new CancellationError('user') diff --git a/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts b/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts index a83e99d04b7..e8bc78f24d8 100644 --- a/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts +++ b/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts @@ -78,12 +78,24 @@ export class AmazonQLoginWebview extends CommonAuthWebview { }) } - async startEnterpriseSetup(startUrl: string, region: string): Promise { - getLogger().debug(`called startEnterpriseSetup() with startUrl: '${startUrl}', region: '${region}'`) + async startEnterpriseSetup( + startUrl: string, + region: string, + app: string, + useDeviceCodeFlow?: boolean + ): Promise { + getLogger().debug( + `called startEnterpriseSetup() with startUrl: '${startUrl}', region: '${region}', useDeviceCodeFlow: ${useDeviceCodeFlow}` + ) await globals.globalState.update('recentSso', { startUrl: startUrl, region: region, }) + // Store the device code flow preference if it's set + if (useDeviceCodeFlow) { + await globals.globalState.update('aws.forceDeviceCodeFlow', true) + } + return await this.ssoSetup('startCodeWhispererEnterpriseSetup', async () => { this.storeMetricMetadata({ credentialStartUrl: startUrl, diff --git a/packages/core/src/login/webview/vue/backend.ts b/packages/core/src/login/webview/vue/backend.ts index c8f1f38d4d7..483a70cc53d 100644 --- a/packages/core/src/login/webview/vue/backend.ts +++ b/packages/core/src/login/webview/vue/backend.ts @@ -165,7 +165,12 @@ export abstract class CommonAuthWebview extends VueWebview { abstract startBuilderIdSetup(app: string): Promise - abstract startEnterpriseSetup(startUrl: string, region: string, app: string): Promise + abstract startEnterpriseSetup( + startUrl: string, + region: string, + app: string, + useDeviceCodeFlow?: boolean + ): Promise async getAuthenticatedCredentialsError(data: StaticProfile): Promise { return Auth.instance.authenticateData(data) diff --git a/packages/core/src/login/webview/vue/login.vue b/packages/core/src/login/webview/vue/login.vue index ddcd1d91c28..ecc326cd576 100644 --- a/packages/core/src/login/webview/vue/login.vue +++ b/packages/core/src/login/webview/vue/login.vue @@ -208,6 +208,19 @@ {{ `${region.name} (${region.id})` }} +
+
+ +
+