Skip to content

Commit 5a71c09

Browse files
committed
Move IAM profile options into typed object
1 parent 8342872 commit 5a71c09

File tree

7 files changed

+99
-118
lines changed

7 files changed

+99
-118
lines changed

packages/amazonq/test/unit/codewhisperer/util/authUtil.test.ts

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { createTestAuthUtil, TestFolder } from 'aws-core-vscode/test'
1111
import { constants, cache } from 'aws-core-vscode/auth'
1212
import { auth2 } from 'aws-core-vscode/auth'
1313
import { mementoUtils, fs } from 'aws-core-vscode/shared'
14+
import { GetIamCredentialResult } from '@aws/language-server-runtimes/protocol'
1415

1516
describe('AuthUtil', async function () {
1617
let auth: any
@@ -431,7 +432,11 @@ describe('AuthUtil', async function () {
431432

432433
sinon.stub(auth2, 'IamLogin').returns(mockIamLogin as any)
433434

434-
const response = await auth.loginIam('accessKey', 'secretKey', 'sessionToken')
435+
const response = await auth.loginIam({
436+
accessKey: 'accessKey',
437+
secretKey: 'secretKey',
438+
sessionToken: 'sessionToken',
439+
})
435440

436441
assert.ok(mockIamLogin.login.calledOnce)
437442
assert.ok(
@@ -445,13 +450,15 @@ describe('AuthUtil', async function () {
445450
})
446451

447452
it('creates IAM session with role ARN', async function () {
448-
const mockResponse = {
449-
id: 'test-credential-id',
450-
credentials: {
451-
accessKeyId: 'encrypted-access-key',
452-
secretAccessKey: 'encrypted-secret-key',
453-
sessionToken: 'encrypted-session-token',
454-
roleArn: 'arn:aws:iam::123456789012:role/TestRole',
453+
const mockResponse: GetIamCredentialResult = {
454+
credential: {
455+
id: 'test-credential-id',
456+
kinds: [],
457+
credentials: {
458+
accessKeyId: 'encrypted-access-key',
459+
secretAccessKey: 'encrypted-secret-key',
460+
sessionToken: 'encrypted-session-token',
461+
},
455462
},
456463
updateCredentialsParams: {
457464
data: 'credential-data',
@@ -465,22 +472,17 @@ describe('AuthUtil', async function () {
465472

466473
sinon.stub(auth2, 'IamLogin').returns(mockIamLoginArn as any)
467474

468-
const response = await auth.loginIam(
469-
'accessKey',
470-
'secretKey',
471-
'sessionToken',
472-
'arn:aws:iam::123456789012:role/TestRole'
473-
)
475+
const opts: auth2.IamProfileOptions = {
476+
accessKey: 'myAccessKey',
477+
secretKey: 'mySecretKey',
478+
sessionToken: 'mySessionToken',
479+
roleArn: 'arn:aws:iam::123456789012:role/MyTestRole',
480+
}
481+
482+
const response = await auth.loginIam(opts)
474483

475484
assert.ok(mockIamLoginArn.login.calledOnce)
476-
assert.ok(
477-
mockIamLoginArn.login.calledWith({
478-
accessKey: 'accessKey',
479-
secretKey: 'secretKey',
480-
sessionToken: 'sessionToken',
481-
roleArn: 'arn:aws:iam::123456789012:role/TestRole',
482-
})
483-
)
485+
assert.ok(mockIamLoginArn.login.calledWith(opts))
484486
assert.strictEqual(response, mockResponse)
485487
})
486488
})

packages/core/src/auth/auth2.ts

Lines changed: 58 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,22 @@ export type Login = SsoLogin | IamLogin
9595

9696
export type TokenSource = IamIdentityCenterSsoTokenSource | AwsBuilderIdSsoTokenSource
9797

98+
export type IamProfileOptions = {
99+
accessKey?: string
100+
secretKey?: string
101+
sessionToken?: string
102+
roleArn?: string
103+
sourceProfile?: string
104+
}
105+
106+
const IamProfileOptionsDefaults = {
107+
accessKey: '',
108+
secretKey: '',
109+
sessionToken: '',
110+
roleArn: '',
111+
sourceProfile: '',
112+
} satisfies IamProfileOptions
113+
98114
/**
99115
* Handles auth requests to the Identity Server in the Amazon Q LSP.
100116
*/
@@ -185,58 +201,32 @@ export class LanguageClientAuth {
185201
} satisfies UpdateProfileParams)
186202
}
187203

188-
updateIamProfile(
189-
profileName: string,
190-
accessKey: string,
191-
secretKey: string,
192-
sessionToken?: string,
193-
roleArn?: string,
194-
sourceProfile?: string
195-
): Promise<UpdateProfileResult> {
196-
// Add credentials and delete SSO settings from profile
197-
let profile: Profile
198-
if (roleArn && sourceProfile) {
199-
profile = {
200-
kinds: [ProfileKind.IamSourceProfileProfile],
201-
name: profileName,
202-
settings: {
203-
sso_session: '',
204-
aws_access_key_id: '',
205-
aws_secret_access_key: '',
206-
aws_session_token: '',
207-
role_arn: roleArn,
208-
source_profile: sourceProfile,
209-
},
210-
}
211-
} else if (accessKey && secretKey) {
212-
profile = {
213-
kinds: [ProfileKind.IamCredentialsProfile],
214-
name: profileName,
215-
settings: {
216-
sso_session: '',
217-
aws_access_key_id: accessKey,
218-
aws_secret_access_key: secretKey,
219-
aws_session_token: sessionToken,
220-
role_arn: '',
221-
source_profile: '',
222-
},
223-
}
204+
updateIamProfile(profileName: string, opts: IamProfileOptions): Promise<UpdateProfileResult> {
205+
// Substitute missing fields for defaults
206+
const fields = { ...IamProfileOptionsDefaults, ...opts }
207+
// Get the profile kind matching the provided fields
208+
let kind: ProfileKind
209+
if (fields.roleArn && fields.sourceProfile) {
210+
kind = ProfileKind.IamSourceProfileProfile
211+
} else if (fields.accessKey && fields.secretKey) {
212+
kind = ProfileKind.IamCredentialsProfile
224213
} else {
225-
profile = {
226-
kinds: [ProfileKind.Unknown],
214+
kind = ProfileKind.Unknown
215+
}
216+
217+
return this.client.sendRequest(updateProfileRequestType.method, {
218+
profile: {
219+
kinds: [kind],
227220
name: profileName,
228221
settings: {
229-
aws_access_key_id: '',
230-
aws_secret_access_key: '',
231-
aws_session_token: '',
232-
role_arn: '',
233-
source_profile: '',
222+
aws_access_key_id: fields.accessKey,
223+
aws_secret_access_key: fields.secretKey,
224+
aws_session_token: fields.sessionToken,
225+
role_arn: fields.roleArn,
226+
source_profile: fields.sourceProfile,
234227
},
235-
}
236-
}
237-
return this.client.sendRequest(updateProfileRequestType.method, {
238-
profile: profile,
239-
} satisfies UpdateProfileParams)
228+
},
229+
})
240230
}
241231

242232
listProfiles() {
@@ -550,7 +540,7 @@ export class IamLogin extends BaseLogin {
550540
lspAuth.registerGetMfaCodeHandler((params: GetMfaCodeParams) => this.getMfaCodeHandler(params))
551541
}
552542

553-
async login(opts: { accessKey: string; secretKey: string; sessionToken?: string; roleArn?: string }) {
543+
async login(opts: IamProfileOptions) {
554544
await this.updateProfile(opts)
555545
return this._getIamCredential(true)
556546
}
@@ -566,34 +556,33 @@ export class IamLogin extends BaseLogin {
566556
if (this.iamCredentialId) {
567557
await this.lspAuth.invalidateStsCredential(this.iamCredentialId)
568558
}
569-
await this.lspAuth.updateIamProfile(this.profileName, '', '', '', '', '')
570-
await this.lspAuth.updateIamProfile(this.profileName + '-source', '', '', '', '', '')
559+
await this.lspAuth.updateIamProfile(this.profileName, {})
560+
await this.lspAuth.updateIamProfile(this.profileName + '-source', {})
571561
this.updateConnectionState('notConnected')
572562
this._data = undefined
573563
// TODO: DeleteProfile api in Identity Service (this doesn't exist yet)
574564
}
575565

576-
async updateProfile(opts: { accessKey: string; secretKey: string; sessionToken?: string; roleArn?: string }) {
566+
async updateProfile(opts: IamProfileOptions) {
577567
if (opts.roleArn) {
568+
// Create the source and target profiles
578569
const sourceProfile = this.profileName + '-source'
579-
await this.lspAuth.updateIamProfile(
580-
sourceProfile,
581-
opts.accessKey,
582-
opts.secretKey,
583-
opts.sessionToken,
584-
'',
585-
''
586-
)
587-
await this.lspAuth.updateIamProfile(this.profileName, '', '', '', opts.roleArn, sourceProfile)
570+
await this.lspAuth.updateIamProfile(sourceProfile, {
571+
accessKey: opts.accessKey,
572+
secretKey: opts.secretKey,
573+
sessionToken: opts.sessionToken,
574+
})
575+
await this.lspAuth.updateIamProfile(this.profileName, {
576+
roleArn: opts.roleArn,
577+
sourceProfile: sourceProfile,
578+
})
588579
} else {
589-
await this.lspAuth.updateIamProfile(
590-
this.profileName,
591-
opts.accessKey,
592-
opts.secretKey,
593-
opts.sessionToken,
594-
'',
595-
''
596-
)
580+
// Create the target profile
581+
await this.lspAuth.updateIamProfile(this.profileName, {
582+
accessKey: opts.accessKey,
583+
secretKey: opts.secretKey,
584+
sessionToken: opts.sessionToken,
585+
})
597586
}
598587
}
599588

packages/core/src/auth/credentials/utils.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,8 @@ const errorMessageUserCancelled = localize('AWS.error.mfa.userCancelled', 'User
105105
/**
106106
* @description Prompts user for MFA serial number
107107
*
108-
* Entered token is passed to the callback.
109-
* If user cancels out, the callback is passed an error with a fixed message string.
110-
*
108+
* @param defaultSerial Default value for the serial number input
111109
* @param profileName Name of Credentials profile we are asking an MFA Token for
112-
* @param callback tokens/errors are passed through here
113110
*/
114111
export async function getMfaSerialFromUser(defaultSerial: string, profileName: string): Promise<string> {
115112
const inputBox = createInputBox({
@@ -133,12 +130,8 @@ export async function getMfaSerialFromUser(defaultSerial: string, profileName: s
133130
/**
134131
* @description Prompts user for MFA token
135132
*
136-
* Entered token is passed to the callback.
137-
* If user cancels out, the callback is passed an error with a fixed message string.
138-
*
139133
* @param mfaSerial Serial arn of MFA device
140134
* @param profileName Name of Credentials profile we are asking an MFA Token for
141-
* @param callback tokens/errors are passed through here
142135
*/
143136
export async function getMfaTokenFromUser(mfaSerial: string, profileName: string): Promise<string> {
144137
const inputBox = createInputBox({

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export interface SsoCache {
3636
}
3737

3838
const defaultCacheDir = () => path.join(fs.getUserHomeDir(), '.aws/sso/cache')
39-
const defaultStsCacheDir = () => path.join(fs.getUserHomeDir(), '.aws/cli/cache')
39+
const defaultStsCacheDir = () => path.join(fs.getUserHomeDir(), '.aws/flare/cache')
4040
export const getCacheDir = () => DevSettings.instance.get('ssoCacheDirectory', defaultCacheDir())
4141
export const getStsCacheDir = () => DevSettings.instance.get('stsCacheDirectory', defaultStsCacheDir())
4242

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

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
IamLogin,
4141
AuthState,
4242
LoginTypes,
43+
IamProfileOptions,
4344
} from '../../auth/auth2'
4445
import { builderIdStartUrl, internalStartUrl } from '../../auth/sso/constants'
4546
import { VSCODE_EXTENSION_ID } from '../../shared/extensions'
@@ -193,22 +194,12 @@ export class AuthUtil implements IAuthProvider {
193194
}
194195

195196
// Log in using IAM or STS credentials
196-
async loginIam(
197-
accessKey: string,
198-
secretKey: string,
199-
sessionToken?: string,
200-
roleArn?: string
201-
): Promise<GetIamCredentialResult | undefined> {
197+
async loginIam(opts: IamProfileOptions): Promise<GetIamCredentialResult | undefined> {
202198
// Create IAM login session
203199
if (!this.isIamSession()) {
204200
this.session = new IamLogin(this.profileName, this.lspAuth, this.eventEmitter)
205201
}
206-
const response = await (this.session as IamLogin).login({
207-
accessKey: accessKey,
208-
secretKey: secretKey,
209-
sessionToken: sessionToken,
210-
roleArn: roleArn,
211-
})
202+
const response = await (this.session as IamLogin).login(opts)
212203
await showAmazonQWalkthroughOnce()
213204
return response
214205
}

packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ export class AmazonQLoginWebview extends CommonAuthWebview {
206206
await globals.globalState.update('recentRoleArn', { roleArn: roleArn })
207207
const runAuth = async (): Promise<AuthError | undefined> => {
208208
try {
209-
await AuthUtil.instance.loginIam(accessKey, secretKey, sessionToken, roleArn)
209+
await AuthUtil.instance.loginIam({ accessKey, secretKey, sessionToken, roleArn })
210210
} catch (e) {
211211
getLogger().error('Failed submitting credentials %O', e)
212212
const message = e instanceof Error ? e.message : (e as string)

packages/core/src/test/credentials/auth2.test.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,11 @@ describe('LanguageClientAuth', () => {
104104
})
105105

106106
it('sends correct IAM profile update parameters', async () => {
107-
await auth.updateIamProfile(profileName, 'accessKey', 'secretKey', 'sessionToken')
107+
await auth.updateIamProfile(profileName, {
108+
accessKey: 'myAccessKey',
109+
secretKey: 'mySecretKey',
110+
sessionToken: 'mySessionToken',
111+
})
108112

109113
sinon.assert.calledOnce(client.sendRequest)
110114
const requestParams = client.sendRequest.firstCall.args[1]
@@ -113,9 +117,11 @@ describe('LanguageClientAuth', () => {
113117
kinds: [ProfileKind.IamCredentialsProfile],
114118
})
115119
sinon.assert.match(requestParams.profile.settings, {
116-
aws_access_key_id: 'accessKey',
117-
aws_secret_access_key: 'secretKey',
118-
aws_session_token: 'sessionToken',
120+
aws_access_key_id: 'myAccessKey',
121+
aws_secret_access_key: 'mySecretKey',
122+
aws_session_token: 'mySessionToken',
123+
role_arn: '',
124+
source_profile: '',
119125
})
120126
})
121127
})
@@ -674,7 +680,7 @@ describe('IamLogin', () => {
674680
const response = await iamLogin.login(loginOpts)
675681

676682
sinon.assert.calledOnce(lspAuth.updateIamProfile)
677-
sinon.assert.calledWith(lspAuth.updateIamProfile, profileName, loginOpts.accessKey, loginOpts.secretKey)
683+
sinon.assert.calledWith(lspAuth.updateIamProfile, profileName, loginOpts)
678684
sinon.assert.calledOnce(lspAuth.getIamCredential)
679685
sinon.assert.match(iamLogin.getConnectionState(), 'connected')
680686
sinon.assert.match(response.credential.id, 'test-credential-id')

0 commit comments

Comments
 (0)