Skip to content

Commit 81aec3c

Browse files
committed
feat(amazonq): Migrate existing SSO connections to the LSP identity server
1 parent 153f8b3 commit 81aec3c

File tree

3 files changed

+93
-3
lines changed

3 files changed

+93
-3
lines changed

packages/amazonq/src/lsp/client.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export async function startLanguageServer(
109109
await validateNodeExe(executable, resourcePaths.lsp, argv, logger)
110110

111111
// Options to control the language client
112+
const clientName = 'AmazonQ-For-VSCode'
112113
const clientOptions: LanguageClientOptions = {
113114
// Register the server for json documents
114115
documentSelector,
@@ -133,7 +134,7 @@ export async function startLanguageServer(
133134
name: env.appName,
134135
version: version,
135136
extension: {
136-
name: 'AmazonQ-For-VSCode',
137+
name: clientName,
137138
version: '0.0.1',
138139
},
139140
clientId: crypto.randomUUID(),
@@ -177,6 +178,12 @@ export async function startLanguageServer(
177178
await client.onReady()
178179
AuthUtil.create(new auth2.LanguageClientAuth(client, clientId, encryptionKey))
179180

181+
try {
182+
await AuthUtil.instance.migrateSsoConnectionToLsp(clientName)
183+
} catch (e) {
184+
client.error(`Error while migration SSO connection to Amazon Q LSP: ${e}`)
185+
}
186+
180187
// Request handler for when the server wants to know about the clients auth connnection. Must be registered before the initial auth init call
181188
client.onRequest<ConnectionMetadata, Error>(auth2.notificationTypes.getConnectionMetadata.method, () => {
182189
return {

packages/core/src/auth/sso/cache.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export function getTokenCache(directory = getCacheDir()): KeyedCache<SsoAccess>
126126
return mapCache(cache, read, write)
127127
}
128128

129-
function getTokenCacheFile(ssoCacheDir: string, key: string): string {
129+
export function getTokenCacheFile(ssoCacheDir: string, key: string): string {
130130
const encoded = encodeURI(key)
131131
// Per the spec: 'SSO Login Token Flow' the access token must be
132132
// cached as the SHA1 hash of the bytes of the UTF-8 encoded
@@ -145,7 +145,7 @@ function getTokenCacheFile(ssoCacheDir: string, key: string): string {
145145
return path.join(ssoCacheDir, `${hashedKey}.json`)
146146
}
147147

148-
function getRegistrationCacheFile(ssoCacheDir: string, key: RegistrationKey): string {
148+
export function getRegistrationCacheFile(ssoCacheDir: string, key: RegistrationKey): string {
149149
const hash = (startUrl: string, scopes: string[]) => {
150150
const shasum = crypto.createHash('sha256')
151151
shasum.update(startUrl)

packages/core/src/codewhisperer/util/authUtil.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import * as vscode from 'vscode'
77
import * as localizedText from '../../shared/localizedText'
88
import * as nls from 'vscode-nls'
9+
import * as fs from 'fs'
10+
import * as path from 'path'
11+
import * as crypto from 'crypto'
912
import { ToolkitError } from '../../shared/errors'
1013
import { AmazonQPromptSettings } from '../../shared/settings'
1114
import {
@@ -16,6 +19,9 @@ import {
1619
TelemetryMetadata,
1720
scopesSsoAccountAccess,
1821
hasScopes,
22+
SsoProfile,
23+
StoredProfile,
24+
hasExactScopes,
1925
} from '../../auth/connection'
2026
import { getLogger } from '../../shared/logger/logger'
2127
import { Commands } from '../../shared/vscode/commands2'
@@ -30,6 +36,8 @@ import { builderIdStartUrl, internalStartUrl } from '../../auth/sso/constants'
3036
import { VSCODE_EXTENSION_ID } from '../../shared/extensions'
3137
import { RegionProfileManager } from '../region/regionProfileManager'
3238
import { AuthFormId } from '../../login/webview/vue/types'
39+
import { getEnvironmentSpecificMemento } from '../../shared/utilities/mementos'
40+
import { getCacheDir, getRegistrationCacheFile, getTokenCacheFile } from '../../auth/sso/cache'
3341

3442
const localize = nls.loadMessageBundle()
3543

@@ -333,4 +341,79 @@ export class AuthUtil implements IAuthProvider {
333341

334342
return authIds
335343
}
344+
345+
/**
346+
* Migrates existing SSO connections to the LSP identity server by updating the cache files
347+
*
348+
* @param clientName - The client name to use for the new registration cache file
349+
* @returns A Promise that resolves when the migration is complete
350+
* @throws Error if file operations fail during migration
351+
*/
352+
async migrateSsoConnectionToLsp(clientName: string) {
353+
const memento = getEnvironmentSpecificMemento()
354+
const key = 'auth.profiles'
355+
const profiles: { readonly [id: string]: StoredProfile } | undefined = memento.get(key)
356+
357+
let toImport: SsoProfile | undefined
358+
let profileId: string | undefined
359+
360+
getLogger().info(`codewhisperer: checking for old SSO connections`)
361+
if (profiles) {
362+
for (const [id, p] of Object.entries(profiles)) {
363+
if (p.type === 'sso' && hasExactScopes(p.scopes ?? [], amazonQScopes)) {
364+
toImport = p
365+
profileId = id
366+
if (p.metadata.connectionState === 'valid') {
367+
break
368+
}
369+
}
370+
}
371+
372+
if (toImport && profileId) {
373+
getLogger().info(`codewhisperer: migrating SSO connection to LSP identity server...`)
374+
375+
const registrationKey = {
376+
startUrl: toImport.startUrl,
377+
region: toImport.ssoRegion,
378+
scopes: amazonQScopes,
379+
}
380+
381+
await this.session.updateProfile(registrationKey)
382+
383+
const cacheDir = getCacheDir()
384+
385+
const hash = (str: string) => {
386+
const hasher = crypto.createHash('sha1')
387+
return hasher.update(str).digest('hex')
388+
}
389+
const filePath = (str: string) => {
390+
return path.join(cacheDir, hash(str) + '.json')
391+
}
392+
393+
const fromRegistrationFile = getRegistrationCacheFile(cacheDir, registrationKey)
394+
const toRegistrationFile = filePath(
395+
JSON.stringify({
396+
region: toImport.ssoRegion,
397+
startUrl: toImport.startUrl,
398+
tool: clientName,
399+
})
400+
)
401+
402+
const fromTokenFile = getTokenCacheFile(cacheDir, profileId)
403+
const toTokenFile = filePath(this.profileName)
404+
405+
try {
406+
fs.renameSync(fromRegistrationFile, toRegistrationFile)
407+
fs.renameSync(fromTokenFile, toTokenFile)
408+
getLogger().debug('Successfully renamed registration and token files')
409+
} catch (err) {
410+
getLogger().error(`Failed to rename files during migration: ${err}`)
411+
throw err
412+
}
413+
414+
await memento.update(key, undefined)
415+
getLogger().info(`codewhisperer: successfully migrated SSO connection to LSP identity server`)
416+
}
417+
}
418+
}
336419
}

0 commit comments

Comments
 (0)