Skip to content

Commit ff181f4

Browse files
committed
start modifying auth2 consumers
1 parent 2dabb43 commit ff181f4

File tree

6 files changed

+173
-51
lines changed

6 files changed

+173
-51
lines changed

packages/core/src/amazonqFeatureDev/client/featureDev.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,22 @@ const writeAPIRetryOptions = {
5151

5252
// Create a client for featureDev proxy client based off of aws sdk v2
5353
export async function createFeatureDevProxyClient(options?: Partial<ServiceOptions>): Promise<FeatureDevProxyClient> {
54-
const bearerToken = await AuthUtil.instance.getToken()
54+
const credential = await AuthUtil.instance.getCredential()
55+
// TODO: handle IAM credentials when IAM version of API JSON file is generated
56+
if (credential !== 'string') {
57+
throw new Error('Feature dev does not support IAM credentials')
58+
}
5559
const cwsprConfig = getCodewhispererConfig()
5660
return (await globals.sdkClientBuilder.createAwsService(
5761
Service,
5862
{
5963
apiConfig: apiConfig,
6064
region: cwsprConfig.region,
6165
endpoint: cwsprConfig.endpoint,
62-
token: new Token({ token: bearerToken }),
6366
httpOptions: {
6467
connectTimeout: 10000, // 10 seconds, 3 times P99 API latency
6568
},
69+
token: new Token({ token: credential }),
6670
...options,
6771
} as ServiceOptions,
6872
undefined

packages/core/src/auth/auth2.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import { ToolkitError } from '../shared/errors'
6161
import { useDeviceFlow } from './sso/ssoAccessTokenProvider'
6262
import { getCacheDir, getCacheFileWatcher, getFlareCacheFileName } from './sso/cache'
6363
import { VSCODE_EXTENSION_ID } from '../shared/extensions'
64+
import { IamCredentials } from '@aws/language-server-runtimes-types'
6465

6566
export const notificationTypes = {
6667
updateIamCredential: new RequestType<UpdateCredentialsParams, ResponseMessage, Error>(
@@ -217,15 +218,15 @@ export class LanguageClientAuth {
217218
return { profile, ssoSession }
218219
}
219220

220-
updateBearerToken(request: UpdateCredentialsParams) {
221+
updateBearerToken(request: UpdateCredentialsParams | undefined) {
221222
return this.client.sendRequest(bearerCredentialsUpdateRequestType.method, request)
222223
}
223224

224225
deleteBearerToken() {
225226
return this.client.sendNotification(bearerCredentialsDeleteNotificationType.method)
226227
}
227228

228-
updateIamCredential(request: UpdateCredentialsParams) {
229+
updateIamCredential(request: UpdateCredentialsParams | undefined) {
229230
return this.client.sendRequest(iamCredentialsUpdateRequestType.method, request)
230231
}
231232

@@ -283,6 +284,10 @@ export abstract class BaseLogin {
283284
abstract reauthenticate(): Promise<GetSsoTokenResult | GetIamCredentialResult | undefined>
284285
abstract logout(): void
285286
abstract restore(): void
287+
abstract getCredential(): Promise<{
288+
credential: string | IamCredentials
289+
updateCredentialsParams: UpdateCredentialsParams
290+
}>
286291

287292
get data() {
288293
return this._data
@@ -402,11 +407,11 @@ export class SsoLogin extends BaseLogin {
402407
* Returns both the decrypted access token and the payload to send to the `updateCredentials` LSP API
403408
* with encrypted token
404409
*/
405-
async getToken() {
410+
async getCredential() {
406411
const response = await this._getSsoToken(false)
407412
const accessToken = await this.decrypt(response.ssoToken.accessToken)
408413
return {
409-
token: accessToken,
414+
credential: accessToken,
410415
updateCredentialsParams: response.updateCredentialsParams,
411416
}
412417
}
@@ -548,18 +553,17 @@ export class IamLogin extends BaseLogin {
548553
* Returns both the decrypted IAM credential and the payload to send to the `updateCredentials` LSP API
549554
* with encrypted credential
550555
*/
551-
async getCredentials() {
556+
async getCredential() {
552557
const response = await this._getIamCredential(false)
553-
const accessKey = await this.decrypt(response.credentials.accessKeyId)
554-
const secretKey = await this.decrypt(response.credentials.secretAccessKey)
555-
let sessionToken: string | undefined
556-
if (response.credentials.sessionToken) {
557-
sessionToken = await this.decrypt(response.credentials.sessionToken)
558+
const credentials: IamCredentials = {
559+
accessKeyId: await this.decrypt(response.credentials.accessKeyId),
560+
secretAccessKey: await this.decrypt(response.credentials.secretAccessKey),
561+
sessionToken: response.credentials.sessionToken
562+
? await this.decrypt(response.credentials.sessionToken)
563+
: undefined,
558564
}
559565
return {
560-
accessKey: accessKey,
561-
secretKey: secretKey,
562-
sessionToken: sessionToken,
566+
credential: credentials,
563567
updateCredentialsParams: response.updateCredentialsParams,
564568
}
565569
}

packages/core/src/codewhisperer/client/codewhisperer.ts

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { AWSError, Credentials, Service } from 'aws-sdk'
6+
import { AWSError, HttpRequest, Service } from 'aws-sdk'
77
import globals from '../../shared/extensionGlobals'
88
import * as CodeWhispererClient from './codewhispererclient'
99
import * as CodeWhispererUserClient from './codewhispereruserclient'
@@ -13,8 +13,8 @@ import { hasVendedIamCredentials } from '../../auth/auth'
1313
import { CodeWhispererSettings } from '../util/codewhispererSettings'
1414
import { PromiseResult } from 'aws-sdk/lib/request'
1515
import { AuthUtil } from '../util/authUtil'
16-
import apiConfig = require('./service-2.json')
1716
import userApiConfig = require('./user-service-2.json')
17+
import apiConfig = require('./service-2.json')
1818
import { session } from '../util/codeWhispererSession'
1919
import { getLogger } from '../../shared/logger/logger'
2020
import { getClientId, getOptOutPreference, getOperatingSystem } from '../../shared/telemetry/util'
@@ -83,8 +83,6 @@ export type Imports = CodeWhispererUserClient.Imports
8383

8484
export class DefaultCodeWhispererClient {
8585
private async createSdkClient(): Promise<CodeWhispererClient> {
86-
throw new Error('Do not call this function until IAM is supported by LSP identity server')
87-
8886
const isOptedOut = CodeWhispererSettings.instance.isOptoutEnabled()
8987
const cwsprConfig = getCodewhispererConfig()
9088
return (await globals.sdkClientBuilder.createAwsService(
@@ -127,7 +125,11 @@ export class DefaultCodeWhispererClient {
127125
async createUserSdkClient(maxRetries?: number): Promise<CodeWhispererUserClient> {
128126
const isOptedOut = CodeWhispererSettings.instance.isOptoutEnabled()
129127
session.setFetchCredentialStart()
130-
const bearerToken = await AuthUtil.instance.getToken()
128+
const credential = await AuthUtil.instance.getCredential()
129+
if (typeof credential !== 'string') {
130+
throw new TypeError('Cannot create user SDK client from IAM credentials')
131+
}
132+
131133
session.setSdkApiCallStart()
132134
const cwsprConfig = getCodewhispererConfig()
133135
return (await globals.sdkClientBuilder.createAwsService(
@@ -137,11 +139,10 @@ export class DefaultCodeWhispererClient {
137139
region: cwsprConfig.region,
138140
endpoint: cwsprConfig.endpoint,
139141
maxRetries: maxRetries,
140-
credentials: new Credentials({ accessKeyId: 'xxx', secretAccessKey: 'xxx' }),
141142
onRequestSetup: [
142-
(req) => {
143-
req.on('build', ({ httpRequest }) => {
144-
httpRequest.headers['Authorization'] = `Bearer ${bearerToken}`
143+
(req: any) => {
144+
req.on('build', ({ httpRequest }: { httpRequest: HttpRequest }) => {
145+
httpRequest.headers['Authorization'] = `Bearer ${credential}`
145146
})
146147
if (req.operation === 'generateCompletions') {
147148
req.on('build', () => {
@@ -160,6 +161,88 @@ export class DefaultCodeWhispererClient {
160161
return AuthUtil.instance.isConnected() // TODO: Handle IAM credentials
161162
}
162163

164+
// private async createServiceSdkClient(credential: IamCredentials): Promise<CodeWhispererClient> {
165+
// const isOptedOut = CodeWhispererSettings.instance.isOptoutEnabled()
166+
// const cwsprConfig = getCodewhispererConfig()
167+
// return (await globals.sdkClientBuilder.createAwsService(
168+
// Service,
169+
// {
170+
// apiConfig: apiConfig,
171+
// region: cwsprConfig.region,
172+
// credentials: undefined,
173+
// endpoint: cwsprConfig.endpoint,
174+
// onRequestSetup: [
175+
// (req) => {
176+
// if (req.operation === 'listRecommendations') {
177+
// req.on('build', () => {
178+
// req.httpRequest.headers['x-amzn-codewhisperer-optout'] = `${isOptedOut}`
179+
// })
180+
// }
181+
// // This logic is for backward compatability with legacy SDK v2 behavior for refreshing
182+
// // credentials. Once the Toolkit adds a file watcher for credentials it won't be needed.
183+
184+
// if (hasVendedIamCredentials()) {
185+
// req.on('retry', (resp) => {
186+
// if (
187+
// resp.error?.code === 'AccessDeniedException' &&
188+
// resp.error.message.match(/expired/i)
189+
// ) {
190+
// // AuthUtil.instance.reauthenticate().catch((e) => {
191+
// // getLogger().error('reauthenticate failed: %s', (e as Error).message)
192+
// // })
193+
// resp.error.retryable = true
194+
// }
195+
// })
196+
// }
197+
// },
198+
// ],
199+
// } as ServiceOptions,
200+
// undefined
201+
// )) as CodeWhispererClient
202+
// }
203+
204+
// private async createUserServiceSdkClient(
205+
// credential: string,
206+
// maxRetries?: number
207+
// ): Promise<CodeWhispererUserClient> {
208+
// const isOptedOut = CodeWhispererSettings.instance.isOptoutEnabled()
209+
// session.setFetchCredentialStart()
210+
// session.setSdkApiCallStart()
211+
// const cwsprConfig = getCodewhispererConfig()
212+
// return (await globals.sdkClientBuilder.createAwsService(
213+
// Service,
214+
// {
215+
// apiConfig: userApiConfig,
216+
// region: cwsprConfig.region,
217+
// endpoint: cwsprConfig.endpoint,
218+
// maxRetries: maxRetries,
219+
// onRequestSetup: [
220+
// (req: any) => {
221+
// req.on('build', ({ httpRequest }: { httpRequest: HttpRequest }) => {
222+
// httpRequest.headers['Authorization'] = `Bearer ${credential}`
223+
// })
224+
// if (req.operation === 'generateCompletions') {
225+
// req.on('build', () => {
226+
// req.httpRequest.headers['x-amzn-codewhisperer-optout'] = `${isOptedOut}`
227+
// req.httpRequest.headers['Connection'] = keepAliveHeader
228+
// })
229+
// }
230+
// },
231+
// ],
232+
// } as ServiceOptions,
233+
// undefined
234+
// )) as CodeWhispererUserClient
235+
// }
236+
237+
// async createSdkClient(maxRetries?: number): Promise<CodeWhispererUserClient | CodeWhispererClient> {
238+
// const credential = await AuthUtil.instance.getCredential()
239+
// if (typeof credential === 'string') {
240+
// return this.createUserServiceSdkClient(credential, maxRetries)
241+
// } else {
242+
// return this.createServiceSdkClient(credential)
243+
// }
244+
// }
245+
163246
public async generateRecommendations(
164247
request: GenerateRecommendationsRequest
165248
): Promise<GenerateRecommendationsResponse> {

packages/core/src/codewhisperer/region/regionProfileManager.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import { showConfirmationMessage } from '../../shared/utilities/messages'
1111
import globals from '../../shared/extensionGlobals'
1212
import { once } from '../../shared/utilities/functionUtils'
1313
import CodeWhispererUserClient from '../client/codewhispereruserclient'
14-
import { Credentials, Service } from 'aws-sdk'
14+
import { Credentials, HttpRequest, Service } from 'aws-sdk'
1515
import { ServiceOptions } from '../../shared/awsClientBuilder'
16-
import userApiConfig = require('../client/user-service-2.json')
16+
import tokenApiConfig = require('../client/user-service-2.json')
17+
import iamApiConfig = require('../client/service-2.json')
1718
import { createConstantMap } from '../../shared/utilities/tsUtils'
1819
import { getLogger } from '../../shared/logger/logger'
1920
import { pageableToCollection } from '../../shared/utilities/collectionUtils'
@@ -425,19 +426,31 @@ export class RegionProfileManager {
425426

426427
// Visible for testing only, do not use this directly, please use createQClient(profile)
427428
async _createQClient(region: string, endpoint: string): Promise<CodeWhispererUserClient> {
428-
const token = await this.authProvider.getToken()
429+
const credential = await this.authProvider.getCredential()
430+
const authConfig: ServiceOptions =
431+
typeof credential === 'string'
432+
? {
433+
onRequestSetup: [
434+
(req: any) => {
435+
req.on('build', ({ httpRequest }: { httpRequest: HttpRequest }) => {
436+
httpRequest.headers['Authorization'] = `Bearer ${credential}`
437+
})
438+
},
439+
],
440+
}
441+
: {
442+
credentials: new Credentials({
443+
accessKeyId: credential.accessKeyId,
444+
secretAccessKey: credential.secretAccessKey,
445+
sessionToken: credential.sessionToken,
446+
}),
447+
}
448+
const apiConfig = typeof credential === 'string' ? tokenApiConfig : iamApiConfig
429449
const serviceOption: ServiceOptions = {
430-
apiConfig: userApiConfig,
450+
apiConfig: apiConfig,
431451
region: region,
432452
endpoint: endpoint,
433-
credentials: new Credentials({ accessKeyId: 'xxx', secretAccessKey: 'xxx' }),
434-
onRequestSetup: [
435-
(req) => {
436-
req.on('build', ({ httpRequest }) => {
437-
httpRequest.headers['Authorization'] = `Bearer ${token}`
438-
})
439-
},
440-
],
453+
...authConfig,
441454
} as ServiceOptions
442455

443456
const c = (await globals.sdkClientBuilder.createAwsService(

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

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
GetSsoTokenResult,
4545
GetIamCredentialResult,
4646
SsoTokenSourceKind,
47+
IamCredentials,
4748
} from '@aws/language-server-runtimes/server-interface'
4849

4950
const localize = nls.loadMessageBundle()
@@ -59,7 +60,8 @@ export interface IAuthProvider {
5960
isBuilderIdConnection(): boolean
6061
isIdcConnection(): boolean
6162
isSsoSession(): boolean
62-
getToken(): Promise<string>
63+
isIamSession(): boolean
64+
getCredential(): Promise<string | IamCredentials>
6365
readonly profileName: string
6466
readonly connection?: { startUrl?: string; region?: string; accessKey?: string; secretKey?: string }
6567
}
@@ -199,11 +201,11 @@ export class AuthUtil implements IAuthProvider {
199201
return response
200202
}
201203

202-
async getToken() {
203-
if (this.isSsoSession()) {
204-
return (await (this.session as SsoLogin).getToken()).token
204+
async getCredential() {
205+
if (this.session) {
206+
return (await this.session.getCredential()).credential
205207
} else {
206-
throw new ToolkitError('Cannot get token for non-SSO session.')
208+
throw new ToolkitError('Cannot get credential without logging in.')
207209
}
208210
}
209211

@@ -336,11 +338,12 @@ export class AuthUtil implements IAuthProvider {
336338

337339
private async stateChangeHandler(e: AuthStateEvent) {
338340
if (e.state === 'refreshed') {
339-
const params = this.isSsoSession()
340-
? (await (this.session as SsoLogin).getToken()).updateCredentialsParams
341-
: undefined
342-
await this.lspAuth.updateBearerToken(params!)
343-
return
341+
const params = this.session ? (await this.session.getCredential()).updateCredentialsParams : undefined
342+
if (this.isSsoSession()) {
343+
await this.lspAuth.updateBearerToken(params)
344+
} else if (this.isIamSession()) {
345+
await this.lspAuth.updateIamCredential(params)
346+
}
344347
} else {
345348
this.logger.info(`codewhisperer: connection changed to ${e.state}`)
346349
await this.refreshState(e.state)
@@ -356,8 +359,12 @@ export class AuthUtil implements IAuthProvider {
356359
}
357360
}
358361
if (state === 'connected') {
359-
const bearerTokenParams = (await (this.session as SsoLogin).getToken()).updateCredentialsParams
360-
await this.lspAuth.updateBearerToken(bearerTokenParams)
362+
const params = this.session ? (await this.session.getCredential()).updateCredentialsParams : undefined
363+
if (this.isSsoSession()) {
364+
await this.lspAuth.updateBearerToken(params)
365+
} else if (this.isIamSession()) {
366+
await this.lspAuth.updateIamCredential(params)
367+
}
361368

362369
if (this.isIdcConnection()) {
363370
await this.regionProfileManager.restoreProfileSelection()
@@ -400,7 +407,7 @@ export class AuthUtil implements IAuthProvider {
400407
credentialStartUrl: AuthUtil.instance.connection?.startUrl,
401408
awsRegion: AuthUtil.instance.connection?.region,
402409
}
403-
} else if (!AuthUtil.instance.isSsoSession) {
410+
} else if (this.isIamSession()) {
404411
return {
405412
credentialSourceId: 'sharedCredentials',
406413
}

0 commit comments

Comments
 (0)