Skip to content

Commit a9eb3b5

Browse files
sso: use new 'confirm match' device code flow (#3829)
Problem: Before this commit, the BuilderID/Identity Center sso flow would require the user to copy a code and paste it in the browser window to confirm their device. Solution: Now, with new changes from the identity team, if we pass the device code in the URL a different flow will be shown in the browser which asks them to instead confirm the code instead of pasting it. - This commit has users go through the new flow - Copying code functionality is removed - The information messages are updated to mention the requirement to confirm the code in the browser Signed-off-by: nkomonen <[email protected]>
1 parent eaa142b commit a9eb3b5

File tree

4 files changed

+36
-41
lines changed

4 files changed

+36
-41
lines changed

src/auth/sso/model.ts

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -86,43 +86,51 @@ export function truncateStartUrl(startUrl: string) {
8686
return startUrl.match(/https?:\/\/(.*)\.awsapps\.com\/start/)?.[1] ?? startUrl
8787
}
8888

89-
export async function openSsoPortalLink(
90-
startUrl: string,
91-
authorization: { readonly verificationUri: string; readonly userCode: string }
92-
): Promise<boolean> {
93-
async function copyCodeAndOpenLink() {
94-
await vscode.env.clipboard.writeText(authorization.userCode).then(undefined, err => {
95-
getLogger().warn(`auth: failed to copy user code "${authorization.userCode}" to clipboard: %s`, err)
96-
})
97-
98-
const didOpen = await vscode.env.openExternal(vscode.Uri.parse(authorization.verificationUri))
99-
if (!didOpen) {
89+
type Authorization = { readonly verificationUri: string; readonly userCode: string }
90+
91+
export const proceedToBrowser = localize('AWS.auth.loginWithBrowser.proceedToBrowser', 'Proceed To Browser')
92+
93+
export async function openSsoPortalLink(startUrl: string, authorization: Authorization): Promise<boolean> {
94+
/**
95+
* Depending on the verification URL + parameters used, the way the sso login flow works changes.
96+
* Previously, users were asked to copy and paste a device code in to the browser page.
97+
*
98+
* Now, with the URL this function creates, the user will instead be asked to confirm the device code
99+
* in the browser.
100+
*/
101+
function makeConfirmCodeUrl(authorization: Authorization): vscode.Uri {
102+
return vscode.Uri.parse(`${authorization.verificationUri}?user_code=${authorization.userCode}`)
103+
}
104+
105+
async function openSsoUrl() {
106+
const ssoLoginUrl = makeConfirmCodeUrl(authorization)
107+
const didOpenUrl = await vscode.env.openExternal(ssoLoginUrl)
108+
109+
if (!didOpenUrl) {
100110
throw new ToolkitError(`User clicked 'Copy' or 'Cancel' during the Trusted Domain popup`, {
101111
code: trustedDomainCancellation,
102112
name: trustedDomainCancellation,
103113
})
104114
}
105-
return didOpen
115+
return didOpenUrl
106116
}
107117

108118
async function showLoginNotification() {
109119
const name = startUrl === builderIdStartUrl ? localizedText.builderId() : localizedText.iamIdentityCenterFull()
110-
const title = localize('AWS.auth.loginWithBrowser.messageTitle', 'Copy Code for {0}', name)
120+
const title = localize('AWS.auth.loginWithBrowser.messageTitle', 'Confirm Code for {0}', name)
111121
const detail = localize(
112122
'AWS.auth.loginWithBrowser.messageDetail',
113-
'To proceed, open the login page and provide this code to confirm the access request: {0}',
123+
'Confirm this code in the browser: {0}',
114124
authorization.userCode
115125
)
116-
const copyCode = localize('AWS.auth.loginWithBrowser.copyCodeAction', 'Copy Code and Proceed')
117-
const options = { modal: true, detail } as vscode.MessageOptions
118126

119127
while (true) {
120128
// TODO: add the 'Help' item back once we have a suitable URL
121129
// const resp = await vscode.window.showInformationMessage(title, options, copyCode, localizedText.help)
122-
const resp = await vscode.window.showInformationMessage(title, options, copyCode)
130+
const resp = await vscode.window.showInformationMessage(title, { modal: true, detail }, proceedToBrowser)
123131
switch (resp) {
124-
case copyCode:
125-
return copyCodeAndOpenLink()
132+
case proceedToBrowser:
133+
return openSsoUrl()
126134
case localizedText.help:
127135
await tryOpenHelpUrl(ssoAuthHelpUrl)
128136
continue

src/auth/sso/ssoAccessTokenProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ async function pollForTokenWithProgress<T>(
234234
{
235235
title: localize(
236236
'AWS.auth.loginWithBrowser.messageDetail',
237-
'Login page opened in browser. When prompted, provide this code: {0}',
237+
'Confirm code "{0}" in the login page opened in your web browser.',
238238
authorization.userCode
239239
),
240240
cancellable: true,

src/test/credentials/sso/model.test.ts

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
*/
55

66
import assert from 'assert'
7-
import * as vscode from 'vscode'
8-
import { openSsoPortalLink } from '../../../auth/sso/model'
7+
import { openSsoPortalLink, proceedToBrowser } from '../../../auth/sso/model'
98
import { assertTelemetry } from '../../testUtil'
10-
import { CancellationError } from '../../../shared/utilities/timeoutUtils'
119
import { getOpenExternalStub } from '../../globalSetup.test'
1210
import { getTestWindow } from '../../shared/vscode/window'
1311

@@ -19,16 +17,16 @@ describe('openSsoPortalLink', function () {
1917
const userCode = 'user-code'
2018
const verificationUri = 'https://example.com/'
2119
async function runFlow(...actions: ('open' | 'help' | 'cancel')[]) {
22-
const copyCode = /copy code/i
20+
const confirmCode = /Confirm Code for/
2321
const waitForMessage = async (): Promise<void> =>
2422
getTestWindow()
25-
.waitForMessage(copyCode)
23+
.waitForMessage(confirmCode)
2624
.then(m => {
2725
assert.ok(m.detail?.includes(userCode), 'Expected message to show the user verification code')
2826

2927
const action = actions.shift()
3028
if (action === 'open') {
31-
m.selectItem(copyCode)
29+
m.selectItem(proceedToBrowser)
3230
} else if (action === 'help') {
3331
m.selectItem(/help/i)
3432
return waitForMessage()
@@ -40,24 +38,13 @@ describe('openSsoPortalLink', function () {
4038
await Promise.all([waitForMessage(), openSsoPortalLink('', { verificationUri, userCode })])
4139
}
4240

43-
it('copies to the clipboard and opens a link when selecting the open URL option', async function () {
41+
it('opens a "confirm code" link when selecting the open URL option', async function () {
4442
await runFlow('open')
4543
assert.ok(getOpenExternalStub().calledOnce)
46-
assert.strictEqual(getOpenExternalStub().args[0].toString(), verificationUri)
47-
assert.strictEqual(await vscode.env.clipboard.readText(), userCode)
44+
assert.strictEqual(getOpenExternalStub().args[0].toString(), `${verificationUri}?user_code%3D${userCode}`)
4845
assertTelemetry('aws_loginWithBrowser', { result: 'Succeeded' })
4946
})
5047

51-
it('does not copy code to clipboard or opens links if the user cancels', async function () {
52-
// This isn't mocked/stubbed so it'll clear the test runners clipboard
53-
await vscode.env.clipboard.writeText('')
54-
const result = await runFlow('cancel').catch(e => e)
55-
assert.ok(getOpenExternalStub().notCalled)
56-
assert.ok(result instanceof CancellationError)
57-
assert.strictEqual(await vscode.env.clipboard.readText(), '')
58-
assertTelemetry('aws_loginWithBrowser', { result: 'Cancelled', reason: 'user' })
59-
})
60-
6148
it('continues to show the notification if the user selects help', async function () {
6249
this.skip()
6350
await runFlow('help', 'open')

src/test/credentials/sso/ssoAccessTokenProvider.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { getCache } from '../../../auth/sso/cache'
1212

1313
import { instance, mock, when, anything, reset } from '../../utilities/mockito'
1414
import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../../shared/filesystemUtilities'
15-
import { ClientRegistration, SsoToken } from '../../../auth/sso/model'
15+
import { ClientRegistration, SsoToken, proceedToBrowser } from '../../../auth/sso/model'
1616
import { OidcClient } from '../../../auth/sso/clients'
1717
import { CancellationError } from '../../../shared/utilities/timeoutUtils'
1818
import {
@@ -189,7 +189,7 @@ describe('SsoAccessTokenProvider', function () {
189189
describe('createToken', function () {
190190
beforeEach(function () {
191191
getTestWindow().onDidShowMessage(m => {
192-
if (m.items[0]?.title.match(/copy code/i)) {
192+
if (m.items[0]?.title.match(proceedToBrowser)) {
193193
m.items[0].select()
194194
}
195195
})

0 commit comments

Comments
 (0)