From 828068f866112906b2716011d52cd884c3b599ee Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Thu, 10 Jul 2025 16:55:16 -0400 Subject: [PATCH 01/37] feat(identity): add support for IAM profile kinds --- .../src/credentials/credentialsProvider.ts | 1 + .../profiles/profileService.test.ts | 61 ++++++----- .../profiles/profileService.ts | 102 ++++++------------ .../profiles/sharedConfigProfileStore.test.ts | 33 +++--- .../profiles/sharedConfigProfileStore.ts | 38 +++++-- 5 files changed, 117 insertions(+), 118 deletions(-) diff --git a/core/aws-lsp-core/src/credentials/credentialsProvider.ts b/core/aws-lsp-core/src/credentials/credentialsProvider.ts index a56be8e0ad..bea8a5cfcd 100644 --- a/core/aws-lsp-core/src/credentials/credentialsProvider.ts +++ b/core/aws-lsp-core/src/credentials/credentialsProvider.ts @@ -4,6 +4,7 @@ export interface IamCredentials { accessKeyId: string secretAccessKey: string sessionToken?: string + expiration?: Date } export interface BearerToken { diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts index 216f3010af..bf928a1119 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts @@ -54,7 +54,7 @@ describe('ProfileService', async () => { } profile4 = { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'profile4', settings: { aws_access_key_id: 'access-key', @@ -259,9 +259,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Sso-session name required on profile.') }) - it('updateProfile throws on missing access key for IamCredentialsProfile', async () => { + it('updateProfile throws on missing access key for IAM user profile', async () => { const profile = { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'profile-name', settings: { aws_secret_access_key: 'secret-key', @@ -271,9 +271,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Access key required on profile.') }) - it('updateProfile throws on missing secret key for IamCredentialsProfile', async () => { + it('updateProfile throws on missing secret key for IAM user profile', async () => { const profile = { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'profile-name', settings: { aws_access_key_id: 'access-key', @@ -283,9 +283,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Secret key required on profile.') }) - it('updateProfile throws on missing role ARN for IamSourceProfileProfile', async () => { + it('updateProfile throws on missing role ARN for role source profile', async () => { const profile = { - kinds: [ProfileKind.IamSourceProfileProfile], + kinds: [ProfileKind.RoleSourceProfile], name: 'profile-name', settings: { source_profile: 'source', @@ -295,9 +295,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Role ARN required on profile.') }) - it('updateProfile throws on missing source profile for IamSourceProfileProfile', async () => { + it('updateProfile throws on missing source profile for role source profile', async () => { const profile = { - kinds: [ProfileKind.IamSourceProfileProfile], + kinds: [ProfileKind.RoleSourceProfile], name: 'profile-name', settings: { role_arn: 'role-arn', @@ -307,9 +307,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Source profile required on profile.') }) - it('updateProfile throws on missing role ARN for IamCredentialSourceProfile', async () => { + it('updateProfile throws on missing role ARN for role instance profile', async () => { const profile = { - kinds: [ProfileKind.IamCredentialSourceProfile], + kinds: [ProfileKind.RoleInstanceProfile], name: 'profile-name', settings: { credential_source: 'Ec2InstanceMetadata', @@ -320,9 +320,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Role ARN required on profile.') }) - it('updateProfile throws on missing credential source for IamCredentialSourceProfile', async () => { + it('updateProfile throws on missing credential source for role instance profile', async () => { const profile = { - kinds: [ProfileKind.IamCredentialSourceProfile], + kinds: [ProfileKind.RoleInstanceProfile], name: 'profile-name', settings: { role_arn: 'role-arn', @@ -338,9 +338,22 @@ describe('ProfileService', async () => { ) }) + it('updateProfile throws on missing region for role instance profile', async () => { + const profile = { + kinds: [ProfileKind.RoleInstanceProfile], + name: 'profile-name', + settings: { + role_arn: 'role-arn', + credential_source: 'Ec2InstanceMetadata', + }, + } + + await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Region required on profile.') + }) + it('updateProfile throws on missing credential process for process profile', async () => { const profile = { - kinds: [ProfileKind.IamCredentialProcessProfile], + kinds: [ProfileKind.ProcessProfile], name: 'profile-name', settings: {}, } @@ -573,7 +586,7 @@ describe('profileService.DuckTypers', () => { } }) - it('profileDuckTypers.IamCredentialsProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.IamUserProfile.eval returns true on valid profiles', () => { const profiles = [ { aws_access_key_id: 'access-key', @@ -587,12 +600,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamCredentialsProfile.eval(profile) + const actual = profileDuckTypers.IamUserProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.IamCredentialsProfile.eval returns false on invalid profiles', () => { + it('profileDuckTypers.IamUserProfile.eval returns false on invalid profiles', () => { const profiles = [ { sso_session: 'my-sso-session', @@ -604,12 +617,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamCredentialsProfile.eval(profile as object) + const actual = profileDuckTypers.IamUserProfile.eval(profile as object) expect(actual).to.be.false } }) - it('profileDuckTypers.IamSourceProfileProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.RoleSourceProfile.eval returns true on valid profiles', () => { const profiles = [ { role_arn: 'role-arn', @@ -624,12 +637,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamSourceProfileProfile.eval(profile) + const actual = profileDuckTypers.RoleSourceProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.IamCredentialSourceProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.RoleInstanceProfile.eval returns true on valid profiles', () => { const profiles = [ { role_arn: 'role-arn', @@ -645,12 +658,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamCredentialSourceProfile.eval(profile) + const actual = profileDuckTypers.RoleInstanceProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.IamCredentialProcessProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.ProcessProfile.eval returns true on valid profiles', () => { const profiles = [ { credential_process: 'credential-process', @@ -664,7 +677,7 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamCredentialProcessProfile.eval(profile) + const actual = profileDuckTypers.ProcessProfile.eval(profile) expect(actual).to.be.true } }) diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts index a37e27e30e..68ed446f04 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts @@ -39,9 +39,6 @@ export const ProfileFields = { credential_source: 'credential_source', source_profile: 'source_profile', mfa_serial: 'mfa_serial', - external_id: 'external_id', - credential_cache: 'credential_cache', - credential_cache_location: 'credential_cache_location', } as const export const SsoSessionFields = { @@ -50,70 +47,30 @@ export const SsoSessionFields = { sso_start_url: 'sso_start_url', } as const -export const profileTypes = { - SsoTokenProfile: { - kind: ProfileKind.SsoTokenProfile, - required: [ProfileFields.sso_session], - optional: [ProfileFields.region], - disallowed: [ProfileFields.sso_account_id, ProfileFields.sso_role_name], - }, - IamCredentialsProfile: { - kind: ProfileKind.IamCredentialsProfile, - required: [ProfileFields.aws_access_key_id, ProfileFields.aws_secret_access_key], - optional: [ProfileFields.aws_session_token], - disallowed: [], - }, - IamSourceProfileProfile: { - kind: ProfileKind.IamSourceProfileProfile, - required: [ProfileFields.role_arn, ProfileFields.source_profile], - optional: [ - ProfileFields.external_id, - ProfileFields.role_session_name, - ProfileFields.region, - ProfileFields.mfa_serial, - ProfileFields.credential_cache, - ProfileFields.credential_cache_location, - ], - disallowed: [ProfileFields.credential_source], - }, - IamCredentialSourceProfile: { - kind: ProfileKind.IamCredentialSourceProfile, - required: [ProfileFields.role_arn, ProfileFields.credential_source], - optional: [ - ProfileFields.external_id, - ProfileFields.role_session_name, - ProfileFields.region, - ProfileFields.credential_cache, - ProfileFields.credential_cache_location, - ], - disallowed: [ProfileFields.source_profile], - }, - IamCredentialProcessProfile: { - kind: ProfileKind.IamCredentialProcessProfile, - required: [ProfileFields.credential_process], - optional: [], - disallowed: [], - }, -} as const - -export const profileDuckTypers = Object.fromEntries( - Object.entries(profileTypes).map(([key, def]) => [ - key, - (() => { - const typer = new DuckTyper() - for (const field of def.required) { - typer.requireProperty(field) - } - for (const field of def.optional) { - typer.optionalProperty(field) - } - for (const field of def.disallowed) { - typer.disallowProperty(field) - } - return typer - })(), - ]) -) +export const profileDuckTypers = { + SsoTokenProfile: new DuckTyper() + .requireProperty(ProfileFields.sso_session) + .disallowProperty(ProfileFields.sso_account_id) + .disallowProperty(ProfileFields.sso_role_name), + IamUserProfile: new DuckTyper() + .requireProperty(ProfileFields.aws_access_key_id) + .requireProperty(ProfileFields.aws_secret_access_key) + .optionalProperty(ProfileFields.aws_session_token), + RoleSourceProfile: new DuckTyper() + .requireProperty(ProfileFields.role_arn) + .requireProperty(ProfileFields.source_profile) + .optionalProperty(ProfileFields.role_session_name) + .optionalProperty(ProfileFields.mfa_serial) + .disallowProperty(ProfileFields.credential_source), + RoleInstanceProfile: new DuckTyper() + .requireProperty(ProfileFields.role_arn) + .requireProperty(ProfileFields.credential_source) + .requireProperty(ProfileFields.region) + .optionalProperty(ProfileFields.role_session_name) + .disallowProperty(ProfileFields.source_profile), + ProcessProfile: new DuckTyper().requireProperty(ProfileFields.credential_process), + Unknown: new DuckTyper(), +} export const ssoSessionDuckTyper = new DuckTyper() .requireProperty(SsoSessionFields.sso_start_url) @@ -163,6 +120,7 @@ export class ProfileService { this.throwOnInvalidProfile(!params.profile, 'Profile required.') const profile = params.profile! + // Removing this check for profile deletion this.throwOnInvalidProfile( !profile.kinds.some(kind => Object.values(ProfileKind).includes(kind)), 'Profile must be non-legacy sso-session or iam-credentials type.' @@ -182,7 +140,6 @@ export class ProfileService { throw new AwsError('Cannot create profile.', AwsErrorCodes.E_CANNOT_CREATE_PROFILE) } - // TODO: can this be refactored and simplified using the existing DuckTypers? // Validate SSO profile if (profile.kinds.includes(ProfileKind.SsoTokenProfile)) { this.throwOnInvalidProfile(!profileSettings.sso_session, 'Sso-session name required on profile.') @@ -219,22 +176,23 @@ export class ProfileService { } // Validate IAM profiles - if (profile.kinds.includes(ProfileKind.IamCredentialsProfile)) { + if (profile.kinds.includes(ProfileKind.IamUserProfile)) { this.throwOnInvalidProfile(!profileSettings.aws_access_key_id, 'Access key required on profile.') this.throwOnInvalidProfile(!profileSettings.aws_secret_access_key, 'Secret key required on profile.') } - if (profile.kinds.includes(ProfileKind.IamCredentialSourceProfile)) { + if (profile.kinds.includes(ProfileKind.RoleInstanceProfile)) { this.throwOnInvalidProfile(!profileSettings.role_arn, 'Role ARN required on profile.') + this.throwOnInvalidProfile(!profileSettings.region, 'Region required on profile.') this.throwOnInvalidProfile(!profileSettings.credential_source, 'Credential source required on profile.') } - if (profile.kinds.includes(ProfileKind.IamSourceProfileProfile)) { + if (profile.kinds.includes(ProfileKind.RoleSourceProfile)) { this.throwOnInvalidProfile(!profileSettings.role_arn, 'Role ARN required on profile.') this.throwOnInvalidProfile(!profileSettings.source_profile, 'Source profile required on profile.') } - if (profile.kinds.includes(ProfileKind.IamCredentialProcessProfile)) { + if (profile.kinds.includes(ProfileKind.ProcessProfile)) { this.throwOnInvalidProfile(!profileSettings.credential_process, 'Credential process required on profile.') } diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts index fa0e85bb72..003b306faa 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts @@ -89,11 +89,12 @@ describe('SharedConfigProfileStore', async () => { expect(actual).to.deep.equal({ profiles: [ { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', + aws_session_token: undefined, }, }, { @@ -180,11 +181,12 @@ describe('SharedConfigProfileStore', async () => { expect(after).to.deep.equal({ profiles: [ { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', + aws_session_token: undefined, }, }, { @@ -276,11 +278,12 @@ describe('SharedConfigProfileStore', async () => { expect(after).to.deep.equal({ profiles: [ { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', + aws_session_token: undefined, }, }, { @@ -316,7 +319,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'iam-user.profile', settings: { aws_access_key_id: 'new-access-key', @@ -325,7 +328,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamSourceProfileProfile], + kinds: [ProfileKind.RoleSourceProfile], name: 'role-source.profile', settings: { role_arn: 'new-role-arn', @@ -333,15 +336,16 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamCredentialSourceProfile], + kinds: [ProfileKind.RoleInstanceProfile], name: 'role-instance.profile', settings: { role_arn: 'new-role-arn', credential_source: 'new-source', + region: 'new-region', }, }, { - kinds: [ProfileKind.IamCredentialProcessProfile], + kinds: [ProfileKind.ProcessProfile], name: 'process.profile', settings: { credential_process: 'new-credential-process', @@ -392,15 +396,16 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', + aws_session_token: undefined, }, }, { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'iam-user.profile', settings: { aws_access_key_id: 'new-access-key', @@ -409,26 +414,30 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamCredentialProcessProfile], + kinds: [ProfileKind.ProcessProfile], name: 'process.profile', settings: { credential_process: 'new-credential-process', }, }, { - kinds: [ProfileKind.IamCredentialSourceProfile], + kinds: [ProfileKind.RoleInstanceProfile], name: 'role-instance.profile', settings: { role_arn: 'new-role-arn', credential_source: 'new-source', + region: 'new-region', + role_session_name: undefined, }, }, { - kinds: [ProfileKind.IamSourceProfileProfile], + kinds: [ProfileKind.RoleSourceProfile], name: 'role-source.profile', settings: { role_arn: 'new-role-arn', source_profile: 'new-source-profile', + mfa_serial: undefined, + role_session_name: undefined, }, }, { diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts index 0b612a46a0..fb4f167d69 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts @@ -53,16 +53,34 @@ export class SharedConfigProfileStore implements ProfileStore { settings: {}, } // Add the kinds and settings for each matched profile type - for (const [profileType, fields] of Object.entries(profileTypes)) { - if (profileDuckTypers[profileType].eval(settings)) { - profile.kinds.push(fields.kind) - const relevantFields = [...fields.required, ...fields.optional] - for (const field of relevantFields) { - if (settings[field] !== undefined) { - profile.settings![field] = settings[field] - } - } - } + if (profileDuckTypers.SsoTokenProfile.eval(settings)) { + profile.kinds.push(ProfileKind.SsoTokenProfile) + profile.settings!.region = settings.region + profile.settings!.sso_session = settings.sso_session + } + if (profileDuckTypers.ProcessProfile.eval(settings)) { + profile.kinds.push(ProfileKind.ProcessProfile) + profile.settings!.credential_process = settings.credential_process + } + if (profileDuckTypers.RoleSourceProfile.eval(settings)) { + profile.kinds.push(ProfileKind.RoleSourceProfile) + profile.settings!.role_arn = settings.role_arn + profile.settings!.source_profile = settings.source_profile + profile.settings!.mfa_serial = settings.mfa_serial + profile.settings!.role_session_name = settings.role_session_name + } + if (profileDuckTypers.RoleInstanceProfile.eval(settings)) { + profile.kinds.push(ProfileKind.RoleInstanceProfile) + profile.settings!.role_arn = settings.role_arn + profile.settings!.region = settings.region + profile.settings!.credential_source = settings.credential_source + profile.settings!.role_session_name = settings.role_session_name + } + if (profileDuckTypers.IamUserProfile.eval(settings)) { + profile.kinds.push(ProfileKind.IamUserProfile) + profile.settings!.aws_access_key_id = settings.aws_access_key_id + profile.settings!.aws_secret_access_key = settings.aws_secret_access_key + profile.settings!.aws_session_token = settings.aws_session_token } // If the profile does not match any profile type, mark it as an unknown profile if (profile.kinds.length === 0) { From 311b455e685b1a6a0aaec5caf71de27641d11789 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Thu, 10 Jul 2025 17:31:10 -0400 Subject: [PATCH 02/37] feat(identity): add support for retrieving IAM user credentials --- .../src/language-server/identityServer.ts | 1 - .../language-server/identityService.test.ts | 42 ++++----- .../src/language-server/identityService.ts | 88 ++++++++++++++++--- 3 files changed, 94 insertions(+), 37 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/identityServer.ts b/server/aws-lsp-identity/src/language-server/identityServer.ts index 31a01577de..9fcbebfcff 100644 --- a/server/aws-lsp-identity/src/language-server/identityServer.ts +++ b/server/aws-lsp-identity/src/language-server/identityServer.ts @@ -11,7 +11,6 @@ import { PartialInitializeResult, ShowMessageRequestParams, GetIamCredentialParams, - GetMfaCodeParams, } from '@aws/language-server-runtimes/server-interface' import { SharedConfigProfileStore } from './profiles/sharedConfigProfileStore' import { IdentityService } from './identityService' diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index a007aead95..17fdcb2a38 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -43,7 +43,7 @@ describe('IdentityService', () => { }, }, { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'my-iam-profile', settings: { aws_access_key_id: 'my-access-key', @@ -51,7 +51,7 @@ describe('IdentityService', () => { }, }, { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'my-sts-profile', settings: { aws_access_key_id: 'my-access-key', @@ -60,7 +60,7 @@ describe('IdentityService', () => { }, }, { - kinds: [ProfileKind.IamSourceProfileProfile], + kinds: [ProfileKind.RoleSourceProfile], name: 'my-role-profile', settings: { role_arn: 'my-role-arn', @@ -68,7 +68,7 @@ describe('IdentityService', () => { }, }, { - kinds: [ProfileKind.IamSourceProfileProfile], + kinds: [ProfileKind.RoleSourceProfile], name: 'my-mfa-profile', settings: { role_arn: 'my-role-arn', @@ -77,7 +77,7 @@ describe('IdentityService', () => { }, }, { - kinds: [ProfileKind.IamCredentialProcessProfile], + kinds: [ProfileKind.ProcessProfile], name: 'my-process-profile', settings: { credential_process: 'my-process', @@ -153,23 +153,8 @@ describe('IdentityService', () => { } ) - stub(STSClient.prototype, 'send').resolves({ - Credentials: { - AccessKeyId: 'role-access-key', - SecretAccessKey: 'role-secret-key', - SessionToken: 'role-session-token', - Expiration: new Date('2024-09-25T18:09:20.455Z'), - }, - AssumedRoleUser: { - Arn: 'role-arn', - AssumedRoleId: 'role-id', - }, - Arn: 'role-arn', - }) - - stub(IAMClient.prototype, 'send').resolves({ - EvaluationResults: [], - }) + const validatePermissionsStub = stub(sut as any, 'validatePermissions') + validatePermissionsStub.resolves(true) }) afterEach(() => { @@ -329,12 +314,19 @@ describe('IdentityService', () => { }) describe('getIamCredential', () => { + it('Can login with access key and secret key.', async () => { + const actual = await sut.getIamCredential({ profileName: 'my-iam-profile' }, CancellationToken.None) + + expect(actual.credentials.accessKeyId).to.equal('my-access-key') + expect(actual.credentials.secretAccessKey).to.equal('my-secret-key') + }) + it('Can login with access key, secret key, and session token.', async () => { const actual = await sut.getIamCredential({ profileName: 'my-sts-profile' }, CancellationToken.None) - expect(actual.credential.credentials.accessKeyId).to.equal('my-access-key') - expect(actual.credential.credentials.secretAccessKey).to.equal('my-secret-key') - expect(actual.credential.credentials.sessionToken).to.equal('my-session-token') + expect(actual.credentials.accessKeyId).to.equal('my-access-key') + expect(actual.credentials.secretAccessKey).to.equal('my-secret-key') + expect(actual.credentials.sessionToken).to.equal('my-session-token') }) }) diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 22eecb3ca5..ae82a74073 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -9,12 +9,14 @@ import { getIamCredentialOptionsDefaults, GetSsoTokenParams, GetSsoTokenResult, + IamCredentials, IamIdentityCenterSsoTokenSource, InvalidateSsoTokenParams, InvalidateSsoTokenResult, MetricEvent, SsoSession, SsoTokenSourceKind, + ProfileKind, } from '@aws/language-server-runtimes/server-interface' import { normalizeSettingList, ProfileStore } from './profiles/profileService' @@ -30,10 +32,11 @@ import { } from '../sso/utils' import { IamHandlers, simulatePermissions } from '../iam/utils' import { AwsError, Observability } from '@aws/lsp-core' +import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts' import { __ServiceException } from '@aws-sdk/client-sso-oidc/dist-types/models/SSOOIDCServiceException' import { deviceCodeFlow } from '../sso/deviceCode/deviceCodeFlow' import { SSOToken } from '@smithy/shared-ini-file-loader' -import { IamProvider } from '../iam/iamProvider' +import { IAMClient, SimulatePrincipalPolicyCommand } from '@aws-sdk/client-iam' type SsoTokenSource = IamIdentityCenterSsoTokenSource | AwsBuilderIdSsoTokenSource type AuthFlows = Record Promise> @@ -157,7 +160,9 @@ export class IdentityService { const options = { ...getIamCredentialOptionsDefaults, ...params.options } token.onCancellationRequested(_ => { - emitMetric('Cancelled', null) + if (options.generateOnInvalidStsCredential) { + emitMetric('Cancelled', null) + } }) // Get the profile with provided name @@ -168,20 +173,31 @@ export class IdentityService { throw new AwsError('Profile not found.', AwsErrorCodes.E_PROFILE_NOT_FOUND) } - const credentials = await this.iamProvider.getCredential(profile, options.callStsOnInvalidIamCredential) - - // Validate permissions - if (options.permissionSet.length > 0) { - const response = await simulatePermissions(credentials, options.permissionSet, profile.settings?.region) - if (!response?.EvaluationResults?.every(result => result.EvalDecision === 'allowed')) { - throw new AwsError(`Credentials have insufficient permissions.`, AwsErrorCodes.E_INVALID_PROFILE) + let credentials: IamCredentials + // Get the credentials directly from the profile + if (profile.kinds.includes(ProfileKind.IamUserProfile)) { + credentials = { + accessKeyId: profile.settings!.aws_access_key_id!, + secretAccessKey: profile.settings!.aws_secret_access_key!, + sessionToken: profile.settings!.aws_session_token!, } + } else { + throw new AwsError('Credentials could not be found for profile', AwsErrorCodes.E_INVALID_PROFILE) } - emitMetric('Succeeded') + // Validate permissions on user or assumed role + const hasPermissions = await this.validatePermissions(credentials, profile.settings?.region) + if (!hasPermissions) { + throw new AwsError( + `User or assumed role has insufficient permissions.`, + AwsErrorCodes.E_INVALID_PROFILE + ) + } + emitMetric('Succeeded') return { - credential: { id: params.profileName, kinds: profile.kinds, credentials: credentials }, + id: profile.name, + credentials: credentials, updateCredentialsParams: { data: credentials, encrypted: false }, } } catch (e) { @@ -301,4 +317,54 @@ export class IdentityService { return ssoSession } + + // Returns whether the identity associated with the provided credentials has sufficient permissions + private async validatePermissions( + credentials: IamCredentials, + region: string | undefined + ): Promise { + // Get the identity associated with the credentials + const stsClient = new STSClient({ region: region || 'us-east-1', credentials: credentials }) + const identity = await stsClient.send(new GetCallerIdentityCommand({})) + if (!identity.Arn) { + throw new AwsError('Caller identity ARN not found.', AwsErrorCodes.E_INVALID_PROFILE) + } + + // Check the permissions attached to the identity + const iamClient = new IAMClient({ region: region || 'us-east-1', credentials: credentials }) + const response = await iamClient.send( + new SimulatePrincipalPolicyCommand({ + PolicySourceArn: this.convertToIamArn(identity.Arn), + ActionNames: [ + 'q:StartConversation', + 'q:SendMessage', + 'q:GetConversation', + 'q:ListConversations', + 'q:UpdateConversation', + 'q:DeleteConversation', + 'q:PassRequest', + 'q:StartTroubleshootingAnalysis', + 'q:StartTroubleshootingResolutionExplanation', + 'q:GetTroubleshootingResults', + 'q:UpdateTroubleshootingCommandResult', + 'q:GetIdentityMetaData', + 'q:GenerateCodeFromCommands', + 'q:UsePlugin', + 'codewhisperer:GenerateRecommendations', + ], + }) + ) + return response.EvaluationResults?.every(result => result.EvalDecision === 'allowed') + } + + // Converts an assumed role ARN into an IAM role ARN + private convertToIamArn(arn: string) { + if (arn.includes(':assumed-role/')) { + const parts = arn.split(':') + const roleName = parts[5].split('/')[1] + return `arn:aws:iam::${parts[4]}:role/${roleName}` + } else { + return arn + } + } } From ddc62dccbc3fc1b9b8296c02afcef9473545e5d9 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Thu, 10 Jul 2025 18:09:40 -0400 Subject: [PATCH 03/37] refactor: prefix IAM-related profiles with 'Iam' --- .../profiles/profileService.test.ts | 24 +++++++++---------- .../profiles/profileService.ts | 12 +++++----- .../profiles/sharedConfigProfileStore.test.ts | 12 +++++----- .../profiles/sharedConfigProfileStore.ts | 12 +++++----- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts index bf928a1119..7c621310e1 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts @@ -285,7 +285,7 @@ describe('ProfileService', async () => { it('updateProfile throws on missing role ARN for role source profile', async () => { const profile = { - kinds: [ProfileKind.RoleSourceProfile], + kinds: [ProfileKind.IamRoleSourceProfile], name: 'profile-name', settings: { source_profile: 'source', @@ -297,7 +297,7 @@ describe('ProfileService', async () => { it('updateProfile throws on missing source profile for role source profile', async () => { const profile = { - kinds: [ProfileKind.RoleSourceProfile], + kinds: [ProfileKind.IamRoleSourceProfile], name: 'profile-name', settings: { role_arn: 'role-arn', @@ -309,7 +309,7 @@ describe('ProfileService', async () => { it('updateProfile throws on missing role ARN for role instance profile', async () => { const profile = { - kinds: [ProfileKind.RoleInstanceProfile], + kinds: [ProfileKind.IamRoleInstanceProfile], name: 'profile-name', settings: { credential_source: 'Ec2InstanceMetadata', @@ -322,7 +322,7 @@ describe('ProfileService', async () => { it('updateProfile throws on missing credential source for role instance profile', async () => { const profile = { - kinds: [ProfileKind.RoleInstanceProfile], + kinds: [ProfileKind.IamRoleInstanceProfile], name: 'profile-name', settings: { role_arn: 'role-arn', @@ -340,7 +340,7 @@ describe('ProfileService', async () => { it('updateProfile throws on missing region for role instance profile', async () => { const profile = { - kinds: [ProfileKind.RoleInstanceProfile], + kinds: [ProfileKind.IamRoleInstanceProfile], name: 'profile-name', settings: { role_arn: 'role-arn', @@ -353,7 +353,7 @@ describe('ProfileService', async () => { it('updateProfile throws on missing credential process for process profile', async () => { const profile = { - kinds: [ProfileKind.ProcessProfile], + kinds: [ProfileKind.IamProcessProfile], name: 'profile-name', settings: {}, } @@ -622,7 +622,7 @@ describe('profileService.DuckTypers', () => { } }) - it('profileDuckTypers.RoleSourceProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.IamRoleSourceProfile.eval returns true on valid profiles', () => { const profiles = [ { role_arn: 'role-arn', @@ -637,12 +637,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.RoleSourceProfile.eval(profile) + const actual = profileDuckTypers.IamRoleSourceProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.RoleInstanceProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.IamRoleInstanceProfile.eval returns true on valid profiles', () => { const profiles = [ { role_arn: 'role-arn', @@ -658,12 +658,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.RoleInstanceProfile.eval(profile) + const actual = profileDuckTypers.IamRoleInstanceProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.ProcessProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.IamProcessProfile.eval returns true on valid profiles', () => { const profiles = [ { credential_process: 'credential-process', @@ -677,7 +677,7 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.ProcessProfile.eval(profile) + const actual = profileDuckTypers.IamProcessProfile.eval(profile) expect(actual).to.be.true } }) diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts index 68ed446f04..125b78dcc3 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts @@ -56,19 +56,19 @@ export const profileDuckTypers = { .requireProperty(ProfileFields.aws_access_key_id) .requireProperty(ProfileFields.aws_secret_access_key) .optionalProperty(ProfileFields.aws_session_token), - RoleSourceProfile: new DuckTyper() + IamRoleSourceProfile: new DuckTyper() .requireProperty(ProfileFields.role_arn) .requireProperty(ProfileFields.source_profile) .optionalProperty(ProfileFields.role_session_name) .optionalProperty(ProfileFields.mfa_serial) .disallowProperty(ProfileFields.credential_source), - RoleInstanceProfile: new DuckTyper() + IamRoleInstanceProfile: new DuckTyper() .requireProperty(ProfileFields.role_arn) .requireProperty(ProfileFields.credential_source) .requireProperty(ProfileFields.region) .optionalProperty(ProfileFields.role_session_name) .disallowProperty(ProfileFields.source_profile), - ProcessProfile: new DuckTyper().requireProperty(ProfileFields.credential_process), + IamProcessProfile: new DuckTyper().requireProperty(ProfileFields.credential_process), Unknown: new DuckTyper(), } @@ -181,18 +181,18 @@ export class ProfileService { this.throwOnInvalidProfile(!profileSettings.aws_secret_access_key, 'Secret key required on profile.') } - if (profile.kinds.includes(ProfileKind.RoleInstanceProfile)) { + if (profile.kinds.includes(ProfileKind.IamRoleInstanceProfile)) { this.throwOnInvalidProfile(!profileSettings.role_arn, 'Role ARN required on profile.') this.throwOnInvalidProfile(!profileSettings.region, 'Region required on profile.') this.throwOnInvalidProfile(!profileSettings.credential_source, 'Credential source required on profile.') } - if (profile.kinds.includes(ProfileKind.RoleSourceProfile)) { + if (profile.kinds.includes(ProfileKind.IamRoleSourceProfile)) { this.throwOnInvalidProfile(!profileSettings.role_arn, 'Role ARN required on profile.') this.throwOnInvalidProfile(!profileSettings.source_profile, 'Source profile required on profile.') } - if (profile.kinds.includes(ProfileKind.ProcessProfile)) { + if (profile.kinds.includes(ProfileKind.IamProcessProfile)) { this.throwOnInvalidProfile(!profileSettings.credential_process, 'Credential process required on profile.') } diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts index 003b306faa..e650c79872 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts @@ -328,7 +328,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.RoleSourceProfile], + kinds: [ProfileKind.IamRoleSourceProfile], name: 'role-source.profile', settings: { role_arn: 'new-role-arn', @@ -336,7 +336,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.RoleInstanceProfile], + kinds: [ProfileKind.IamRoleInstanceProfile], name: 'role-instance.profile', settings: { role_arn: 'new-role-arn', @@ -345,7 +345,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.ProcessProfile], + kinds: [ProfileKind.IamProcessProfile], name: 'process.profile', settings: { credential_process: 'new-credential-process', @@ -414,14 +414,14 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.ProcessProfile], + kinds: [ProfileKind.IamProcessProfile], name: 'process.profile', settings: { credential_process: 'new-credential-process', }, }, { - kinds: [ProfileKind.RoleInstanceProfile], + kinds: [ProfileKind.IamRoleInstanceProfile], name: 'role-instance.profile', settings: { role_arn: 'new-role-arn', @@ -431,7 +431,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.RoleSourceProfile], + kinds: [ProfileKind.IamRoleSourceProfile], name: 'role-source.profile', settings: { role_arn: 'new-role-arn', diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts index fb4f167d69..be0eed09c8 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts @@ -58,19 +58,19 @@ export class SharedConfigProfileStore implements ProfileStore { profile.settings!.region = settings.region profile.settings!.sso_session = settings.sso_session } - if (profileDuckTypers.ProcessProfile.eval(settings)) { - profile.kinds.push(ProfileKind.ProcessProfile) + if (profileDuckTypers.IamProcessProfile.eval(settings)) { + profile.kinds.push(ProfileKind.IamProcessProfile) profile.settings!.credential_process = settings.credential_process } - if (profileDuckTypers.RoleSourceProfile.eval(settings)) { - profile.kinds.push(ProfileKind.RoleSourceProfile) + if (profileDuckTypers.IamRoleSourceProfile.eval(settings)) { + profile.kinds.push(ProfileKind.IamRoleSourceProfile) profile.settings!.role_arn = settings.role_arn profile.settings!.source_profile = settings.source_profile profile.settings!.mfa_serial = settings.mfa_serial profile.settings!.role_session_name = settings.role_session_name } - if (profileDuckTypers.RoleInstanceProfile.eval(settings)) { - profile.kinds.push(ProfileKind.RoleInstanceProfile) + if (profileDuckTypers.IamRoleInstanceProfile.eval(settings)) { + profile.kinds.push(ProfileKind.IamRoleInstanceProfile) profile.settings!.role_arn = settings.role_arn profile.settings!.region = settings.region profile.settings!.credential_source = settings.credential_source From a63dbc1cbcae06518209492aa827daac671235b2 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Thu, 10 Jul 2025 18:09:40 -0400 Subject: [PATCH 04/37] refactor: prefix IAM-related profiles with 'Iam' --- .../src/language-server/identityService.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index 17fdcb2a38..95e5a6df66 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -60,7 +60,7 @@ describe('IdentityService', () => { }, }, { - kinds: [ProfileKind.RoleSourceProfile], + kinds: [ProfileKind.IamRoleSourceProfile], name: 'my-role-profile', settings: { role_arn: 'my-role-arn', @@ -68,7 +68,7 @@ describe('IdentityService', () => { }, }, { - kinds: [ProfileKind.RoleSourceProfile], + kinds: [ProfileKind.IamRoleSourceProfile], name: 'my-mfa-profile', settings: { role_arn: 'my-role-arn', @@ -77,7 +77,7 @@ describe('IdentityService', () => { }, }, { - kinds: [ProfileKind.ProcessProfile], + kinds: [ProfileKind.IamProcessProfile], name: 'my-process-profile', settings: { credential_process: 'my-process', From a6a11b05cc48cd01d8f84804471151a703d8da19 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Fri, 11 Jul 2025 11:55:14 -0400 Subject: [PATCH 05/37] refactor: move profile fields into profileTypes object --- .../profiles/profileService.test.ts | 13 ---- .../profiles/profileService.ts | 77 +++++++++++++------ .../profiles/sharedConfigProfileStore.test.ts | 9 --- .../profiles/sharedConfigProfileStore.ts | 38 +++------ 4 files changed, 62 insertions(+), 75 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts index 7c621310e1..8eb911e140 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts @@ -338,19 +338,6 @@ describe('ProfileService', async () => { ) }) - it('updateProfile throws on missing region for role instance profile', async () => { - const profile = { - kinds: [ProfileKind.IamRoleInstanceProfile], - name: 'profile-name', - settings: { - role_arn: 'role-arn', - credential_source: 'Ec2InstanceMetadata', - }, - } - - await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Region required on profile.') - }) - it('updateProfile throws on missing credential process for process profile', async () => { const profile = { kinds: [ProfileKind.IamProcessProfile], diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts index 125b78dcc3..dfc93f791e 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts @@ -39,6 +39,7 @@ export const ProfileFields = { credential_source: 'credential_source', source_profile: 'source_profile', mfa_serial: 'mfa_serial', + external_id: 'external_id', } as const export const SsoSessionFields = { @@ -47,30 +48,57 @@ export const SsoSessionFields = { sso_start_url: 'sso_start_url', } as const -export const profileDuckTypers = { - SsoTokenProfile: new DuckTyper() - .requireProperty(ProfileFields.sso_session) - .disallowProperty(ProfileFields.sso_account_id) - .disallowProperty(ProfileFields.sso_role_name), - IamUserProfile: new DuckTyper() - .requireProperty(ProfileFields.aws_access_key_id) - .requireProperty(ProfileFields.aws_secret_access_key) - .optionalProperty(ProfileFields.aws_session_token), - IamRoleSourceProfile: new DuckTyper() - .requireProperty(ProfileFields.role_arn) - .requireProperty(ProfileFields.source_profile) - .optionalProperty(ProfileFields.role_session_name) - .optionalProperty(ProfileFields.mfa_serial) - .disallowProperty(ProfileFields.credential_source), - IamRoleInstanceProfile: new DuckTyper() - .requireProperty(ProfileFields.role_arn) - .requireProperty(ProfileFields.credential_source) - .requireProperty(ProfileFields.region) - .optionalProperty(ProfileFields.role_session_name) - .disallowProperty(ProfileFields.source_profile), - IamProcessProfile: new DuckTyper().requireProperty(ProfileFields.credential_process), - Unknown: new DuckTyper(), -} +export const profileTypes = { + SsoTokenProfile: { + kind: ProfileKind.SsoTokenProfile, + required: [ProfileFields.sso_session], + optional: [ProfileFields.region], + disallowed: [ProfileFields.sso_account_id, ProfileFields.sso_role_name], + }, + IamUserProfile: { + kind: ProfileKind.IamUserProfile, + required: [ProfileFields.aws_access_key_id, ProfileFields.aws_secret_access_key], + optional: [ProfileFields.aws_session_token], + disallowed: [], + }, + IamRoleSourceProfile: { + kind: ProfileKind.IamRoleSourceProfile, + required: [ProfileFields.role_arn, ProfileFields.source_profile], + optional: [ProfileFields.external_id, ProfileFields.role_session_name, ProfileFields.mfa_serial], + disallowed: [ProfileFields.credential_source], + }, + IamRoleInstanceProfile: { + kind: ProfileKind.IamRoleInstanceProfile, + required: [ProfileFields.role_arn, ProfileFields.credential_source], + optional: [ProfileFields.external_id, ProfileFields.role_session_name, ProfileFields.region], + disallowed: [ProfileFields.source_profile], + }, + IamProcessProfile: { + kind: ProfileKind.IamProcessProfile, + required: [ProfileFields.credential_process], + optional: [], + disallowed: [], + }, +} as const + +export const profileDuckTypers = Object.fromEntries( + Object.entries(profileTypes).map(([key, def]) => [ + key, + (() => { + const typer = new DuckTyper() + for (const field of def.required) { + typer.requireProperty(field) + } + for (const field of def.optional) { + typer.optionalProperty(field) + } + for (const field of def.disallowed) { + typer.disallowProperty(field) + } + return typer + })(), + ]) +) export const ssoSessionDuckTyper = new DuckTyper() .requireProperty(SsoSessionFields.sso_start_url) @@ -183,7 +211,6 @@ export class ProfileService { if (profile.kinds.includes(ProfileKind.IamRoleInstanceProfile)) { this.throwOnInvalidProfile(!profileSettings.role_arn, 'Role ARN required on profile.') - this.throwOnInvalidProfile(!profileSettings.region, 'Region required on profile.') this.throwOnInvalidProfile(!profileSettings.credential_source, 'Credential source required on profile.') } diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts index e650c79872..ef38b3dee6 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts @@ -94,7 +94,6 @@ describe('SharedConfigProfileStore', async () => { settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', - aws_session_token: undefined, }, }, { @@ -186,7 +185,6 @@ describe('SharedConfigProfileStore', async () => { settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', - aws_session_token: undefined, }, }, { @@ -283,7 +281,6 @@ describe('SharedConfigProfileStore', async () => { settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', - aws_session_token: undefined, }, }, { @@ -341,7 +338,6 @@ describe('SharedConfigProfileStore', async () => { settings: { role_arn: 'new-role-arn', credential_source: 'new-source', - region: 'new-region', }, }, { @@ -401,7 +397,6 @@ describe('SharedConfigProfileStore', async () => { settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', - aws_session_token: undefined, }, }, { @@ -426,8 +421,6 @@ describe('SharedConfigProfileStore', async () => { settings: { role_arn: 'new-role-arn', credential_source: 'new-source', - region: 'new-region', - role_session_name: undefined, }, }, { @@ -436,8 +429,6 @@ describe('SharedConfigProfileStore', async () => { settings: { role_arn: 'new-role-arn', source_profile: 'new-source-profile', - mfa_serial: undefined, - role_session_name: undefined, }, }, { diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts index be0eed09c8..0b612a46a0 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts @@ -53,34 +53,16 @@ export class SharedConfigProfileStore implements ProfileStore { settings: {}, } // Add the kinds and settings for each matched profile type - if (profileDuckTypers.SsoTokenProfile.eval(settings)) { - profile.kinds.push(ProfileKind.SsoTokenProfile) - profile.settings!.region = settings.region - profile.settings!.sso_session = settings.sso_session - } - if (profileDuckTypers.IamProcessProfile.eval(settings)) { - profile.kinds.push(ProfileKind.IamProcessProfile) - profile.settings!.credential_process = settings.credential_process - } - if (profileDuckTypers.IamRoleSourceProfile.eval(settings)) { - profile.kinds.push(ProfileKind.IamRoleSourceProfile) - profile.settings!.role_arn = settings.role_arn - profile.settings!.source_profile = settings.source_profile - profile.settings!.mfa_serial = settings.mfa_serial - profile.settings!.role_session_name = settings.role_session_name - } - if (profileDuckTypers.IamRoleInstanceProfile.eval(settings)) { - profile.kinds.push(ProfileKind.IamRoleInstanceProfile) - profile.settings!.role_arn = settings.role_arn - profile.settings!.region = settings.region - profile.settings!.credential_source = settings.credential_source - profile.settings!.role_session_name = settings.role_session_name - } - if (profileDuckTypers.IamUserProfile.eval(settings)) { - profile.kinds.push(ProfileKind.IamUserProfile) - profile.settings!.aws_access_key_id = settings.aws_access_key_id - profile.settings!.aws_secret_access_key = settings.aws_secret_access_key - profile.settings!.aws_session_token = settings.aws_session_token + for (const [profileType, fields] of Object.entries(profileTypes)) { + if (profileDuckTypers[profileType].eval(settings)) { + profile.kinds.push(fields.kind) + const relevantFields = [...fields.required, ...fields.optional] + for (const field of relevantFields) { + if (settings[field] !== undefined) { + profile.settings![field] = settings[field] + } + } + } } // If the profile does not match any profile type, mark it as an unknown profile if (profile.kinds.length === 0) { From 5fb1185470159e2375907c39698671c97cb74c65 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Fri, 11 Jul 2025 12:51:22 -0400 Subject: [PATCH 06/37] fix: add region as optional field to IamRoleSourceProfile --- .../src/language-server/profiles/profileService.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts index dfc93f791e..6283f78a97 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts @@ -64,7 +64,12 @@ export const profileTypes = { IamRoleSourceProfile: { kind: ProfileKind.IamRoleSourceProfile, required: [ProfileFields.role_arn, ProfileFields.source_profile], - optional: [ProfileFields.external_id, ProfileFields.role_session_name, ProfileFields.mfa_serial], + optional: [ + ProfileFields.external_id, + ProfileFields.role_session_name, + ProfileFields.region, + ProfileFields.mfa_serial, + ], disallowed: [ProfileFields.credential_source], }, IamRoleInstanceProfile: { From b74a2474e62b245e12e112b585f8c7654ff11eed Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Mon, 14 Jul 2025 16:22:43 -0400 Subject: [PATCH 07/37] refactor: move MFA code retrieval into separate request --- .../src/language-server/identityServer.ts | 4 +- .../language-server/identityService.test.ts | 21 +++++- .../src/language-server/identityService.ts | 67 ++++++++++--------- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/identityServer.ts b/server/aws-lsp-identity/src/language-server/identityServer.ts index 9fcbebfcff..24c8c3c3e4 100644 --- a/server/aws-lsp-identity/src/language-server/identityServer.ts +++ b/server/aws-lsp-identity/src/language-server/identityServer.ts @@ -61,8 +61,8 @@ export class IdentityServer extends ServerBase { profileStore, ssoCache, autoRefresher, - iamProvider, - { showUrl, showMessageRequest, showProgress, sendGetMfaCode }, + { showUrl, showMessageRequest, showProgress }, + this.features.identityManagement.sendGetMfaCode, this.getClientName(params), this.observability ) diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index 95e5a6df66..810b742715 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -16,7 +16,6 @@ import { Logging, Telemetry } from '@aws/language-server-runtimes/server-interfa import { Observability } from '@aws/lsp-core' import { STSClient } from '@aws-sdk/client-sts' import { IAMClient } from '@aws-sdk/client-iam' -import { IamProvider } from '../iam/iamProvider' // eslint-disable-next-line use(require('chai-as-promised')) @@ -145,6 +144,7 @@ describe('IdentityService', () => { showProgress: _ => Promise.resolve(), sendGetMfaCode: () => Promise.resolve({ code: 'mfa-code' }), }, + () => Promise.resolve({ code: 'mfa-code' }), 'My Client', observability, { @@ -153,8 +153,23 @@ describe('IdentityService', () => { } ) - const validatePermissionsStub = stub(sut as any, 'validatePermissions') - validatePermissionsStub.resolves(true) + stub(STSClient.prototype, 'send').resolves({ + Credentials: { + AccessKeyId: 'role-access-key', + SecretAccessKey: 'role-secret-key', + SessionToken: 'role-session-token', + Expiration: new Date('2024-09-25T18:09:20.455Z'), + }, + AssumedRoleUser: { + Arn: 'role-arn', + AssumedRoleId: 'role-id', + }, + Arn: 'role-arn', + }) + + stub(IAMClient.prototype, 'send').resolves({ + EvaluationResults: [], + }) }) afterEach(() => { diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index ae82a74073..4ef52c3370 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -17,6 +17,8 @@ import { SsoSession, SsoTokenSourceKind, ProfileKind, + GetMfaCodeParams, + GetMfaCodeResult, } from '@aws/language-server-runtimes/server-interface' import { normalizeSettingList, ProfileStore } from './profiles/profileService' @@ -36,7 +38,7 @@ import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts' import { __ServiceException } from '@aws-sdk/client-sso-oidc/dist-types/models/SSOOIDCServiceException' import { deviceCodeFlow } from '../sso/deviceCode/deviceCodeFlow' import { SSOToken } from '@smithy/shared-ini-file-loader' -import { IAMClient, SimulatePrincipalPolicyCommand } from '@aws-sdk/client-iam' +import { IAMClient, SimulatePrincipalPolicyCommand, SimulatePrincipalPolicyCommandOutput } from '@aws-sdk/client-iam' type SsoTokenSource = IamIdentityCenterSsoTokenSource | AwsBuilderIdSsoTokenSource type AuthFlows = Record Promise> @@ -46,14 +48,31 @@ const flows: AuthFlows = { [AuthorizationFlowKind.DeviceCode]: deviceCodeFlow, [AuthorizationFlowKind.Pkce]: authorizationCodePkceFlow, } +const qPermissions = [ + 'q:StartConversation', + 'q:SendMessage', + 'q:GetConversation', + 'q:ListConversations', + 'q:UpdateConversation', + 'q:DeleteConversation', + 'q:PassRequest', + 'q:StartTroubleshootingAnalysis', + 'q:StartTroubleshootingResolutionExplanation', + 'q:GetTroubleshootingResults', + 'q:UpdateTroubleshootingCommandResult', + 'q:GetIdentityMetaData', + 'q:GenerateCodeFromCommands', + 'q:UsePlugin', + 'codewhisperer:GenerateRecommendations', +] export class IdentityService { constructor( private readonly profileStore: ProfileStore, private readonly ssoCache: SsoCache, private readonly autoRefresher: SsoTokenAutoRefresher, - private readonly iamProvider: IamProvider, - private readonly handlers: Handlers, + private readonly handlers: SsoFlowParams['handlers'], + private readonly sendGetMfaCode: (params: GetMfaCodeParams) => Promise, private readonly clientName: string, private readonly observability: Observability, private readonly authFlows: AuthFlows = flows @@ -186,12 +205,14 @@ export class IdentityService { } // Validate permissions on user or assumed role - const hasPermissions = await this.validatePermissions(credentials, profile.settings?.region) - if (!hasPermissions) { - throw new AwsError( - `User or assumed role has insufficient permissions.`, - AwsErrorCodes.E_INVALID_PROFILE - ) + if (options.validatePermissions) { + const response = await this.simulatePermissions(credentials, qPermissions, profile.settings?.region) + if (!response?.EvaluationResults?.every(result => result.EvalDecision === 'allowed')) { + throw new AwsError( + `User or assumed role has insufficient permissions.`, + AwsErrorCodes.E_INVALID_PROFILE + ) + } } emitMetric('Succeeded') @@ -319,10 +340,11 @@ export class IdentityService { } // Returns whether the identity associated with the provided credentials has sufficient permissions - private async validatePermissions( + private async simulatePermissions( credentials: IamCredentials, - region: string | undefined - ): Promise { + permissions: string[], + region?: string + ): Promise { // Get the identity associated with the credentials const stsClient = new STSClient({ region: region || 'us-east-1', credentials: credentials }) const identity = await stsClient.send(new GetCallerIdentityCommand({})) @@ -332,29 +354,12 @@ export class IdentityService { // Check the permissions attached to the identity const iamClient = new IAMClient({ region: region || 'us-east-1', credentials: credentials }) - const response = await iamClient.send( + return await iamClient.send( new SimulatePrincipalPolicyCommand({ PolicySourceArn: this.convertToIamArn(identity.Arn), - ActionNames: [ - 'q:StartConversation', - 'q:SendMessage', - 'q:GetConversation', - 'q:ListConversations', - 'q:UpdateConversation', - 'q:DeleteConversation', - 'q:PassRequest', - 'q:StartTroubleshootingAnalysis', - 'q:StartTroubleshootingResolutionExplanation', - 'q:GetTroubleshootingResults', - 'q:UpdateTroubleshootingCommandResult', - 'q:GetIdentityMetaData', - 'q:GenerateCodeFromCommands', - 'q:UsePlugin', - 'codewhisperer:GenerateRecommendations', - ], + ActionNames: permissions, }) ) - return response.EvaluationResults?.every(result => result.EvalDecision === 'allowed') } // Converts an assumed role ARN into an IAM role ARN From 108a053f60ba8855099975399fc87a4b7e8d3d5f Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Tue, 15 Jul 2025 14:52:48 -0400 Subject: [PATCH 08/37] fix: naming changes --- .../src/credentials/credentialsProvider.ts | 1 - .../profiles/profileService.test.ts | 44 +++++++++---------- .../profiles/profileService.ts | 38 ++++++++++------ .../profiles/sharedConfigProfileStore.test.ts | 24 +++++----- 4 files changed, 58 insertions(+), 49 deletions(-) diff --git a/core/aws-lsp-core/src/credentials/credentialsProvider.ts b/core/aws-lsp-core/src/credentials/credentialsProvider.ts index bea8a5cfcd..a56be8e0ad 100644 --- a/core/aws-lsp-core/src/credentials/credentialsProvider.ts +++ b/core/aws-lsp-core/src/credentials/credentialsProvider.ts @@ -4,7 +4,6 @@ export interface IamCredentials { accessKeyId: string secretAccessKey: string sessionToken?: string - expiration?: Date } export interface BearerToken { diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts index 8eb911e140..ebbfecde4d 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts @@ -54,7 +54,7 @@ describe('ProfileService', async () => { } profile4 = { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'profile4', settings: { aws_access_key_id: 'access-key', @@ -259,9 +259,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Sso-session name required on profile.') }) - it('updateProfile throws on missing access key for IAM user profile', async () => { + it('updateProfile throws on missing access key for IamCredentialsProfile', async () => { const profile = { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'profile-name', settings: { aws_secret_access_key: 'secret-key', @@ -271,9 +271,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Access key required on profile.') }) - it('updateProfile throws on missing secret key for IAM user profile', async () => { + it('updateProfile throws on missing secret key for IamCredentialsProfile', async () => { const profile = { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'profile-name', settings: { aws_access_key_id: 'access-key', @@ -283,9 +283,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Secret key required on profile.') }) - it('updateProfile throws on missing role ARN for role source profile', async () => { + it('updateProfile throws on missing role ARN for IamSourceProfileProfile', async () => { const profile = { - kinds: [ProfileKind.IamRoleSourceProfile], + kinds: [ProfileKind.IamSourceProfileProfile], name: 'profile-name', settings: { source_profile: 'source', @@ -295,9 +295,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Role ARN required on profile.') }) - it('updateProfile throws on missing source profile for role source profile', async () => { + it('updateProfile throws on missing source profile for IamSourceProfileProfile', async () => { const profile = { - kinds: [ProfileKind.IamRoleSourceProfile], + kinds: [ProfileKind.IamSourceProfileProfile], name: 'profile-name', settings: { role_arn: 'role-arn', @@ -309,7 +309,7 @@ describe('ProfileService', async () => { it('updateProfile throws on missing role ARN for role instance profile', async () => { const profile = { - kinds: [ProfileKind.IamRoleInstanceProfile], + kinds: [ProfileKind.IamCredentialSourceProfile], name: 'profile-name', settings: { credential_source: 'Ec2InstanceMetadata', @@ -322,7 +322,7 @@ describe('ProfileService', async () => { it('updateProfile throws on missing credential source for role instance profile', async () => { const profile = { - kinds: [ProfileKind.IamRoleInstanceProfile], + kinds: [ProfileKind.IamCredentialSourceProfile], name: 'profile-name', settings: { role_arn: 'role-arn', @@ -340,7 +340,7 @@ describe('ProfileService', async () => { it('updateProfile throws on missing credential process for process profile', async () => { const profile = { - kinds: [ProfileKind.IamProcessProfile], + kinds: [ProfileKind.IamCredentialProcessProfile], name: 'profile-name', settings: {}, } @@ -573,7 +573,7 @@ describe('profileService.DuckTypers', () => { } }) - it('profileDuckTypers.IamUserProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.IamCredentialsProfile.eval returns true on valid profiles', () => { const profiles = [ { aws_access_key_id: 'access-key', @@ -587,12 +587,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamUserProfile.eval(profile) + const actual = profileDuckTypers.IamCredentialsProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.IamUserProfile.eval returns false on invalid profiles', () => { + it('profileDuckTypers.IamCredentialsProfile.eval returns false on invalid profiles', () => { const profiles = [ { sso_session: 'my-sso-session', @@ -604,12 +604,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamUserProfile.eval(profile as object) + const actual = profileDuckTypers.IamCredentialsProfile.eval(profile as object) expect(actual).to.be.false } }) - it('profileDuckTypers.IamRoleSourceProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.IamSourceProfileProfile.eval returns true on valid profiles', () => { const profiles = [ { role_arn: 'role-arn', @@ -624,12 +624,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamRoleSourceProfile.eval(profile) + const actual = profileDuckTypers.IamSourceProfileProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.IamRoleInstanceProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.IamCredentialSourceProfile.eval returns true on valid profiles', () => { const profiles = [ { role_arn: 'role-arn', @@ -645,12 +645,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamRoleInstanceProfile.eval(profile) + const actual = profileDuckTypers.IamCredentialSourceProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.IamProcessProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.IamCredentialProcessProfile.eval returns true on valid profiles', () => { const profiles = [ { credential_process: 'credential-process', @@ -664,7 +664,7 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamProcessProfile.eval(profile) + const actual = profileDuckTypers.IamCredentialProcessProfile.eval(profile) expect(actual).to.be.true } }) diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts index 6283f78a97..a37e27e30e 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts @@ -40,6 +40,8 @@ export const ProfileFields = { source_profile: 'source_profile', mfa_serial: 'mfa_serial', external_id: 'external_id', + credential_cache: 'credential_cache', + credential_cache_location: 'credential_cache_location', } as const export const SsoSessionFields = { @@ -55,31 +57,39 @@ export const profileTypes = { optional: [ProfileFields.region], disallowed: [ProfileFields.sso_account_id, ProfileFields.sso_role_name], }, - IamUserProfile: { - kind: ProfileKind.IamUserProfile, + IamCredentialsProfile: { + kind: ProfileKind.IamCredentialsProfile, required: [ProfileFields.aws_access_key_id, ProfileFields.aws_secret_access_key], optional: [ProfileFields.aws_session_token], disallowed: [], }, - IamRoleSourceProfile: { - kind: ProfileKind.IamRoleSourceProfile, + IamSourceProfileProfile: { + kind: ProfileKind.IamSourceProfileProfile, required: [ProfileFields.role_arn, ProfileFields.source_profile], optional: [ ProfileFields.external_id, ProfileFields.role_session_name, ProfileFields.region, ProfileFields.mfa_serial, + ProfileFields.credential_cache, + ProfileFields.credential_cache_location, ], disallowed: [ProfileFields.credential_source], }, - IamRoleInstanceProfile: { - kind: ProfileKind.IamRoleInstanceProfile, + IamCredentialSourceProfile: { + kind: ProfileKind.IamCredentialSourceProfile, required: [ProfileFields.role_arn, ProfileFields.credential_source], - optional: [ProfileFields.external_id, ProfileFields.role_session_name, ProfileFields.region], + optional: [ + ProfileFields.external_id, + ProfileFields.role_session_name, + ProfileFields.region, + ProfileFields.credential_cache, + ProfileFields.credential_cache_location, + ], disallowed: [ProfileFields.source_profile], }, - IamProcessProfile: { - kind: ProfileKind.IamProcessProfile, + IamCredentialProcessProfile: { + kind: ProfileKind.IamCredentialProcessProfile, required: [ProfileFields.credential_process], optional: [], disallowed: [], @@ -153,7 +163,6 @@ export class ProfileService { this.throwOnInvalidProfile(!params.profile, 'Profile required.') const profile = params.profile! - // Removing this check for profile deletion this.throwOnInvalidProfile( !profile.kinds.some(kind => Object.values(ProfileKind).includes(kind)), 'Profile must be non-legacy sso-session or iam-credentials type.' @@ -173,6 +182,7 @@ export class ProfileService { throw new AwsError('Cannot create profile.', AwsErrorCodes.E_CANNOT_CREATE_PROFILE) } + // TODO: can this be refactored and simplified using the existing DuckTypers? // Validate SSO profile if (profile.kinds.includes(ProfileKind.SsoTokenProfile)) { this.throwOnInvalidProfile(!profileSettings.sso_session, 'Sso-session name required on profile.') @@ -209,22 +219,22 @@ export class ProfileService { } // Validate IAM profiles - if (profile.kinds.includes(ProfileKind.IamUserProfile)) { + if (profile.kinds.includes(ProfileKind.IamCredentialsProfile)) { this.throwOnInvalidProfile(!profileSettings.aws_access_key_id, 'Access key required on profile.') this.throwOnInvalidProfile(!profileSettings.aws_secret_access_key, 'Secret key required on profile.') } - if (profile.kinds.includes(ProfileKind.IamRoleInstanceProfile)) { + if (profile.kinds.includes(ProfileKind.IamCredentialSourceProfile)) { this.throwOnInvalidProfile(!profileSettings.role_arn, 'Role ARN required on profile.') this.throwOnInvalidProfile(!profileSettings.credential_source, 'Credential source required on profile.') } - if (profile.kinds.includes(ProfileKind.IamRoleSourceProfile)) { + if (profile.kinds.includes(ProfileKind.IamSourceProfileProfile)) { this.throwOnInvalidProfile(!profileSettings.role_arn, 'Role ARN required on profile.') this.throwOnInvalidProfile(!profileSettings.source_profile, 'Source profile required on profile.') } - if (profile.kinds.includes(ProfileKind.IamProcessProfile)) { + if (profile.kinds.includes(ProfileKind.IamCredentialProcessProfile)) { this.throwOnInvalidProfile(!profileSettings.credential_process, 'Credential process required on profile.') } diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts index ef38b3dee6..fa0e85bb72 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts @@ -89,7 +89,7 @@ describe('SharedConfigProfileStore', async () => { expect(actual).to.deep.equal({ profiles: [ { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', @@ -180,7 +180,7 @@ describe('SharedConfigProfileStore', async () => { expect(after).to.deep.equal({ profiles: [ { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', @@ -276,7 +276,7 @@ describe('SharedConfigProfileStore', async () => { expect(after).to.deep.equal({ profiles: [ { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', @@ -316,7 +316,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'iam-user.profile', settings: { aws_access_key_id: 'new-access-key', @@ -325,7 +325,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamRoleSourceProfile], + kinds: [ProfileKind.IamSourceProfileProfile], name: 'role-source.profile', settings: { role_arn: 'new-role-arn', @@ -333,7 +333,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamRoleInstanceProfile], + kinds: [ProfileKind.IamCredentialSourceProfile], name: 'role-instance.profile', settings: { role_arn: 'new-role-arn', @@ -341,7 +341,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamProcessProfile], + kinds: [ProfileKind.IamCredentialProcessProfile], name: 'process.profile', settings: { credential_process: 'new-credential-process', @@ -392,7 +392,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', @@ -400,7 +400,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'iam-user.profile', settings: { aws_access_key_id: 'new-access-key', @@ -409,14 +409,14 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamProcessProfile], + kinds: [ProfileKind.IamCredentialProcessProfile], name: 'process.profile', settings: { credential_process: 'new-credential-process', }, }, { - kinds: [ProfileKind.IamRoleInstanceProfile], + kinds: [ProfileKind.IamCredentialSourceProfile], name: 'role-instance.profile', settings: { role_arn: 'new-role-arn', @@ -424,7 +424,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamRoleSourceProfile], + kinds: [ProfileKind.IamSourceProfileProfile], name: 'role-source.profile', settings: { role_arn: 'new-role-arn', From a731b96ea1de70155661b0671e011154264ff2a4 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Wed, 16 Jul 2025 09:22:50 -0400 Subject: [PATCH 09/37] chore: rename profile in unit test title --- .../src/language-server/profiles/profileService.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts index ebbfecde4d..216f3010af 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts @@ -307,7 +307,7 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Source profile required on profile.') }) - it('updateProfile throws on missing role ARN for role instance profile', async () => { + it('updateProfile throws on missing role ARN for IamCredentialSourceProfile', async () => { const profile = { kinds: [ProfileKind.IamCredentialSourceProfile], name: 'profile-name', @@ -320,7 +320,7 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Role ARN required on profile.') }) - it('updateProfile throws on missing credential source for role instance profile', async () => { + it('updateProfile throws on missing credential source for IamCredentialSourceProfile', async () => { const profile = { kinds: [ProfileKind.IamCredentialSourceProfile], name: 'profile-name', From 71dc614a4dc7247fa7e2f3e6d22cae1269695093 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Wed, 16 Jul 2025 11:42:37 -0400 Subject: [PATCH 10/37] fix: sync changes with iam-profile --- .../language-server/identityService.test.ts | 10 ++++---- .../src/language-server/identityService.ts | 25 ++++++------------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index 810b742715..88ee4d0f98 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -42,7 +42,7 @@ describe('IdentityService', () => { }, }, { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'my-iam-profile', settings: { aws_access_key_id: 'my-access-key', @@ -50,7 +50,7 @@ describe('IdentityService', () => { }, }, { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'my-sts-profile', settings: { aws_access_key_id: 'my-access-key', @@ -59,7 +59,7 @@ describe('IdentityService', () => { }, }, { - kinds: [ProfileKind.IamRoleSourceProfile], + kinds: [ProfileKind.IamSourceProfileProfile], name: 'my-role-profile', settings: { role_arn: 'my-role-arn', @@ -67,7 +67,7 @@ describe('IdentityService', () => { }, }, { - kinds: [ProfileKind.IamRoleSourceProfile], + kinds: [ProfileKind.IamSourceProfileProfile], name: 'my-mfa-profile', settings: { role_arn: 'my-role-arn', @@ -76,7 +76,7 @@ describe('IdentityService', () => { }, }, { - kinds: [ProfileKind.IamProcessProfile], + kinds: [ProfileKind.IamCredentialProcessProfile], name: 'my-process-profile', settings: { credential_process: 'my-process', diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 4ef52c3370..897701ef6e 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -179,7 +179,7 @@ export class IdentityService { const options = { ...getIamCredentialOptionsDefaults, ...params.options } token.onCancellationRequested(_ => { - if (options.generateOnInvalidStsCredential) { + if (options.callStsOnInvalidIamCredential) { emitMetric('Cancelled', null) } }) @@ -194,7 +194,7 @@ export class IdentityService { let credentials: IamCredentials // Get the credentials directly from the profile - if (profile.kinds.includes(ProfileKind.IamUserProfile)) { + if (profile.kinds.includes(ProfileKind.IamCredentialsProfile)) { credentials = { accessKeyId: profile.settings!.aws_access_key_id!, secretAccessKey: profile.settings!.aws_secret_access_key!, @@ -204,7 +204,7 @@ export class IdentityService { throw new AwsError('Credentials could not be found for profile', AwsErrorCodes.E_INVALID_PROFILE) } - // Validate permissions on user or assumed role + // Validate permissions if (options.validatePermissions) { const response = await this.simulatePermissions(credentials, qPermissions, profile.settings?.region) if (!response?.EvaluationResults?.every(result => result.EvalDecision === 'allowed')) { @@ -339,37 +339,26 @@ export class IdentityService { return ssoSession } - // Returns whether the identity associated with the provided credentials has sufficient permissions + // Simulate permissions on the identity associated with the credentials private async simulatePermissions( credentials: IamCredentials, permissions: string[], region?: string ): Promise { - // Get the identity associated with the credentials + // Convert the credentials into an identity const stsClient = new STSClient({ region: region || 'us-east-1', credentials: credentials }) const identity = await stsClient.send(new GetCallerIdentityCommand({})) if (!identity.Arn) { throw new AwsError('Caller identity ARN not found.', AwsErrorCodes.E_INVALID_PROFILE) } - // Check the permissions attached to the identity + // Simulate permissions on the identity const iamClient = new IAMClient({ region: region || 'us-east-1', credentials: credentials }) return await iamClient.send( new SimulatePrincipalPolicyCommand({ - PolicySourceArn: this.convertToIamArn(identity.Arn), + PolicySourceArn: identity.Arn, ActionNames: permissions, }) ) } - - // Converts an assumed role ARN into an IAM role ARN - private convertToIamArn(arn: string) { - if (arn.includes(':assumed-role/')) { - const parts = arn.split(':') - const roleName = parts[5].split('/')[1] - return `arn:aws:iam::${parts[4]}:role/${roleName}` - } else { - return arn - } - } } From 518cbb25bc8bd8595930584bc95d4d4c730da379 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Wed, 16 Jul 2025 17:40:45 -0400 Subject: [PATCH 11/37] chore: remove unused mfa code --- server/aws-lsp-identity/src/language-server/identityServer.ts | 1 - .../src/language-server/identityService.test.ts | 1 - server/aws-lsp-identity/src/language-server/identityService.ts | 3 --- 3 files changed, 5 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/identityServer.ts b/server/aws-lsp-identity/src/language-server/identityServer.ts index 24c8c3c3e4..ade57172aa 100644 --- a/server/aws-lsp-identity/src/language-server/identityServer.ts +++ b/server/aws-lsp-identity/src/language-server/identityServer.ts @@ -62,7 +62,6 @@ export class IdentityServer extends ServerBase { ssoCache, autoRefresher, { showUrl, showMessageRequest, showProgress }, - this.features.identityManagement.sendGetMfaCode, this.getClientName(params), this.observability ) diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index 88ee4d0f98..ca205bb360 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -144,7 +144,6 @@ describe('IdentityService', () => { showProgress: _ => Promise.resolve(), sendGetMfaCode: () => Promise.resolve({ code: 'mfa-code' }), }, - () => Promise.resolve({ code: 'mfa-code' }), 'My Client', observability, { diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 897701ef6e..44833c7e86 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -17,8 +17,6 @@ import { SsoSession, SsoTokenSourceKind, ProfileKind, - GetMfaCodeParams, - GetMfaCodeResult, } from '@aws/language-server-runtimes/server-interface' import { normalizeSettingList, ProfileStore } from './profiles/profileService' @@ -72,7 +70,6 @@ export class IdentityService { private readonly ssoCache: SsoCache, private readonly autoRefresher: SsoTokenAutoRefresher, private readonly handlers: SsoFlowParams['handlers'], - private readonly sendGetMfaCode: (params: GetMfaCodeParams) => Promise, private readonly clientName: string, private readonly observability: Observability, private readonly authFlows: AuthFlows = flows From 95700498df40621801cece9de0065ac895875f65 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Thu, 17 Jul 2025 12:29:34 -0400 Subject: [PATCH 12/37] fix: incorporate PR feedback --- .../src/language-server/identityServer.ts | 6 +- .../language-server/identityService.test.ts | 14 +--- .../src/language-server/identityService.ts | 77 +++---------------- .../profiles/sharedConfigProfileStore.test.ts | 16 +++- .../profiles/sharedConfigProfileStore.ts | 2 + 5 files changed, 31 insertions(+), 84 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/identityServer.ts b/server/aws-lsp-identity/src/language-server/identityServer.ts index ade57172aa..9b44d1e53a 100644 --- a/server/aws-lsp-identity/src/language-server/identityServer.ts +++ b/server/aws-lsp-identity/src/language-server/identityServer.ts @@ -41,8 +41,7 @@ export class IdentityServer extends ServerBase { const showMessageRequest: ShowMessageRequest = (params: ShowMessageRequestParams) => this.features.lsp.window.showMessageRequest(params) const showProgress: ShowProgress = this.features.lsp.sendProgress - const sendGetMfaCode: SendGetMfaCode = (params: GetMfaCodeParams) => - this.features.identityManagement.sendGetMfaCode(params) + const sendGetMfaCode: SendGetMfaCode = this.features.identityManagement.sendGetMfaCode // Initialize dependencies const profileStore = new SharedConfigProfileStore(this.observability) @@ -61,7 +60,8 @@ export class IdentityServer extends ServerBase { profileStore, ssoCache, autoRefresher, - { showUrl, showMessageRequest, showProgress }, + iamProvider, + { showUrl, showMessageRequest, showProgress, sendGetMfaCode }, this.getClientName(params), this.observability ) diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index ca205bb360..a007aead95 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -16,6 +16,7 @@ import { Logging, Telemetry } from '@aws/language-server-runtimes/server-interfa import { Observability } from '@aws/lsp-core' import { STSClient } from '@aws-sdk/client-sts' import { IAMClient } from '@aws-sdk/client-iam' +import { IamProvider } from '../iam/iamProvider' // eslint-disable-next-line use(require('chai-as-promised')) @@ -328,19 +329,12 @@ describe('IdentityService', () => { }) describe('getIamCredential', () => { - it('Can login with access key and secret key.', async () => { - const actual = await sut.getIamCredential({ profileName: 'my-iam-profile' }, CancellationToken.None) - - expect(actual.credentials.accessKeyId).to.equal('my-access-key') - expect(actual.credentials.secretAccessKey).to.equal('my-secret-key') - }) - it('Can login with access key, secret key, and session token.', async () => { const actual = await sut.getIamCredential({ profileName: 'my-sts-profile' }, CancellationToken.None) - expect(actual.credentials.accessKeyId).to.equal('my-access-key') - expect(actual.credentials.secretAccessKey).to.equal('my-secret-key') - expect(actual.credentials.sessionToken).to.equal('my-session-token') + expect(actual.credential.credentials.accessKeyId).to.equal('my-access-key') + expect(actual.credential.credentials.secretAccessKey).to.equal('my-secret-key') + expect(actual.credential.credentials.sessionToken).to.equal('my-session-token') }) }) diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 44833c7e86..09198309a7 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -9,14 +9,12 @@ import { getIamCredentialOptionsDefaults, GetSsoTokenParams, GetSsoTokenResult, - IamCredentials, IamIdentityCenterSsoTokenSource, InvalidateSsoTokenParams, InvalidateSsoTokenResult, MetricEvent, SsoSession, SsoTokenSourceKind, - ProfileKind, } from '@aws/language-server-runtimes/server-interface' import { normalizeSettingList, ProfileStore } from './profiles/profileService' @@ -32,11 +30,10 @@ import { } from '../sso/utils' import { IamHandlers, simulatePermissions } from '../iam/utils' import { AwsError, Observability } from '@aws/lsp-core' -import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts' import { __ServiceException } from '@aws-sdk/client-sso-oidc/dist-types/models/SSOOIDCServiceException' import { deviceCodeFlow } from '../sso/deviceCode/deviceCodeFlow' import { SSOToken } from '@smithy/shared-ini-file-loader' -import { IAMClient, SimulatePrincipalPolicyCommand, SimulatePrincipalPolicyCommandOutput } from '@aws-sdk/client-iam' +import { IamProvider } from '../iam/iamProvider' type SsoTokenSource = IamIdentityCenterSsoTokenSource | AwsBuilderIdSsoTokenSource type AuthFlows = Record Promise> @@ -46,30 +43,14 @@ const flows: AuthFlows = { [AuthorizationFlowKind.DeviceCode]: deviceCodeFlow, [AuthorizationFlowKind.Pkce]: authorizationCodePkceFlow, } -const qPermissions = [ - 'q:StartConversation', - 'q:SendMessage', - 'q:GetConversation', - 'q:ListConversations', - 'q:UpdateConversation', - 'q:DeleteConversation', - 'q:PassRequest', - 'q:StartTroubleshootingAnalysis', - 'q:StartTroubleshootingResolutionExplanation', - 'q:GetTroubleshootingResults', - 'q:UpdateTroubleshootingCommandResult', - 'q:GetIdentityMetaData', - 'q:GenerateCodeFromCommands', - 'q:UsePlugin', - 'codewhisperer:GenerateRecommendations', -] export class IdentityService { constructor( private readonly profileStore: ProfileStore, private readonly ssoCache: SsoCache, private readonly autoRefresher: SsoTokenAutoRefresher, - private readonly handlers: SsoFlowParams['handlers'], + private readonly iamProvider: IamProvider, + private readonly handlers: Handlers, private readonly clientName: string, private readonly observability: Observability, private readonly authFlows: AuthFlows = flows @@ -176,9 +157,7 @@ export class IdentityService { const options = { ...getIamCredentialOptionsDefaults, ...params.options } token.onCancellationRequested(_ => { - if (options.callStsOnInvalidIamCredential) { - emitMetric('Cancelled', null) - } + emitMetric('Cancelled', null) }) // Get the profile with provided name @@ -189,33 +168,20 @@ export class IdentityService { throw new AwsError('Profile not found.', AwsErrorCodes.E_PROFILE_NOT_FOUND) } - let credentials: IamCredentials - // Get the credentials directly from the profile - if (profile.kinds.includes(ProfileKind.IamCredentialsProfile)) { - credentials = { - accessKeyId: profile.settings!.aws_access_key_id!, - secretAccessKey: profile.settings!.aws_secret_access_key!, - sessionToken: profile.settings!.aws_session_token!, - } - } else { - throw new AwsError('Credentials could not be found for profile', AwsErrorCodes.E_INVALID_PROFILE) - } + const credentials = await this.iamProvider.getCredential(profile, options.callStsOnInvalidIamCredential) // Validate permissions - if (options.validatePermissions) { - const response = await this.simulatePermissions(credentials, qPermissions, profile.settings?.region) + if (options.permissionSet.length > 0) { + const response = await simulatePermissions(credentials, options.permissionSet, profile.settings?.region) if (!response?.EvaluationResults?.every(result => result.EvalDecision === 'allowed')) { - throw new AwsError( - `User or assumed role has insufficient permissions.`, - AwsErrorCodes.E_INVALID_PROFILE - ) + throw new AwsError(`Credentials have insufficient permissions.`, AwsErrorCodes.E_INVALID_PROFILE) } } emitMetric('Succeeded') + return { - id: profile.name, - credentials: credentials, + credential: { id: params.profileName, credentials: credentials }, updateCredentialsParams: { data: credentials, encrypted: false }, } } catch (e) { @@ -335,27 +301,4 @@ export class IdentityService { return ssoSession } - - // Simulate permissions on the identity associated with the credentials - private async simulatePermissions( - credentials: IamCredentials, - permissions: string[], - region?: string - ): Promise { - // Convert the credentials into an identity - const stsClient = new STSClient({ region: region || 'us-east-1', credentials: credentials }) - const identity = await stsClient.send(new GetCallerIdentityCommand({})) - if (!identity.Arn) { - throw new AwsError('Caller identity ARN not found.', AwsErrorCodes.E_INVALID_PROFILE) - } - - // Simulate permissions on the identity - const iamClient = new IAMClient({ region: region || 'us-east-1', credentials: credentials }) - return await iamClient.send( - new SimulatePrincipalPolicyCommand({ - PolicySourceArn: identity.Arn, - ActionNames: permissions, - }) - ) - } } diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts index fa0e85bb72..758ef27aa2 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts @@ -99,7 +99,9 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: {}, + settings: { + region: undefined, + }, }, { kinds: [ProfileKind.SsoTokenProfile], @@ -190,7 +192,9 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: {}, + settings: { + region: undefined, + }, }, { kinds: [ProfileKind.SsoTokenProfile], @@ -286,7 +290,9 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: {}, + settings: { + region: undefined, + }, }, { kinds: ['SsoTokenProfile'], @@ -434,7 +440,9 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: {}, + settings: { + region: undefined, + }, }, ], ssoSessions: [ diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts index 0b612a46a0..a1cdbcca28 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts @@ -67,6 +67,8 @@ export class SharedConfigProfileStore implements ProfileStore { // If the profile does not match any profile type, mark it as an unknown profile if (profile.kinds.length === 0) { profile.kinds.push(ProfileKind.Unknown) + // Dummy field to avoid deleting profile when loading and saving 0 changes to the profile + profile.settings!['region'] = settings['region'] } result.profiles.push(profile) break From d9756d09d51093f413c03b0853bbe4e60adf346c Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Mon, 21 Jul 2025 10:06:08 -0400 Subject: [PATCH 13/37] fix: remove dummy settings from unknown profiles and wrap sendGetMfaCode in lambda --- .../src/language-server/identityServer.ts | 4 +++- .../src/language-server/identityService.ts | 2 +- .../profiles/sharedConfigProfileStore.test.ts | 16 ++++------------ .../profiles/sharedConfigProfileStore.ts | 2 -- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/identityServer.ts b/server/aws-lsp-identity/src/language-server/identityServer.ts index 9b44d1e53a..31a01577de 100644 --- a/server/aws-lsp-identity/src/language-server/identityServer.ts +++ b/server/aws-lsp-identity/src/language-server/identityServer.ts @@ -11,6 +11,7 @@ import { PartialInitializeResult, ShowMessageRequestParams, GetIamCredentialParams, + GetMfaCodeParams, } from '@aws/language-server-runtimes/server-interface' import { SharedConfigProfileStore } from './profiles/sharedConfigProfileStore' import { IdentityService } from './identityService' @@ -41,7 +42,8 @@ export class IdentityServer extends ServerBase { const showMessageRequest: ShowMessageRequest = (params: ShowMessageRequestParams) => this.features.lsp.window.showMessageRequest(params) const showProgress: ShowProgress = this.features.lsp.sendProgress - const sendGetMfaCode: SendGetMfaCode = this.features.identityManagement.sendGetMfaCode + const sendGetMfaCode: SendGetMfaCode = (params: GetMfaCodeParams) => + this.features.identityManagement.sendGetMfaCode(params) // Initialize dependencies const profileStore = new SharedConfigProfileStore(this.observability) diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 09198309a7..22eecb3ca5 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -181,7 +181,7 @@ export class IdentityService { emitMetric('Succeeded') return { - credential: { id: params.profileName, credentials: credentials }, + credential: { id: params.profileName, kinds: profile.kinds, credentials: credentials }, updateCredentialsParams: { data: credentials, encrypted: false }, } } catch (e) { diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts index 758ef27aa2..fa0e85bb72 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts @@ -99,9 +99,7 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: { - region: undefined, - }, + settings: {}, }, { kinds: [ProfileKind.SsoTokenProfile], @@ -192,9 +190,7 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: { - region: undefined, - }, + settings: {}, }, { kinds: [ProfileKind.SsoTokenProfile], @@ -290,9 +286,7 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: { - region: undefined, - }, + settings: {}, }, { kinds: ['SsoTokenProfile'], @@ -440,9 +434,7 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: { - region: undefined, - }, + settings: {}, }, ], ssoSessions: [ diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts index a1cdbcca28..0b612a46a0 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts @@ -67,8 +67,6 @@ export class SharedConfigProfileStore implements ProfileStore { // If the profile does not match any profile type, mark it as an unknown profile if (profile.kinds.length === 0) { profile.kinds.push(ProfileKind.Unknown) - // Dummy field to avoid deleting profile when loading and saving 0 changes to the profile - profile.settings!['region'] = settings['region'] } result.profiles.push(profile) break From fd14234ae195ba6f0cddfc404fd343c06fe76b4b Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Thu, 10 Jul 2025 16:55:16 -0400 Subject: [PATCH 14/37] feat(identity): add support for IAM profile kinds --- .../src/credentials/credentialsProvider.ts | 1 + package-lock.json | 1619 ++++++++++++++--- .../profiles/profileService.test.ts | 61 +- .../profiles/profileService.ts | 102 +- .../profiles/sharedConfigProfileStore.test.ts | 33 +- .../profiles/sharedConfigProfileStore.ts | 38 +- 6 files changed, 1464 insertions(+), 390 deletions(-) diff --git a/core/aws-lsp-core/src/credentials/credentialsProvider.ts b/core/aws-lsp-core/src/credentials/credentialsProvider.ts index a56be8e0ad..bea8a5cfcd 100644 --- a/core/aws-lsp-core/src/credentials/credentialsProvider.ts +++ b/core/aws-lsp-core/src/credentials/credentialsProvider.ts @@ -4,6 +4,7 @@ export interface IamCredentials { accessKeyId: string secretAccessKey: string sessionToken?: string + expiration?: Date } export interface BearerToken { diff --git a/package-lock.json b/package-lock.json index 36b3816e8f..5b2b09f113 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2351,44 +2351,31 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.839.0.tgz", - "integrity": "sha512-7zDInY+qltKxeG+9d/97nbs+FWINcAi5bChBrleUQkuQ/dA9pSP1URo/6JlVzD2Ejvksm+hVK6z3VUWZaIAVOw==", + "node_modules/@aws-sdk/client-iam": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-iam/-/client-iam-3.840.0.tgz", + "integrity": "sha512-+HWqpTwXQYhFzgwfjGFHfo+a0mRQwYq29BEYlgfcydo8UOApc1oxsVmEmnYh2nbukaefUkOaMDb1xORybsE6Lw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.839.0", - "@aws-sdk/credential-provider-node": "3.839.0", - "@aws-sdk/middleware-bucket-endpoint": "3.830.0", - "@aws-sdk/middleware-expect-continue": "3.821.0", - "@aws-sdk/middleware-flexible-checksums": "3.839.0", - "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-location-constraint": "3.821.0", - "@aws-sdk/middleware-logger": "3.821.0", - "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-sdk-s3": "3.839.0", - "@aws-sdk/middleware-ssec": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.839.0", - "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/signature-v4-multi-region": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.839.0", - "@aws-sdk/xml-builder": "3.821.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/credential-provider-node": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.840.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", - "@smithy/eventstream-serde-browser": "^4.0.4", - "@smithy/eventstream-serde-config-resolver": "^4.1.2", - "@smithy/eventstream-serde-node": "^4.0.4", "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-blob-browser": "^4.0.4", "@smithy/hash-node": "^4.0.4", - "@smithy/hash-stream-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", - "@smithy/md5-js": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.13", "@smithy/middleware-retry": "^4.1.14", @@ -2408,34 +2395,33 @@ "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", - "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.6", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.839.0.tgz", - "integrity": "sha512-AZABysUhbfcwXVlMo97/vwHgsfJNF81wypCAowpqAJkSjP2KrqsqHpb71/RoR2w8JGmEnBBXRD4wIxDhnmifWg==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/client-sso": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.840.0.tgz", + "integrity": "sha512-3Zp+FWN2hhmKdpS0Ragi5V2ZPsZNScE3jlbgoJjzjI/roHZqO+e3/+XFN4TlM0DsPKYJNp+1TAjmhxN6rOnfYA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.839.0", - "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-logger": "3.821.0", - "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.839.0", - "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.839.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.840.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", "@smithy/fetch-http-handler": "^5.0.4", @@ -2467,12 +2453,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.839.0.tgz", - "integrity": "sha512-KdwL5RaK7eUIlOpdOoZ5u+2t4X1rdX/MTZgz3IV/aBzjVUoGsp+uUnbyqXomLQSUitPHp72EE/NHDsvWW/IHvQ==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/core": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.840.0.tgz", + "integrity": "sha512-x3Zgb39tF1h2XpU+yA4OAAQlW6LVEfXNlSedSYJ7HGKXqA/E9h3rWQVpYfhXXVVsLdYXdNw5KBUkoAoruoZSZA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", + "@aws-sdk/types": "3.840.0", "@aws-sdk/xml-builder": "3.821.0", "@smithy/core": "^3.6.0", "@smithy/node-config-provider": "^4.1.3", @@ -2492,13 +2480,15 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.839.0.tgz", - "integrity": "sha512-cWTadewPPz1OvObZJB+olrgh8VwcgIVcT293ZUT9V0CMF0UU7QaPwJP7uNXcNxltTh+sk1yhjH4UlcnJigZZbA==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.840.0.tgz", + "integrity": "sha512-EzF6VcJK7XvQ/G15AVEfJzN2mNXU8fcVpXo4bRyr1S6t2q5zx6UPH/XjDbn18xyUmOq01t+r8gG+TmHEVo18fA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/types": "3.821.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2507,13 +2497,15 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.839.0.tgz", - "integrity": "sha512-fv0BZwrDhWDju4D1MCLT4I2aPjr0dVQ6P+MpqvcGNOA41Oa9UdRhYTV5iuy5NLXzIzoCmnS+XfSq5Kbsf6//xw==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.840.0.tgz", + "integrity": "sha512-wbnUiPGLVea6mXbUh04fu+VJmGkQvmToPeTYdHE8eRZq3NRDi3t3WltT+jArLBKD/4NppRpMjf2ju4coMCz91g==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/types": "3.821.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/node-http-handler": "^4.0.6", "@smithy/property-provider": "^4.0.4", @@ -2527,19 +2519,21 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.839.0.tgz", - "integrity": "sha512-GHm0hF4CiDxIDR7TauMaA6iI55uuSqRxMBcqTAHaTPm6+h1A+MS+ysQMxZ+Jvwtoy8WmfTIGrJVxSCw0sK2hvA==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.840.0.tgz", + "integrity": "sha512-7F290BsWydShHb+7InXd+IjJc3mlEIm9I0R57F/Pjl1xZB69MdkhVGCnuETWoBt4g53ktJd6NEjzm/iAhFXFmw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/credential-provider-env": "3.839.0", - "@aws-sdk/credential-provider-http": "3.839.0", - "@aws-sdk/credential-provider-process": "3.839.0", - "@aws-sdk/credential-provider-sso": "3.839.0", - "@aws-sdk/credential-provider-web-identity": "3.839.0", - "@aws-sdk/nested-clients": "3.839.0", - "@aws-sdk/types": "3.821.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/credential-provider-env": "3.840.0", + "@aws-sdk/credential-provider-http": "3.840.0", + "@aws-sdk/credential-provider-process": "3.840.0", + "@aws-sdk/credential-provider-sso": "3.840.0", + "@aws-sdk/credential-provider-web-identity": "3.840.0", + "@aws-sdk/nested-clients": "3.840.0", + "@aws-sdk/types": "3.840.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -2550,18 +2544,20 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.839.0.tgz", - "integrity": "sha512-7bR+U2h+ft0V8chyeu9Bh/pvau4ZkQMeRt5f0dAULoepZQ77QQVRP4H04yJPTg9DCtqbVULQ3uf5YOp1/08vQw==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.840.0.tgz", + "integrity": "sha512-KufP8JnxA31wxklLm63evUPSFApGcH8X86z3mv9SRbpCm5ycgWIGVCTXpTOdgq6rPZrwT9pftzv2/b4mV/9clg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.839.0", - "@aws-sdk/credential-provider-http": "3.839.0", - "@aws-sdk/credential-provider-ini": "3.839.0", - "@aws-sdk/credential-provider-process": "3.839.0", - "@aws-sdk/credential-provider-sso": "3.839.0", - "@aws-sdk/credential-provider-web-identity": "3.839.0", - "@aws-sdk/types": "3.821.0", + "@aws-sdk/credential-provider-env": "3.840.0", + "@aws-sdk/credential-provider-http": "3.840.0", + "@aws-sdk/credential-provider-ini": "3.840.0", + "@aws-sdk/credential-provider-process": "3.840.0", + "@aws-sdk/credential-provider-sso": "3.840.0", + "@aws-sdk/credential-provider-web-identity": "3.840.0", + "@aws-sdk/types": "3.840.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -2572,13 +2568,15 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.839.0.tgz", - "integrity": "sha512-qShpekjociUZ+isyQNa0P7jo+0q3N2+0eJDg8SGyP6K6hHTcGfiqxTDps+IKl6NreCPhZCBzyI9mWkP0xSDR6g==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.840.0.tgz", + "integrity": "sha512-HkDQWHy8tCI4A0Ps2NVtuVYMv9cB4y/IuD/TdOsqeRIAT12h8jDb98BwQPNLAImAOwOWzZJ8Cu0xtSpX7CQhMw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/types": "3.821.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", @@ -2588,15 +2586,17 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.839.0.tgz", - "integrity": "sha512-w10zBLHhU8SBQcdrSPMI02haLoRGZg+gP7mH/Er8VhIXfHefbr7o4NirmB0hwdw/YAH8MLlC9jj7c2SJlsNhYA==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.840.0.tgz", + "integrity": "sha512-2qgdtdd6R0Z1y0KL8gzzwFUGmhBHSUx4zy85L2XV1CXhpRNwV71SVWJqLDVV5RVWVf9mg50Pm3AWrUC0xb0pcA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.839.0", - "@aws-sdk/core": "3.839.0", - "@aws-sdk/token-providers": "3.839.0", - "@aws-sdk/types": "3.821.0", + "@aws-sdk/client-sso": "3.840.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/token-providers": "3.840.0", + "@aws-sdk/types": "3.840.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", @@ -2606,14 +2606,16 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.839.0.tgz", - "integrity": "sha512-EvqTc7J1kgmiuxknpCp1S60hyMQvmKxsI5uXzQtcogl/N55rxiXEqnCLI5q6p33q91PJegrcMCM5Q17Afhm5qA==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.840.0.tgz", + "integrity": "sha512-dpEeVXG8uNZSmVXReE4WP0lwoioX2gstk4RnUgrdUE3YaPq8A+hJiVAyc3h+cjDeIqfbsQbZm9qFetKC2LF9dQ==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/nested-clients": "3.839.0", - "@aws-sdk/types": "3.821.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/nested-clients": "3.840.0", + "@aws-sdk/types": "3.840.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2622,12 +2624,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", - "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", + "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", + "@aws-sdk/types": "3.840.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2636,12 +2640,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", - "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-logger": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", + "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", + "@aws-sdk/types": "3.840.0", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -2649,12 +2655,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", - "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", + "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", + "@aws-sdk/types": "3.840.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2663,14 +2671,16 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.839.0.tgz", - "integrity": "sha512-2u74uRM1JWq6Sf7+3YpjejPM9YkomGt4kWhrmooIBEq1k5r2GTbkH7pNCxBQwBueXM21jAGVDxxeClpTx+5hig==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.840.0.tgz", + "integrity": "sha512-hiiMf7BP5ZkAFAvWRcK67Mw/g55ar7OCrvrynC92hunx/xhMkrgSLM0EXIZ1oTn3uql9kH/qqGF0nqsK6K555A==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", "@smithy/core": "^3.6.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", @@ -2680,23 +2690,25 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/nested-clients": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.839.0.tgz", - "integrity": "sha512-Glic0pg2THYP3aRhJORwJJBe1JLtJoEdWV/MFZNyzCklfMwEzpWtZAyxy+tQyFmMeW50uBAnh2R0jhMMcf257w==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/nested-clients": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.840.0.tgz", + "integrity": "sha512-LXYYo9+n4hRqnRSIMXLBb+BLz+cEmjMtTudwK1BF6Bn2RfdDv29KuyeDRrPCS3TwKl7ZKmXUmE9n5UuHAPfBpA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.839.0", - "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-logger": "3.821.0", - "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.839.0", - "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.839.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.840.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", "@smithy/fetch-http-handler": "^5.0.4", @@ -2728,12 +2740,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", - "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", + "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", + "@aws-sdk/types": "3.840.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", @@ -2744,24 +2758,77 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", - "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/token-providers": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.840.0.tgz", + "integrity": "sha512-6BuTOLTXvmgwjK7ve7aTg9JaWFdM5UoMolLVPMyh3wTv9Ufalh8oklxYHUBIxsKkBGO2WiHXytveuxH6tAgTYg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.821.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/nested-clients": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/types": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", + "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-endpoints": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.840.0.tgz", + "integrity": "sha512-eqE9ROdg/Kk0rj3poutyRCFauPDXIf/WSvCqFiRDDVi6QOnCv/M0g2XW8/jSvkJlOyaXkNCptapIp6BeeFFGYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", + "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.839.0.tgz", - "integrity": "sha512-MuunkIG1bJVMtTH7MbjXOrhHleU5wjHz5eCAUc6vj7M9rwol71nqjj9b8RLnkO5gsJcKc29Qk8iV6xQuzKWNMw==", + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.840.0.tgz", + "integrity": "sha512-Fy5JUEDQU1tPm2Yw/YqRYYc27W5+QD/J4mYvQvdWjUGZLB5q3eLFMGD35Uc28ZFoGMufPr4OCxK/bRfWROBRHQ==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.839.0", - "@aws-sdk/types": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/types": "3.840.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2778,10 +2845,12 @@ } } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/abort-controller": { + "node_modules/@aws-sdk/client-iam/node_modules/@smithy/abort-controller": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2790,10 +2859,12 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/node-http-handler": { + "node_modules/@aws-sdk/client-iam/node_modules/@smithy/node-http-handler": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^4.0.4", "@smithy/protocol-http": "^5.1.2", @@ -2805,10 +2876,12 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-iam/node_modules/@smithy/types": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -2816,89 +2889,44 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.731.0.tgz", - "integrity": "sha512-O4C/UYGgqMsBg21MMApFdgyh8BX568hQhbdoNFmRVTBoSnCZ3w+H4a1wBPX4Gyl0NX+ab6Xxo9rId8HiyPXJ0A==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.731.0", - "@aws-sdk/middleware-host-header": "3.731.0", - "@aws-sdk/middleware-logger": "3.731.0", - "@aws-sdk/middleware-recursion-detection": "3.731.0", - "@aws-sdk/middleware-user-agent": "3.731.0", - "@aws-sdk/region-config-resolver": "3.731.0", - "@aws-sdk/types": "3.731.0", - "@aws-sdk/util-endpoints": "3.731.0", - "@aws-sdk/util-user-agent-browser": "3.731.0", - "@aws-sdk/util-user-agent-node": "3.731.0", - "@smithy/config-resolver": "^4.0.0", - "@smithy/core": "^3.0.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/hash-node": "^4.0.0", - "@smithy/invalid-dependency": "^4.0.0", - "@smithy/middleware-content-length": "^4.0.0", - "@smithy/middleware-endpoint": "^4.0.0", - "@smithy/middleware-retry": "^4.0.0", - "@smithy/middleware-serde": "^4.0.0", - "@smithy/middleware-stack": "^4.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/url-parser": "^4.0.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.0", - "@smithy/util-defaults-mode-node": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "@smithy/util-middleware": "^4.0.0", - "@smithy/util-retry": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc": { + "node_modules/@aws-sdk/client-s3": { "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.839.0.tgz", - "integrity": "sha512-7QnMApYfQBT441YkxObxt1hZ8TdqZH7h0NdYsvbLdEqGROXBDDT+Wq7ZVfsnKjuVUGQ/t75bIqFn7M8cdyESfA==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.839.0.tgz", + "integrity": "sha512-7zDInY+qltKxeG+9d/97nbs+FWINcAi5bChBrleUQkuQ/dA9pSP1URo/6JlVzD2Ejvksm+hVK6z3VUWZaIAVOw==", "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.839.0", "@aws-sdk/credential-provider-node": "3.839.0", + "@aws-sdk/middleware-bucket-endpoint": "3.830.0", + "@aws-sdk/middleware-expect-continue": "3.821.0", + "@aws-sdk/middleware-flexible-checksums": "3.839.0", "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-location-constraint": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-sdk-s3": "3.839.0", + "@aws-sdk/middleware-ssec": "3.821.0", "@aws-sdk/middleware-user-agent": "3.839.0", "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/signature-v4-multi-region": "3.839.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", "@aws-sdk/util-user-agent-node": "3.839.0", + "@aws-sdk/xml-builder": "3.821.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", + "@smithy/eventstream-serde-browser": "^4.0.4", + "@smithy/eventstream-serde-config-resolver": "^4.1.2", + "@smithy/eventstream-serde-node": "^4.0.4", "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-blob-browser": "^4.0.4", "@smithy/hash-node": "^4.0.4", + "@smithy/hash-stream-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", + "@smithy/md5-js": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.13", "@smithy/middleware-retry": "^4.1.14", @@ -2918,14 +2946,18 @@ "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", + "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@smithy/util-waiter": "^4.0.6", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.839.0.tgz", "integrity": "sha512-AZABysUhbfcwXVlMo97/vwHgsfJNF81wypCAowpqAJkSjP2KrqsqHpb71/RoR2w8JGmEnBBXRD4wIxDhnmifWg==", @@ -2973,7 +3005,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/core": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.839.0.tgz", "integrity": "sha512-KdwL5RaK7eUIlOpdOoZ5u+2t4X1rdX/MTZgz3IV/aBzjVUoGsp+uUnbyqXomLQSUitPHp72EE/NHDsvWW/IHvQ==", @@ -2998,7 +3030,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-env": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.839.0.tgz", "integrity": "sha512-cWTadewPPz1OvObZJB+olrgh8VwcgIVcT293ZUT9V0CMF0UU7QaPwJP7uNXcNxltTh+sk1yhjH4UlcnJigZZbA==", @@ -3013,7 +3045,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-http": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.839.0.tgz", "integrity": "sha512-fv0BZwrDhWDju4D1MCLT4I2aPjr0dVQ6P+MpqvcGNOA41Oa9UdRhYTV5iuy5NLXzIzoCmnS+XfSq5Kbsf6//xw==", @@ -3033,7 +3065,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-ini": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.839.0.tgz", "integrity": "sha512-GHm0hF4CiDxIDR7TauMaA6iI55uuSqRxMBcqTAHaTPm6+h1A+MS+ysQMxZ+Jvwtoy8WmfTIGrJVxSCw0sK2hvA==", @@ -3056,7 +3088,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-node": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.839.0.tgz", "integrity": "sha512-7bR+U2h+ft0V8chyeu9Bh/pvau4ZkQMeRt5f0dAULoepZQ77QQVRP4H04yJPTg9DCtqbVULQ3uf5YOp1/08vQw==", @@ -3078,7 +3110,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-process": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.839.0.tgz", "integrity": "sha512-qShpekjociUZ+isyQNa0P7jo+0q3N2+0eJDg8SGyP6K6hHTcGfiqxTDps+IKl6NreCPhZCBzyI9mWkP0xSDR6g==", @@ -3094,7 +3126,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-sso": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.839.0.tgz", "integrity": "sha512-w10zBLHhU8SBQcdrSPMI02haLoRGZg+gP7mH/Er8VhIXfHefbr7o4NirmB0hwdw/YAH8MLlC9jj7c2SJlsNhYA==", @@ -3112,7 +3144,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-web-identity": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.839.0.tgz", "integrity": "sha512-EvqTc7J1kgmiuxknpCp1S60hyMQvmKxsI5uXzQtcogl/N55rxiXEqnCLI5q6p33q91PJegrcMCM5Q17Afhm5qA==", @@ -3128,7 +3160,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-host-header": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", @@ -3142,7 +3174,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-logger": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", @@ -3155,7 +3187,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-recursion-detection": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", @@ -3169,7 +3201,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-user-agent": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.839.0.tgz", "integrity": "sha512-2u74uRM1JWq6Sf7+3YpjejPM9YkomGt4kWhrmooIBEq1k5r2GTbkH7pNCxBQwBueXM21jAGVDxxeClpTx+5hig==", @@ -3186,7 +3218,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/nested-clients": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/nested-clients": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.839.0.tgz", "integrity": "sha512-Glic0pg2THYP3aRhJORwJJBe1JLtJoEdWV/MFZNyzCklfMwEzpWtZAyxy+tQyFmMeW50uBAnh2R0jhMMcf257w==", @@ -3234,7 +3266,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/region-config-resolver": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", @@ -3250,7 +3282,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-browser": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", @@ -3261,7 +3293,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-node": { + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.839.0.tgz", "integrity": "sha512-MuunkIG1bJVMtTH7MbjXOrhHleU5wjHz5eCAUc6vj7M9rwol71nqjj9b8RLnkO5gsJcKc29Qk8iV6xQuzKWNMw==", @@ -3284,7 +3316,7 @@ } } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/abort-controller": { + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/abort-controller": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", @@ -3296,7 +3328,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-http-handler": { + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/node-http-handler": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", @@ -3311,7 +3343,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/types": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", @@ -3322,63 +3354,1106 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { + "node_modules/@aws-sdk/client-s3/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@aws-sdk/client-sso": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.731.0.tgz", - "integrity": "sha512-NrdkJg6oOUbXR2r9WvHP408CLyvST8cJfp1/jP9pemtjvjPoh6NukbCtiSFdOOb1eryP02CnqQWItfJC1p2Y/Q==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.731.0.tgz", + "integrity": "sha512-O4C/UYGgqMsBg21MMApFdgyh8BX568hQhbdoNFmRVTBoSnCZ3w+H4a1wBPX4Gyl0NX+ab6Xxo9rId8HiyPXJ0A==", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.731.0", + "@aws-sdk/middleware-host-header": "3.731.0", + "@aws-sdk/middleware-logger": "3.731.0", + "@aws-sdk/middleware-recursion-detection": "3.731.0", + "@aws-sdk/middleware-user-agent": "3.731.0", + "@aws-sdk/region-config-resolver": "3.731.0", + "@aws-sdk/types": "3.731.0", + "@aws-sdk/util-endpoints": "3.731.0", + "@aws-sdk/util-user-agent-browser": "3.731.0", + "@aws-sdk/util-user-agent-node": "3.731.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { - "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.731.0.tgz", - "integrity": "sha512-riztxTAfncFS9yQWcBJffGgOgLoKSa63ph+rxWJxKl6BHAmWEvHICj1qDcVmnWfIcvJ5cClclY75l9qKaUH7rQ==", + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.839.0.tgz", + "integrity": "sha512-7QnMApYfQBT441YkxObxt1hZ8TdqZH7h0NdYsvbLdEqGROXBDDT+Wq7ZVfsnKjuVUGQ/t75bIqFn7M8cdyESfA==", "dependencies": { - "@aws-sdk/types": "3.731.0", - "@smithy/types": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/credential-provider-node": "3.839.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.839.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sso": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.839.0.tgz", + "integrity": "sha512-AZABysUhbfcwXVlMo97/vwHgsfJNF81wypCAowpqAJkSjP2KrqsqHpb71/RoR2w8JGmEnBBXRD4wIxDhnmifWg==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.839.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/core": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.839.0.tgz", + "integrity": "sha512-KdwL5RaK7eUIlOpdOoZ5u+2t4X1rdX/MTZgz3IV/aBzjVUoGsp+uUnbyqXomLQSUitPHp72EE/NHDsvWW/IHvQ==", "dependencies": { + "@aws-sdk/types": "3.821.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.6.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-http-handler": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", - "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.839.0.tgz", + "integrity": "sha512-cWTadewPPz1OvObZJB+olrgh8VwcgIVcT293ZUT9V0CMF0UU7QaPwJP7uNXcNxltTh+sk1yhjH4UlcnJigZZbA==", "dependencies": { - "@smithy/abort-controller": "^4.0.4", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.839.0.tgz", + "integrity": "sha512-fv0BZwrDhWDju4D1MCLT4I2aPjr0dVQ6P+MpqvcGNOA41Oa9UdRhYTV5iuy5NLXzIzoCmnS+XfSq5Kbsf6//xw==", + "dependencies": { + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", + "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.839.0.tgz", + "integrity": "sha512-GHm0hF4CiDxIDR7TauMaA6iI55uuSqRxMBcqTAHaTPm6+h1A+MS+ysQMxZ+Jvwtoy8WmfTIGrJVxSCw0sK2hvA==", + "dependencies": { + "@aws-sdk/core": "3.839.0", + "@aws-sdk/credential-provider-env": "3.839.0", + "@aws-sdk/credential-provider-http": "3.839.0", + "@aws-sdk/credential-provider-process": "3.839.0", + "@aws-sdk/credential-provider-sso": "3.839.0", + "@aws-sdk/credential-provider-web-identity": "3.839.0", + "@aws-sdk/nested-clients": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.839.0.tgz", + "integrity": "sha512-7bR+U2h+ft0V8chyeu9Bh/pvau4ZkQMeRt5f0dAULoepZQ77QQVRP4H04yJPTg9DCtqbVULQ3uf5YOp1/08vQw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.839.0", + "@aws-sdk/credential-provider-http": "3.839.0", + "@aws-sdk/credential-provider-ini": "3.839.0", + "@aws-sdk/credential-provider-process": "3.839.0", + "@aws-sdk/credential-provider-sso": "3.839.0", + "@aws-sdk/credential-provider-web-identity": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.839.0.tgz", + "integrity": "sha512-qShpekjociUZ+isyQNa0P7jo+0q3N2+0eJDg8SGyP6K6hHTcGfiqxTDps+IKl6NreCPhZCBzyI9mWkP0xSDR6g==", + "dependencies": { + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.839.0.tgz", + "integrity": "sha512-w10zBLHhU8SBQcdrSPMI02haLoRGZg+gP7mH/Er8VhIXfHefbr7o4NirmB0hwdw/YAH8MLlC9jj7c2SJlsNhYA==", + "dependencies": { + "@aws-sdk/client-sso": "3.839.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/token-providers": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.839.0.tgz", + "integrity": "sha512-EvqTc7J1kgmiuxknpCp1S60hyMQvmKxsI5uXzQtcogl/N55rxiXEqnCLI5q6p33q91PJegrcMCM5Q17Afhm5qA==", + "dependencies": { + "@aws-sdk/core": "3.839.0", + "@aws-sdk/nested-clients": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", + "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-logger": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", + "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", + "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.839.0.tgz", + "integrity": "sha512-2u74uRM1JWq6Sf7+3YpjejPM9YkomGt4kWhrmooIBEq1k5r2GTbkH7pNCxBQwBueXM21jAGVDxxeClpTx+5hig==", + "dependencies": { + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@smithy/core": "^3.6.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/nested-clients": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.839.0.tgz", + "integrity": "sha512-Glic0pg2THYP3aRhJORwJJBe1JLtJoEdWV/MFZNyzCklfMwEzpWtZAyxy+tQyFmMeW50uBAnh2R0jhMMcf257w==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.839.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", + "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", + "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.839.0.tgz", + "integrity": "sha512-MuunkIG1bJVMtTH7MbjXOrhHleU5wjHz5eCAUc6vj7M9rwol71nqjj9b8RLnkO5gsJcKc29Qk8iV6xQuzKWNMw==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { + "version": "3.731.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.731.0.tgz", + "integrity": "sha512-NrdkJg6oOUbXR2r9WvHP408CLyvST8cJfp1/jP9pemtjvjPoh6NukbCtiSFdOOb1eryP02CnqQWItfJC1p2Y/Q==", + "dependencies": { + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { + "version": "3.731.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.731.0.tgz", + "integrity": "sha512-riztxTAfncFS9yQWcBJffGgOgLoKSa63ph+rxWJxKl6BHAmWEvHICj1qDcVmnWfIcvJ5cClclY75l9qKaUH7rQ==", + "dependencies": { + "@aws-sdk/types": "3.731.0", + "@smithy/types": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.840.0.tgz", + "integrity": "sha512-h+mu89Wk81Ne+B624GT/pBM5VjuAZueSeQNixhgtQ1QHi6bZzrpz8+lvMSibKO+kXFyQsTLzkyibbxnhLpWQZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/credential-provider-node": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.840.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/client-sso": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.840.0.tgz", + "integrity": "sha512-3Zp+FWN2hhmKdpS0Ragi5V2ZPsZNScE3jlbgoJjzjI/roHZqO+e3/+XFN4TlM0DsPKYJNp+1TAjmhxN6rOnfYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.840.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/core": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.840.0.tgz", + "integrity": "sha512-x3Zgb39tF1h2XpU+yA4OAAQlW6LVEfXNlSedSYJ7HGKXqA/E9h3rWQVpYfhXXVVsLdYXdNw5KBUkoAoruoZSZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.6.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.840.0.tgz", + "integrity": "sha512-EzF6VcJK7XvQ/G15AVEfJzN2mNXU8fcVpXo4bRyr1S6t2q5zx6UPH/XjDbn18xyUmOq01t+r8gG+TmHEVo18fA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.840.0.tgz", + "integrity": "sha512-wbnUiPGLVea6mXbUh04fu+VJmGkQvmToPeTYdHE8eRZq3NRDi3t3WltT+jArLBKD/4NppRpMjf2ju4coMCz91g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.840.0.tgz", + "integrity": "sha512-7F290BsWydShHb+7InXd+IjJc3mlEIm9I0R57F/Pjl1xZB69MdkhVGCnuETWoBt4g53ktJd6NEjzm/iAhFXFmw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/credential-provider-env": "3.840.0", + "@aws-sdk/credential-provider-http": "3.840.0", + "@aws-sdk/credential-provider-process": "3.840.0", + "@aws-sdk/credential-provider-sso": "3.840.0", + "@aws-sdk/credential-provider-web-identity": "3.840.0", + "@aws-sdk/nested-clients": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.840.0.tgz", + "integrity": "sha512-KufP8JnxA31wxklLm63evUPSFApGcH8X86z3mv9SRbpCm5ycgWIGVCTXpTOdgq6rPZrwT9pftzv2/b4mV/9clg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.840.0", + "@aws-sdk/credential-provider-http": "3.840.0", + "@aws-sdk/credential-provider-ini": "3.840.0", + "@aws-sdk/credential-provider-process": "3.840.0", + "@aws-sdk/credential-provider-sso": "3.840.0", + "@aws-sdk/credential-provider-web-identity": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.840.0.tgz", + "integrity": "sha512-HkDQWHy8tCI4A0Ps2NVtuVYMv9cB4y/IuD/TdOsqeRIAT12h8jDb98BwQPNLAImAOwOWzZJ8Cu0xtSpX7CQhMw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.840.0.tgz", + "integrity": "sha512-2qgdtdd6R0Z1y0KL8gzzwFUGmhBHSUx4zy85L2XV1CXhpRNwV71SVWJqLDVV5RVWVf9mg50Pm3AWrUC0xb0pcA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.840.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/token-providers": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.840.0.tgz", + "integrity": "sha512-dpEeVXG8uNZSmVXReE4WP0lwoioX2gstk4RnUgrdUE3YaPq8A+hJiVAyc3h+cjDeIqfbsQbZm9qFetKC2LF9dQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/nested-clients": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", + "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-logger": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", + "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", + "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.840.0.tgz", + "integrity": "sha512-hiiMf7BP5ZkAFAvWRcK67Mw/g55ar7OCrvrynC92hunx/xhMkrgSLM0EXIZ1oTn3uql9kH/qqGF0nqsK6K555A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@smithy/core": "^3.6.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/nested-clients": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.840.0.tgz", + "integrity": "sha512-LXYYo9+n4hRqnRSIMXLBb+BLz+cEmjMtTudwK1BF6Bn2RfdDv29KuyeDRrPCS3TwKl7ZKmXUmE9n5UuHAPfBpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.840.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", + "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/token-providers": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.840.0.tgz", + "integrity": "sha512-6BuTOLTXvmgwjK7ve7aTg9JaWFdM5UoMolLVPMyh3wTv9Ufalh8oklxYHUBIxsKkBGO2WiHXytveuxH6tAgTYg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.840.0", + "@aws-sdk/nested-clients": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/types": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", + "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-endpoints": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.840.0.tgz", + "integrity": "sha512-eqE9ROdg/Kk0rj3poutyRCFauPDXIf/WSvCqFiRDDVi6QOnCv/M0g2XW8/jSvkJlOyaXkNCptapIp6BeeFFGYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", + "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.840.0.tgz", + "integrity": "sha512-Fy5JUEDQU1tPm2Yw/YqRYYc27W5+QD/J4mYvQvdWjUGZLB5q3eLFMGD35Uc28ZFoGMufPr4OCxK/bRfWROBRHQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts index 216f3010af..bf928a1119 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts @@ -54,7 +54,7 @@ describe('ProfileService', async () => { } profile4 = { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'profile4', settings: { aws_access_key_id: 'access-key', @@ -259,9 +259,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Sso-session name required on profile.') }) - it('updateProfile throws on missing access key for IamCredentialsProfile', async () => { + it('updateProfile throws on missing access key for IAM user profile', async () => { const profile = { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'profile-name', settings: { aws_secret_access_key: 'secret-key', @@ -271,9 +271,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Access key required on profile.') }) - it('updateProfile throws on missing secret key for IamCredentialsProfile', async () => { + it('updateProfile throws on missing secret key for IAM user profile', async () => { const profile = { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'profile-name', settings: { aws_access_key_id: 'access-key', @@ -283,9 +283,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Secret key required on profile.') }) - it('updateProfile throws on missing role ARN for IamSourceProfileProfile', async () => { + it('updateProfile throws on missing role ARN for role source profile', async () => { const profile = { - kinds: [ProfileKind.IamSourceProfileProfile], + kinds: [ProfileKind.RoleSourceProfile], name: 'profile-name', settings: { source_profile: 'source', @@ -295,9 +295,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Role ARN required on profile.') }) - it('updateProfile throws on missing source profile for IamSourceProfileProfile', async () => { + it('updateProfile throws on missing source profile for role source profile', async () => { const profile = { - kinds: [ProfileKind.IamSourceProfileProfile], + kinds: [ProfileKind.RoleSourceProfile], name: 'profile-name', settings: { role_arn: 'role-arn', @@ -307,9 +307,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Source profile required on profile.') }) - it('updateProfile throws on missing role ARN for IamCredentialSourceProfile', async () => { + it('updateProfile throws on missing role ARN for role instance profile', async () => { const profile = { - kinds: [ProfileKind.IamCredentialSourceProfile], + kinds: [ProfileKind.RoleInstanceProfile], name: 'profile-name', settings: { credential_source: 'Ec2InstanceMetadata', @@ -320,9 +320,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Role ARN required on profile.') }) - it('updateProfile throws on missing credential source for IamCredentialSourceProfile', async () => { + it('updateProfile throws on missing credential source for role instance profile', async () => { const profile = { - kinds: [ProfileKind.IamCredentialSourceProfile], + kinds: [ProfileKind.RoleInstanceProfile], name: 'profile-name', settings: { role_arn: 'role-arn', @@ -338,9 +338,22 @@ describe('ProfileService', async () => { ) }) + it('updateProfile throws on missing region for role instance profile', async () => { + const profile = { + kinds: [ProfileKind.RoleInstanceProfile], + name: 'profile-name', + settings: { + role_arn: 'role-arn', + credential_source: 'Ec2InstanceMetadata', + }, + } + + await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Region required on profile.') + }) + it('updateProfile throws on missing credential process for process profile', async () => { const profile = { - kinds: [ProfileKind.IamCredentialProcessProfile], + kinds: [ProfileKind.ProcessProfile], name: 'profile-name', settings: {}, } @@ -573,7 +586,7 @@ describe('profileService.DuckTypers', () => { } }) - it('profileDuckTypers.IamCredentialsProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.IamUserProfile.eval returns true on valid profiles', () => { const profiles = [ { aws_access_key_id: 'access-key', @@ -587,12 +600,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamCredentialsProfile.eval(profile) + const actual = profileDuckTypers.IamUserProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.IamCredentialsProfile.eval returns false on invalid profiles', () => { + it('profileDuckTypers.IamUserProfile.eval returns false on invalid profiles', () => { const profiles = [ { sso_session: 'my-sso-session', @@ -604,12 +617,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamCredentialsProfile.eval(profile as object) + const actual = profileDuckTypers.IamUserProfile.eval(profile as object) expect(actual).to.be.false } }) - it('profileDuckTypers.IamSourceProfileProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.RoleSourceProfile.eval returns true on valid profiles', () => { const profiles = [ { role_arn: 'role-arn', @@ -624,12 +637,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamSourceProfileProfile.eval(profile) + const actual = profileDuckTypers.RoleSourceProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.IamCredentialSourceProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.RoleInstanceProfile.eval returns true on valid profiles', () => { const profiles = [ { role_arn: 'role-arn', @@ -645,12 +658,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamCredentialSourceProfile.eval(profile) + const actual = profileDuckTypers.RoleInstanceProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.IamCredentialProcessProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.ProcessProfile.eval returns true on valid profiles', () => { const profiles = [ { credential_process: 'credential-process', @@ -664,7 +677,7 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamCredentialProcessProfile.eval(profile) + const actual = profileDuckTypers.ProcessProfile.eval(profile) expect(actual).to.be.true } }) diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts index a37e27e30e..68ed446f04 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts @@ -39,9 +39,6 @@ export const ProfileFields = { credential_source: 'credential_source', source_profile: 'source_profile', mfa_serial: 'mfa_serial', - external_id: 'external_id', - credential_cache: 'credential_cache', - credential_cache_location: 'credential_cache_location', } as const export const SsoSessionFields = { @@ -50,70 +47,30 @@ export const SsoSessionFields = { sso_start_url: 'sso_start_url', } as const -export const profileTypes = { - SsoTokenProfile: { - kind: ProfileKind.SsoTokenProfile, - required: [ProfileFields.sso_session], - optional: [ProfileFields.region], - disallowed: [ProfileFields.sso_account_id, ProfileFields.sso_role_name], - }, - IamCredentialsProfile: { - kind: ProfileKind.IamCredentialsProfile, - required: [ProfileFields.aws_access_key_id, ProfileFields.aws_secret_access_key], - optional: [ProfileFields.aws_session_token], - disallowed: [], - }, - IamSourceProfileProfile: { - kind: ProfileKind.IamSourceProfileProfile, - required: [ProfileFields.role_arn, ProfileFields.source_profile], - optional: [ - ProfileFields.external_id, - ProfileFields.role_session_name, - ProfileFields.region, - ProfileFields.mfa_serial, - ProfileFields.credential_cache, - ProfileFields.credential_cache_location, - ], - disallowed: [ProfileFields.credential_source], - }, - IamCredentialSourceProfile: { - kind: ProfileKind.IamCredentialSourceProfile, - required: [ProfileFields.role_arn, ProfileFields.credential_source], - optional: [ - ProfileFields.external_id, - ProfileFields.role_session_name, - ProfileFields.region, - ProfileFields.credential_cache, - ProfileFields.credential_cache_location, - ], - disallowed: [ProfileFields.source_profile], - }, - IamCredentialProcessProfile: { - kind: ProfileKind.IamCredentialProcessProfile, - required: [ProfileFields.credential_process], - optional: [], - disallowed: [], - }, -} as const - -export const profileDuckTypers = Object.fromEntries( - Object.entries(profileTypes).map(([key, def]) => [ - key, - (() => { - const typer = new DuckTyper() - for (const field of def.required) { - typer.requireProperty(field) - } - for (const field of def.optional) { - typer.optionalProperty(field) - } - for (const field of def.disallowed) { - typer.disallowProperty(field) - } - return typer - })(), - ]) -) +export const profileDuckTypers = { + SsoTokenProfile: new DuckTyper() + .requireProperty(ProfileFields.sso_session) + .disallowProperty(ProfileFields.sso_account_id) + .disallowProperty(ProfileFields.sso_role_name), + IamUserProfile: new DuckTyper() + .requireProperty(ProfileFields.aws_access_key_id) + .requireProperty(ProfileFields.aws_secret_access_key) + .optionalProperty(ProfileFields.aws_session_token), + RoleSourceProfile: new DuckTyper() + .requireProperty(ProfileFields.role_arn) + .requireProperty(ProfileFields.source_profile) + .optionalProperty(ProfileFields.role_session_name) + .optionalProperty(ProfileFields.mfa_serial) + .disallowProperty(ProfileFields.credential_source), + RoleInstanceProfile: new DuckTyper() + .requireProperty(ProfileFields.role_arn) + .requireProperty(ProfileFields.credential_source) + .requireProperty(ProfileFields.region) + .optionalProperty(ProfileFields.role_session_name) + .disallowProperty(ProfileFields.source_profile), + ProcessProfile: new DuckTyper().requireProperty(ProfileFields.credential_process), + Unknown: new DuckTyper(), +} export const ssoSessionDuckTyper = new DuckTyper() .requireProperty(SsoSessionFields.sso_start_url) @@ -163,6 +120,7 @@ export class ProfileService { this.throwOnInvalidProfile(!params.profile, 'Profile required.') const profile = params.profile! + // Removing this check for profile deletion this.throwOnInvalidProfile( !profile.kinds.some(kind => Object.values(ProfileKind).includes(kind)), 'Profile must be non-legacy sso-session or iam-credentials type.' @@ -182,7 +140,6 @@ export class ProfileService { throw new AwsError('Cannot create profile.', AwsErrorCodes.E_CANNOT_CREATE_PROFILE) } - // TODO: can this be refactored and simplified using the existing DuckTypers? // Validate SSO profile if (profile.kinds.includes(ProfileKind.SsoTokenProfile)) { this.throwOnInvalidProfile(!profileSettings.sso_session, 'Sso-session name required on profile.') @@ -219,22 +176,23 @@ export class ProfileService { } // Validate IAM profiles - if (profile.kinds.includes(ProfileKind.IamCredentialsProfile)) { + if (profile.kinds.includes(ProfileKind.IamUserProfile)) { this.throwOnInvalidProfile(!profileSettings.aws_access_key_id, 'Access key required on profile.') this.throwOnInvalidProfile(!profileSettings.aws_secret_access_key, 'Secret key required on profile.') } - if (profile.kinds.includes(ProfileKind.IamCredentialSourceProfile)) { + if (profile.kinds.includes(ProfileKind.RoleInstanceProfile)) { this.throwOnInvalidProfile(!profileSettings.role_arn, 'Role ARN required on profile.') + this.throwOnInvalidProfile(!profileSettings.region, 'Region required on profile.') this.throwOnInvalidProfile(!profileSettings.credential_source, 'Credential source required on profile.') } - if (profile.kinds.includes(ProfileKind.IamSourceProfileProfile)) { + if (profile.kinds.includes(ProfileKind.RoleSourceProfile)) { this.throwOnInvalidProfile(!profileSettings.role_arn, 'Role ARN required on profile.') this.throwOnInvalidProfile(!profileSettings.source_profile, 'Source profile required on profile.') } - if (profile.kinds.includes(ProfileKind.IamCredentialProcessProfile)) { + if (profile.kinds.includes(ProfileKind.ProcessProfile)) { this.throwOnInvalidProfile(!profileSettings.credential_process, 'Credential process required on profile.') } diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts index fa0e85bb72..003b306faa 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts @@ -89,11 +89,12 @@ describe('SharedConfigProfileStore', async () => { expect(actual).to.deep.equal({ profiles: [ { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', + aws_session_token: undefined, }, }, { @@ -180,11 +181,12 @@ describe('SharedConfigProfileStore', async () => { expect(after).to.deep.equal({ profiles: [ { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', + aws_session_token: undefined, }, }, { @@ -276,11 +278,12 @@ describe('SharedConfigProfileStore', async () => { expect(after).to.deep.equal({ profiles: [ { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', + aws_session_token: undefined, }, }, { @@ -316,7 +319,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'iam-user.profile', settings: { aws_access_key_id: 'new-access-key', @@ -325,7 +328,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamSourceProfileProfile], + kinds: [ProfileKind.RoleSourceProfile], name: 'role-source.profile', settings: { role_arn: 'new-role-arn', @@ -333,15 +336,16 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamCredentialSourceProfile], + kinds: [ProfileKind.RoleInstanceProfile], name: 'role-instance.profile', settings: { role_arn: 'new-role-arn', credential_source: 'new-source', + region: 'new-region', }, }, { - kinds: [ProfileKind.IamCredentialProcessProfile], + kinds: [ProfileKind.ProcessProfile], name: 'process.profile', settings: { credential_process: 'new-credential-process', @@ -392,15 +396,16 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', + aws_session_token: undefined, }, }, { - kinds: [ProfileKind.IamCredentialsProfile], + kinds: [ProfileKind.IamUserProfile], name: 'iam-user.profile', settings: { aws_access_key_id: 'new-access-key', @@ -409,26 +414,30 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamCredentialProcessProfile], + kinds: [ProfileKind.ProcessProfile], name: 'process.profile', settings: { credential_process: 'new-credential-process', }, }, { - kinds: [ProfileKind.IamCredentialSourceProfile], + kinds: [ProfileKind.RoleInstanceProfile], name: 'role-instance.profile', settings: { role_arn: 'new-role-arn', credential_source: 'new-source', + region: 'new-region', + role_session_name: undefined, }, }, { - kinds: [ProfileKind.IamSourceProfileProfile], + kinds: [ProfileKind.RoleSourceProfile], name: 'role-source.profile', settings: { role_arn: 'new-role-arn', source_profile: 'new-source-profile', + mfa_serial: undefined, + role_session_name: undefined, }, }, { diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts index 0b612a46a0..fb4f167d69 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts @@ -53,16 +53,34 @@ export class SharedConfigProfileStore implements ProfileStore { settings: {}, } // Add the kinds and settings for each matched profile type - for (const [profileType, fields] of Object.entries(profileTypes)) { - if (profileDuckTypers[profileType].eval(settings)) { - profile.kinds.push(fields.kind) - const relevantFields = [...fields.required, ...fields.optional] - for (const field of relevantFields) { - if (settings[field] !== undefined) { - profile.settings![field] = settings[field] - } - } - } + if (profileDuckTypers.SsoTokenProfile.eval(settings)) { + profile.kinds.push(ProfileKind.SsoTokenProfile) + profile.settings!.region = settings.region + profile.settings!.sso_session = settings.sso_session + } + if (profileDuckTypers.ProcessProfile.eval(settings)) { + profile.kinds.push(ProfileKind.ProcessProfile) + profile.settings!.credential_process = settings.credential_process + } + if (profileDuckTypers.RoleSourceProfile.eval(settings)) { + profile.kinds.push(ProfileKind.RoleSourceProfile) + profile.settings!.role_arn = settings.role_arn + profile.settings!.source_profile = settings.source_profile + profile.settings!.mfa_serial = settings.mfa_serial + profile.settings!.role_session_name = settings.role_session_name + } + if (profileDuckTypers.RoleInstanceProfile.eval(settings)) { + profile.kinds.push(ProfileKind.RoleInstanceProfile) + profile.settings!.role_arn = settings.role_arn + profile.settings!.region = settings.region + profile.settings!.credential_source = settings.credential_source + profile.settings!.role_session_name = settings.role_session_name + } + if (profileDuckTypers.IamUserProfile.eval(settings)) { + profile.kinds.push(ProfileKind.IamUserProfile) + profile.settings!.aws_access_key_id = settings.aws_access_key_id + profile.settings!.aws_secret_access_key = settings.aws_secret_access_key + profile.settings!.aws_session_token = settings.aws_session_token } // If the profile does not match any profile type, mark it as an unknown profile if (profile.kinds.length === 0) { From 1c93d34a42070840d1e7cb58785dccb8fb8000db Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Thu, 10 Jul 2025 17:49:38 -0400 Subject: [PATCH 15/37] feat(identity): add support for assumed role credentials --- .../src/language-server/identityServer.ts | 25 ++- .../language-server/identityService.test.ts | 121 +++++++++-- .../src/language-server/identityService.ts | 167 ++++++++++++++- .../src/sts/cache/fileSystemStsCache.test.ts | 190 ++++++++++++++++++ .../src/sts/cache/fileSystemStsCache.ts | 84 ++++++++ .../src/sts/cache/refreshingStsCache.test.ts | 74 +++++++ .../src/sts/cache/refreshingStsCache.ts | 58 ++++++ .../src/sts/cache/stsCache.ts | 12 ++ .../src/sts/stsAutoRefresher.test.ts | 137 +++++++++++++ .../src/sts/stsAutoRefresher.ts | 115 +++++++++++ 10 files changed, 958 insertions(+), 25 deletions(-) create mode 100644 server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts create mode 100644 server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts create mode 100644 server/aws-lsp-identity/src/sts/cache/refreshingStsCache.test.ts create mode 100644 server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts create mode 100644 server/aws-lsp-identity/src/sts/cache/stsCache.ts create mode 100644 server/aws-lsp-identity/src/sts/stsAutoRefresher.test.ts create mode 100644 server/aws-lsp-identity/src/sts/stsAutoRefresher.ts diff --git a/server/aws-lsp-identity/src/language-server/identityServer.ts b/server/aws-lsp-identity/src/language-server/identityServer.ts index 31a01577de..7c7b761fb4 100644 --- a/server/aws-lsp-identity/src/language-server/identityServer.ts +++ b/server/aws-lsp-identity/src/language-server/identityServer.ts @@ -7,6 +7,7 @@ import { AwsErrorCodes, GetSsoTokenParams, InvalidateSsoTokenParams, + InvalidateStsCredentialParams, InitializeParams, PartialInitializeResult, ShowMessageRequestParams, @@ -16,7 +17,10 @@ import { import { SharedConfigProfileStore } from './profiles/sharedConfigProfileStore' import { IdentityService } from './identityService' import { FileSystemSsoCache, RefreshingSsoCache } from '../sso/cache' +import { RefreshingStsCache } from '../sts/cache/refreshingStsCache' import { SsoTokenAutoRefresher } from './ssoTokenAutoRefresher' +import { FileSystemStsCache } from '../sts/cache/fileSystemStsCache' +import { StsAutoRefresher } from '../sts/stsAutoRefresher' import { AwsError, ServerBase } from '@aws/lsp-core' import { Features } from '@aws/language-server-runtimes/server-interface/server' import { ShowUrl, ShowMessageRequest, ShowProgress } from '../sso/utils' @@ -56,14 +60,20 @@ export class IdentityServer extends ServerBase { const autoRefresher = new SsoTokenAutoRefresher(ssoCache, this.observability) - const iamProvider = new IamProvider(this.observability, profileStore) + const stsCache = new RefreshingStsCache(new FileSystemStsCache(this.observability), this.observability) + const stsAutoRefresher = new StsAutoRefresher( + stsCache, + this.features.identityManagement.sendStsCredentialChanged, + this.observability + ) const identityService = new IdentityService( profileStore, ssoCache, autoRefresher, - iamProvider, - { showUrl, showMessageRequest, showProgress, sendGetMfaCode }, + stsCache, + stsAutoRefresher, + { showUrl, showMessageRequest, showProgress }, this.getClientName(params), this.observability ) @@ -95,6 +105,14 @@ export class IdentityServer extends ServerBase { }) ) + this.features.identityManagement.onInvalidateStsCredential( + async (params: InvalidateStsCredentialParams, token: CancellationToken) => + await identityService.invalidateStsCredential(params, token).catch(reason => { + this.observability.logging.log(`InvalidateIamCredentials failed. ${reason}`) + throw awsResponseErrorWrap(reason) + }) + ) + this.features.identityManagement.onListProfiles( async (params: ListProfilesParams, token: CancellationToken) => await profileService.listProfiles(params, token).catch(reason => { @@ -112,6 +130,7 @@ export class IdentityServer extends ServerBase { ) this.disposables.push(autoRefresher) + this.disposables.push(stsAutoRefresher) return { ...result, diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index a007aead95..1bc0f027ad 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -14,9 +14,8 @@ import { import { SSOToken } from '@smithy/shared-ini-file-loader' import { Logging, Telemetry } from '@aws/language-server-runtimes/server-interface' import { Observability } from '@aws/lsp-core' -import { STSClient } from '@aws-sdk/client-sts' -import { IAMClient } from '@aws-sdk/client-iam' -import { IamProvider } from '../iam/iamProvider' +import { StsCache, StsCredential } from '../sts/cache/stsCache' +import { StsAutoRefresher } from '../sts/stsAutoRefresher' // eslint-disable-next-line use(require('chai-as-promised')) @@ -25,8 +24,9 @@ let sut: IdentityService let profileStore: StubbedInstance let ssoCache: StubbedInstance +let stsCache: StubbedInstance let autoRefresher: StubbedInstance -let iamProvider: StubbedInstance +let stsAutoRefresher: StubbedInstance let observability: StubbedInstance let authFlowFn: SinonSpy @@ -110,18 +110,21 @@ describe('IdentityService', () => { setSsoToken: Promise.resolve(), }) + stsCache = stubInterface({ + getStsCredential: Promise.resolve(undefined), + setStsCredential: Promise.resolve(), + removeStsCredential: Promise.resolve(), + }) + autoRefresher = createStubInstance(SsoTokenAutoRefresher, { watch: Promise.resolve(), unwatch: undefined, }) as StubbedInstance - iamProvider = stubInterface({ - getCredential: Promise.resolve({ - accessKeyId: 'my-access-key', - secretAccessKey: 'my-secret-key', - sessionToken: 'my-session-token', - }), - }) + stsAutoRefresher = createStubInstance(StsAutoRefresher, { + watch: Promise.resolve(), + unwatch: undefined, + }) as StubbedInstance authFlowFn = spy(() => Promise.resolve({ @@ -138,7 +141,8 @@ describe('IdentityService', () => { profileStore, ssoCache, autoRefresher, - iamProvider, + stsCache, + stsAutoRefresher, { showUrl: _ => {}, showMessageRequest: _ => Promise.resolve({ title: 'client-response' }), @@ -153,7 +157,11 @@ describe('IdentityService', () => { } ) - stub(STSClient.prototype, 'send').resolves({ + const validatePermissionsStub = stub(sut as any, 'validatePermissions') + validatePermissionsStub.resolves(true) + + const generateStsCredentialStub = stub(sut as any, 'generateStsCredential') + generateStsCredentialStub.resolves({ Credentials: { AccessKeyId: 'role-access-key', SecretAccessKey: 'role-secret-key', @@ -164,12 +172,7 @@ describe('IdentityService', () => { Arn: 'role-arn', AssumedRoleId: 'role-id', }, - Arn: 'role-arn', - }) - - stub(IAMClient.prototype, 'send').resolves({ - EvaluationResults: [], - }) + } as StsCredential) }) afterEach(() => { @@ -336,6 +339,70 @@ describe('IdentityService', () => { expect(actual.credential.credentials.secretAccessKey).to.equal('my-secret-key') expect(actual.credential.credentials.sessionToken).to.equal('my-session-token') }) + + it('Can login with assumed role.', async () => { + const actual = await sut.getIamCredential({ profileName: 'my-role-profile' }, CancellationToken.None) + + expect(actual.credentials.accessKeyId).to.equal('role-access-key') + expect(actual.credentials.secretAccessKey).to.equal('role-secret-key') + expect(actual.credentials.sessionToken).to.equal('role-session-token') + expect(actual.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') + expect(stsAutoRefresher.watch.calledOnce).to.be.true + }) + + it('Returns existing STS credential.', async () => { + stsCache.getStsCredential = (() => + Promise.resolve({ + Credentials: { + AccessKeyId: 'other-access-key', + SecretAccessKey: 'other-secret-key', + SessionToken: 'other-session-token', + Expiration: new Date('2024-10-25T18:09:20.455Z'), + }, + AssumedRoleUser: { + Arn: 'other-role-arn', + AssumedRoleId: 'other-role-id', + }, + })) as any + const actual = await sut.getIamCredential({ profileName: 'my-role-profile' }, CancellationToken.None) + + expect(actual.credentials.accessKeyId).to.equal('other-access-key') + expect(actual.credentials.secretAccessKey).to.equal('other-secret-key') + expect(actual.credentials.sessionToken).to.equal('other-session-token') + expect(actual.credentials.expiration?.toISOString()).to.equal('2024-10-25T18:09:20.455Z') + expect(stsAutoRefresher.watch.calledOnce).to.be.true + }) + + it('Throws when no STS credential cached and generateOnInvalidStsCredential is false.', async () => { + const error = await expect( + sut.getIamCredential( + { + profileName: 'my-role-profile', + options: { generateOnInvalidStsCredential: false }, + }, + CancellationToken.None + ) + ).rejectedWith(Error) + + expect(error.message).to.equal('STS credential not found.') + expect(stsAutoRefresher.watch.calledOnce).to.be.false + }) + + it('Can assume role with MFA.', async () => { + const actual = await sut.getIamCredential( + { + profileName: 'my-mfa-profile', + mfaCode: '123456', + }, + CancellationToken.None + ) + + expect(actual.credentials.accessKeyId).to.equal('role-access-key') + expect(actual.credentials.secretAccessKey).to.equal('role-secret-key') + expect(actual.credentials.sessionToken).to.equal('role-session-token') + expect(actual.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') + expect(stsAutoRefresher.watch.notCalled).to.be.true + }) }) describe('invalidateSsoToken', () => { @@ -351,4 +418,20 @@ describe('IdentityService', () => { expect(ssoCache.removeSsoToken.notCalled).is.true }) }) + + describe('invalidateStsCredential', () => { + it('Removes on valid profile name', async () => { + await sut.invalidateStsCredential({ profileName: 'my-role-profile' }, CancellationToken.None) + + expect(stsCache.removeStsCredential.called).is.true + }) + + it('Throws on invalid profile name', async () => { + await expect( + sut.invalidateStsCredential({ profileName: ' ' }, CancellationToken.None) + ).to.be.rejectedWith() + + expect(stsCache.removeStsCredential.notCalled).is.true + }) + }) }) diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 22eecb3ca5..4c939b7a87 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -12,15 +12,21 @@ import { IamIdentityCenterSsoTokenSource, InvalidateSsoTokenParams, InvalidateSsoTokenResult, + InvalidateStsCredentialParams, + InvalidateStsCredentialResult, MetricEvent, SsoSession, SsoTokenSourceKind, + Profile, + ProfileKind, } from '@aws/language-server-runtimes/server-interface' import { normalizeSettingList, ProfileStore } from './profiles/profileService' import { authorizationCodePkceFlow, awsBuilderIdReservedName, awsBuilderIdSsoRegion } from '../sso' import { SsoCache, SsoClientRegistration } from '../sso/cache' import { SsoTokenAutoRefresher } from './ssoTokenAutoRefresher' +import { StsCache, StsCredential } from '../sts/cache/stsCache' +import { StsAutoRefresher } from '../sts/stsAutoRefresher' import { throwOnInvalidClientRegistration, throwOnInvalidSsoSession, @@ -30,6 +36,7 @@ import { } from '../sso/utils' import { IamHandlers, simulatePermissions } from '../iam/utils' import { AwsError, Observability } from '@aws/lsp-core' +import { GetCallerIdentityCommand, STSClient, AssumeRoleCommand } from '@aws-sdk/client-sts' import { __ServiceException } from '@aws-sdk/client-sso-oidc/dist-types/models/SSOOIDCServiceException' import { deviceCodeFlow } from '../sso/deviceCode/deviceCodeFlow' import { SSOToken } from '@smithy/shared-ini-file-loader' @@ -49,8 +56,9 @@ export class IdentityService { private readonly profileStore: ProfileStore, private readonly ssoCache: SsoCache, private readonly autoRefresher: SsoTokenAutoRefresher, - private readonly iamProvider: IamProvider, - private readonly handlers: Handlers, + private readonly stsCache: StsCache, + private readonly stsAutoRefresher: StsAutoRefresher, + private readonly handlers: SsoFlowParams['handlers'], private readonly clientName: string, private readonly observability: Observability, private readonly authFlows: AuthFlows = flows @@ -168,7 +176,39 @@ export class IdentityService { throw new AwsError('Profile not found.', AwsErrorCodes.E_PROFILE_NOT_FOUND) } - const credentials = await this.iamProvider.getCredential(profile, options.callStsOnInvalidIamCredential) + let credentials: IamCredentials + // Assume the role matching the found ARN + if (profile.kinds.includes(ProfileKind.RoleSourceProfile)) { + const sourceProfile = profileData.profiles.find(p => p.name === profile.settings?.source_profile) + if ( + sourceProfile && + sourceProfile.settings?.aws_access_key_id && + sourceProfile.settings.aws_secret_access_key + ) { + credentials = await this.getAssumedRoleCredential( + profile, + { + accessKeyId: sourceProfile.settings?.aws_access_key_id, + secretAccessKey: sourceProfile.settings?.aws_secret_access_key, + sessionToken: sourceProfile.settings?.aws_session_token, + }, + options.generateOnInvalidStsCredential, + params.mfaCode + ) + } else { + throw new AwsError('Source IAM credentials not found', AwsErrorCodes.E_INVALID_PROFILE) + } + } + // Get the credentials directly from the profile + else if (profile.kinds.includes(ProfileKind.IamUserProfile)) { + credentials = { + accessKeyId: profile.settings!.aws_access_key_id!, + secretAccessKey: profile.settings!.aws_secret_access_key!, + sessionToken: profile.settings!.aws_session_token!, + } + } else { + throw new AwsError('Credentials could not be found for profile', AwsErrorCodes.E_INVALID_PROFILE) + } // Validate permissions if (options.permissionSet.length > 0) { @@ -190,6 +230,94 @@ export class IdentityService { } } + private async getAssumedRoleCredential( + profile: Profile, + parentCredentials: IamCredentials, + generateOnInvalidStsCredential: boolean, + mfaCode?: string + ): Promise { + if (!profile.settings) { + throw new AwsError('Profile settings not found when assuming role.', AwsErrorCodes.E_INVALID_PROFILE) + } + + // Create STS client for role assumption + const stsClient = new STSClient({ + region: profile.settings.region || 'us-east-1', + credentials: parentCredentials, + }) + + // Try to get the STS credentials from cache + let result: IamCredentials + const roleArn = profile.settings.role_arn! + const stsCredentials = await this.stsCache.getStsCredential(profile.name).catch(_ => undefined) + + if (stsCredentials?.Credentials) { + result = { + accessKeyId: stsCredentials.Credentials.AccessKeyId!, + secretAccessKey: stsCredentials.Credentials.SecretAccessKey!, + sessionToken: stsCredentials.Credentials.SessionToken!, + expiration: stsCredentials.Credentials.Expiration!, + } + } else if (generateOnInvalidStsCredential) { + // Generate STS credentials + const response = await this.generateStsCredential(stsClient, roleArn, profile.settings.mfa_serial, mfaCode) + if (!response.Credentials) { + throw new AwsError( + 'Failed to assume role: No credentials returned', + AwsErrorCodes.E_INVALID_STS_CREDENTIAL + ) + } + // Cache STS credentials + await this.stsCache.setStsCredential(profile.name, response) + result = { + accessKeyId: response.Credentials.AccessKeyId!, + secretAccessKey: response.Credentials.SecretAccessKey!, + sessionToken: response.Credentials.SessionToken!, // Always present in STS response + expiration: response.Credentials.Expiration!, + } + } else { + // If we could not get the cached STS credential and cannot generate a new credential, give up + this.observability.logging.log( + 'STS credential not found an generateOnInvalidStsCredential = false, returning no credential.' + ) + throw new AwsError('STS credential not found.', AwsErrorCodes.E_INVALID_STS_CREDENTIAL) + } + + // Set up auto-refresh if MFA is disabled + if (!profile.settings.mfa_serial) { + await this.stsAutoRefresher + .watch(profile.name, () => this.generateStsCredential(stsClient, roleArn)) + .catch(reason => { + this.observability.logging.log(`Unable to auto-refresh STS credentials. ${reason}`) + }) + } + + return result + } + + private async generateStsCredential( + stsClient: STSClient, + roleArn: string, + mfaSerial?: string, + mfaCode?: string + ): Promise { + try { + const mfaFields = mfaSerial && mfaCode ? { SerialNumber: mfaSerial, TokenCode: mfaCode } : {} + const command = new AssumeRoleCommand({ + RoleArn: roleArn, + RoleSessionName: `session-${Date.now()}`, + DurationSeconds: 3600, + ...mfaFields, + }) + + const { Credentials, AssumedRoleUser } = await stsClient.send(command) + return { Credentials, AssumedRoleUser } + } catch (e) { + this.observability.logging.log(`Error generating STS credentials.`) + throw new AwsError(`Error generating STS credentials.`, AwsErrorCodes.E_CANNOT_CREATE_STS_CREDENTIAL) + } + } + async invalidateSsoToken( params: InvalidateSsoTokenParams, token: CancellationToken @@ -222,6 +350,39 @@ export class IdentityService { } } + async invalidateStsCredential( + params: InvalidateStsCredentialParams, + token: CancellationToken + ): Promise { + const emitMetric = this.emitMetric.bind( + this, + 'flareIdentity_invalidateStsCredential', + this.invalidateStsCredential.name, + Date.now() + ) + + token.onCancellationRequested(_ => { + emitMetric('Cancelled') + }) + + try { + if (!params?.profileName?.trim()) { + throw new AwsError('Profile name is invalid.', AwsErrorCodes.E_INVALID_PROFILE) + } + + this.stsAutoRefresher.unwatch(params.profileName) + + await this.stsCache.removeStsCredential(params.profileName) + + emitMetric('Succeeded') + this.observability.logging.log('Successfully invalidated STS credentials.') + return {} + } catch (e) { + emitMetric('Failed', e) + throw e + } + } + private emitMetric( name: string, source: string, diff --git a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts new file mode 100644 index 0000000000..13f2a2f1a1 --- /dev/null +++ b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts @@ -0,0 +1,190 @@ +// eslint-disable-next-line @typescript-eslint/no-require-imports +import mock = require('mock-fs') +import { FileSystemStsCache, getStsCredentialFilepath } from './fileSystemStsCache' +import { expect, use } from 'chai' +import { DirectoryItems } from 'mock-fs/lib/filesystem' +import { Logging, Telemetry } from '@aws/language-server-runtimes/server-interface' +import { access } from 'fs/promises' +import * as fs from 'fs' +import { StubbedInstance, stubInterface } from 'ts-sinon' +import { Observability } from '@aws/lsp-core' +import { StsCredential } from './stsCache' + +// eslint-disable-next-line +use(require('chai-as-promised')) + +let sut: FileSystemStsCache + +let observability: StubbedInstance + +const profileName: string = 'someprofile' + +const stsCredential: StsCredential = { + Credentials: { + AccessKeyId: 'someaccesskeyid', + SecretAccessKey: 'somesecretaccesskey', + SessionToken: 'somesessiontoken', + Expiration: new Date('2024-09-25T18:09:20.455Z'), + }, + AssumedRoleUser: { + Arn: 'arn:aws:sts::123456789012:assumed-role/somerole/somesession', + AssumedRoleId: 'someassumedroleid', + }, +} + +function setupTest(args?: { profileName?: string; stsCredential?: StsCredential }): void { + // Just for sanity, safe to call restore if mock not currently active + mock.restore() + + args = { ...{ profileName, stsCredential }, ...args } + + const mockConfig: DirectoryItems = {} + mockConfig[getStsCredentialFilepath(args.profileName!)] = JSON.stringify(args.stsCredential) + + mock(mockConfig) +} + +function expectFileExists(filename: string): Chai.Assertion { + return expect(access(filename, fs.constants.F_OK)) +} + +describe('FileSystemStsCache', () => { + beforeEach(() => { + observability = stubInterface() + observability.logging = stubInterface() + observability.telemetry = stubInterface() + + sut = new FileSystemStsCache(observability) + }) + + afterEach(() => { + mock.restore() + }) + + it('removeStsCredential deletes a valid credential', async () => { + const filename = getStsCredentialFilepath(profileName) + setupTest() + + await expectFileExists(filename).to.not.be.rejectedWith() + + await sut.removeStsCredential(profileName) + + await expectFileExists(filename).to.be.rejectedWith() + }) + + it('removeStsCredential does nothing on invalid/non-existent credential', async () => { + const filename = getStsCredentialFilepath(profileName) + setupTest() + + await expectFileExists(filename).to.not.be.rejectedWith() + + await sut.removeStsCredential('non-existent credential') + + await expectFileExists(filename).to.not.be.rejectedWith() + }) + + it('removeStsCredential throws on invalid profile name', async () => { + await expect(sut.removeStsCredential(null!)).to.be.rejectedWith() + }) + + it('getStsCredential returns valid credential', async () => { + setupTest() + + const actual = await sut.getStsCredential(profileName) + + expect(actual).to.not.be.null.and.not.undefined + expect(actual?.Credentials?.AccessKeyId).to.equal(stsCredential.Credentials?.AccessKeyId) + expect(actual?.Credentials?.SecretAccessKey).to.equal(stsCredential.Credentials?.SecretAccessKey) + expect(actual?.Credentials?.SessionToken).to.equal(stsCredential.Credentials?.SessionToken) + expect(actual?.Credentials?.Expiration?.toISOString()).to.equal( + stsCredential.Credentials?.Expiration?.toISOString() + ) + expect(actual?.AssumedRoleUser?.Arn).to.equal(stsCredential.AssumedRoleUser?.Arn) + expect(actual?.AssumedRoleUser?.AssumedRoleId).to.equal(stsCredential.AssumedRoleUser?.AssumedRoleId) + }) + + it('getStsCredential returns undefined when file does not exist', async () => { + setupTest() + + const actual = await sut.getStsCredential('does not exist') + + expect(actual).to.be.undefined + }) + + it('getStsCredential returns undefined on invalid credential', async () => { + setupTest({ profileName: 'invalid-profile', stsCredential: {} as StsCredential }) + + const actual = await sut.getStsCredential(profileName) + + expect(actual).to.be.undefined + }) + + it('setStsCredential writes new valid credential', async () => { + setupTest() + await sut.setStsCredential(profileName, stsCredential) + + const actual = await sut.getStsCredential(profileName) + + expect(actual).to.not.be.null.and.not.undefined + expect(actual?.Credentials?.AccessKeyId).to.equal(stsCredential.Credentials?.AccessKeyId) + expect(actual?.Credentials?.SecretAccessKey).to.equal(stsCredential.Credentials?.SecretAccessKey) + expect(actual?.Credentials?.SessionToken).to.equal(stsCredential.Credentials?.SessionToken) + expect(actual?.Credentials?.Expiration?.toISOString()).to.equal( + stsCredential.Credentials?.Expiration?.toISOString() + ) + expect(actual?.AssumedRoleUser?.Arn).to.equal(stsCredential.AssumedRoleUser?.Arn) + expect(actual?.AssumedRoleUser?.AssumedRoleId).to.equal(stsCredential.AssumedRoleUser?.AssumedRoleId) + }) + + it('setStsCredential writes new valid credential when ~/.aws does not exist', async () => { + mock.restore() + mock({}) + + await sut.setStsCredential(profileName, stsCredential) + + const actual = await sut.getStsCredential(profileName) + + expect(actual).to.not.be.null.and.not.undefined + expect(actual?.Credentials?.AccessKeyId).to.equal(stsCredential.Credentials?.AccessKeyId) + expect(actual?.Credentials?.SecretAccessKey).to.equal(stsCredential.Credentials?.SecretAccessKey) + expect(actual?.Credentials?.SessionToken).to.equal(stsCredential.Credentials?.SessionToken) + expect(actual?.Credentials?.Expiration?.toISOString()).to.equal( + stsCredential.Credentials?.Expiration?.toISOString() + ) + expect(actual?.AssumedRoleUser?.Arn).to.equal(stsCredential.AssumedRoleUser?.Arn) + expect(actual?.AssumedRoleUser?.AssumedRoleId).to.equal(stsCredential.AssumedRoleUser?.AssumedRoleId) + }) + + it('setStsCredential writes updated existing credential', async () => { + setupTest() + + await sut.setStsCredential(profileName, { + Credentials: { + AccessKeyId: 'newaccesskeyid', + SecretAccessKey: 'newsecretaccesskey', + SessionToken: 'newsessiontoken', + Expiration: new Date('2024-10-14T12:00:00.000Z'), + }, + AssumedRoleUser: { + Arn: 'newarn', + AssumedRoleId: 'newroleid', + }, + }) + + const actual = await sut.getStsCredential(profileName) + + expect(actual).to.not.be.null.and.not.undefined + expect(actual?.Credentials?.AccessKeyId).to.equal('newaccesskeyid') + expect(actual?.Credentials?.SecretAccessKey).to.equal('newsecretaccesskey') + expect(actual?.Credentials?.SessionToken).to.equal('newsessiontoken') + expect(actual?.Credentials?.Expiration?.toISOString()).to.equal('2024-10-14T12:00:00.000Z') + expect(actual?.AssumedRoleUser?.Arn).to.equal('newarn') + expect(actual?.AssumedRoleUser?.AssumedRoleId).to.equal('newroleid') + }) + + it('setStsCredential returns without error on invalid credential', async () => { + setupTest() + + await sut.setStsCredential(profileName, {} as StsCredential) // no throw + }) +}) diff --git a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts new file mode 100644 index 0000000000..00da58c4ab --- /dev/null +++ b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts @@ -0,0 +1,84 @@ +import { StsCache, StsCredential, stsCredentialDuckTyper } from './stsCache' +import { AwsError, Observability } from '@aws/lsp-core' +import { AwsErrorCodes } from '@aws/language-server-runtimes/protocol' +import path, { join } from 'path' +import { mkdir, readFile, unlink, writeFile } from 'fs/promises' +import { createHash } from 'crypto' +import { getHomeDir } from '@smithy/shared-ini-file-loader' + +export class FileSystemStsCache implements StsCache { + constructor(private readonly observability: Observability) {} + + async removeStsCredential(name: string): Promise { + await unlink(getStsCredentialFilepath(name)).catch(reason => this.ignoreDoesNotExistOrThrow(reason)) + } + + async getStsCredential(name: string): Promise { + return await getStsCredentialFromFile(name) + .then(stsCredential => { + if (stsCredentialDuckTyper.eval(stsCredential)) { + // Ensure Expiration is a Date object + if (typeof stsCredential.Credentials?.Expiration === 'string') { + stsCredential.Credentials.Expiration = new Date(stsCredential.Credentials.Expiration) + } + return stsCredential + } else { + return undefined + } + }) + .catch(reason => this.ignoreDoesNotExistOrThrow(reason)) + } + + async setStsCredential(name: string, credentials: StsCredential): Promise { + if (!stsCredentialDuckTyper.eval(credentials)) { + this.observability.logging.log('File read from STS cache is not an STS credential.') + return + } + + await writeStsObjectToFile(name, credentials).catch(reason => { + throw AwsError.wrap(reason, AwsErrorCodes.E_CANNOT_WRITE_SSO_CACHE) + }) + } + + private ignoreDoesNotExistOrThrow(error: unknown): Promise { + // Error codes are consistent across OSes (Windows is converted to libuv error codes) + // https://nodejs.org/api/errors.html#errorerrno + if ((error as SystemError)?.code === 'ENOENT') { + return Promise.resolve(undefined) + } + + this.observability.logging.log('Cannot read STS cache.') + throw AwsError.wrap(error as Error, AwsErrorCodes.E_CANNOT_READ_SSO_CACHE) + } +} + +// Based on: +// https://github.com/smithy-lang/smithy-typescript/blob/main/packages/shared-ini-file-loader/src/getSSOTokenFilepath.ts +export function getStsCredentialFilepath(id: string) { + const hasher = createHash('sha1') + const cacheName = hasher.update(id).digest('hex') + return join(getHomeDir(), '.aws', 'cli', 'cache', `${cacheName}.json`) +} + +// Based on: +// https://github.com/smithy-lang/smithy-typescript/blob/main/packages/shared-ini-file-loader/src/getSSOTokenFromFile.ts +async function getStsCredentialFromFile(id: string) { + const stsCredentialFilepath = getStsCredentialFilepath(id) + const stsCredentialText = await readFile(stsCredentialFilepath, 'utf8') + return JSON.parse(stsCredentialText) as StsCredential +} + +// Based on: +// https://github.com/aws/aws-sdk-js-v3/blob/6e61f0e78ff7a9e3b1f2cd651bde5fc656d85ba9/packages/token-providers/src/writeSSOTokenToFile.ts +async function writeStsObjectToFile(id: string, credentials: StsCredential): Promise { + const filepath = getStsCredentialFilepath(id) + await mkdir(path.dirname(filepath), { mode: 0o755, recursive: true }) + const json = JSON.stringify(credentials, null, 2) + return await writeFile(filepath, json, { encoding: 'utf-8', flush: true, mode: 0o600 }) +} + +// Minimal declaration of SystemError (no node type declaration for it) to access code property +// https://nodejs.org/api/errors.html#class-systemerror +interface SystemError { + code: string +} diff --git a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.test.ts b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.test.ts new file mode 100644 index 0000000000..858817179e --- /dev/null +++ b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.test.ts @@ -0,0 +1,74 @@ +import { expect, use } from 'chai' +import { restore } from 'sinon' +import { stubInterface } from 'ts-sinon' +import { RefreshingStsCache } from './refreshingStsCache' +import { Logging, Telemetry } from '@aws/language-server-runtimes/server-interface' +import { Observability } from '@aws/lsp-core' +import { StsCache, StsCredential } from './stsCache' + +// eslint-disable-next-line +use(require('chai-as-promised')) + +let observability: Observability + +const profileName: string = 'someprofile' + +function createStsCredential(expiresAsOffsetMillis: number): StsCredential { + return { + Credentials: { + AccessKeyId: 'someaccesskeyid', + SecretAccessKey: 'somesecretaccesskey', + SessionToken: 'somesessiontoken', + Expiration: new Date(Date.now() + expiresAsOffsetMillis), + }, + AssumedRoleUser: { + Arn: 'arn:aws:sts::123456789012:assumed-role/somerole/somesession', + AssumedRoleId: 'someassumedroleid', + }, + } as StsCredential +} + +function stubStsCache(stsCredential?: StsCredential): StsCache { + return stubInterface({ + getStsCredential: Promise.resolve(stsCredential), + }) +} + +describe('RefreshingStsCache', () => { + beforeEach(() => { + observability = stubInterface() + observability.logging = stubInterface() + observability.telemetry = stubInterface() + }) + + afterEach(() => { + restore() + }) + + describe('getStsCredential', () => { + it('Returns nothing on no cached STS credential.', async () => { + const stsCache = stubStsCache() + const sut = new RefreshingStsCache(stsCache, observability) + + const actual = await sut.getStsCredential(profileName) + + expect(actual).to.be.undefined + }) + + it('Returns existing STS credential before refresh window (5 minutes before expiration).', async () => { + const stsCredential = createStsCredential(6 * 60 * 1000 /* 6 minutes before */) + const stsCache = stubStsCache(stsCredential) + const sut = new RefreshingStsCache(stsCache, observability) + + const actual = await sut.getStsCredential(profileName) + + expect(actual).to.not.be.null.and.not.empty + expect(actual?.Credentials?.AccessKeyId).to.equal(stsCredential.Credentials?.AccessKeyId) + expect(actual?.Credentials?.SecretAccessKey).to.equal(stsCredential.Credentials?.SecretAccessKey) + expect(actual?.Credentials?.SessionToken).to.equal(stsCredential.Credentials?.SessionToken) + expect(actual?.Credentials?.Expiration).to.equal(stsCredential.Credentials?.Expiration) + expect(actual?.AssumedRoleUser?.Arn).to.equal(stsCredential.AssumedRoleUser?.Arn) + expect(actual?.AssumedRoleUser?.AssumedRoleId).to.equal(stsCredential.AssumedRoleUser?.AssumedRoleId) + }) + }) +}) diff --git a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts new file mode 100644 index 0000000000..b6f27fc8bb --- /dev/null +++ b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts @@ -0,0 +1,58 @@ +import { StsCache, StsCredential } from './stsCache' +import { AwsErrorCodes } from '@aws/language-server-runtimes/server-interface' +import { AwsError, Observability } from '@aws/lsp-core' + +interface StsCredentialDetail { + lastRefreshMillis: number +} + +export class RefreshingStsCache implements StsCache { + private readonly stsCredentialDetails: Record = {} + + constructor( + private readonly next: StsCache, + private readonly observability: Observability + ) {} + + async removeStsCredential(name: string): Promise { + this.observability.logging.log('Removing STS Credential.') + if (!name.trim()) { + throw new AwsError('Profile name is invalid.', AwsErrorCodes.E_INVALID_PROFILE) + } + + await this.next.removeStsCredential(name) + } + + async getStsCredential(name: string): Promise { + this.observability.logging.log('Retrieving STS Credential.') + + if (!name.trim()) { + throw new AwsError('Profile name is invalid.', AwsErrorCodes.E_INVALID_PROFILE) + } + + const stsCredential = await this.next.getStsCredential(name) + + if (!stsCredential || !stsCredential.Credentials?.Expiration) { + this.observability.logging.log('STS Credential not found.') + return undefined + } + + const nowMillis = Date.now() + const expirationMillis = new Date(stsCredential.Credentials.Expiration).getTime() + + // Check if credential is still valid (not in refresh window) + if (nowMillis < expirationMillis) { + this.observability.logging.log('STS credential before refresh window. Returning current STS credential.') + return stsCredential + } else { + // Credential is in refresh window or expired + this.observability.logging.log('STS credential has expired.') + throw new AwsError('STS credential has expired.', AwsErrorCodes.E_STS_CREDENTIAL_EXPIRED) + } + } + + async setStsCredential(name: string, credentials: StsCredential): Promise { + this.observability.logging.log('Storing STS Credential.') + await this.next.setStsCredential(name, credentials) + } +} diff --git a/server/aws-lsp-identity/src/sts/cache/stsCache.ts b/server/aws-lsp-identity/src/sts/cache/stsCache.ts new file mode 100644 index 0000000000..bf403d66b3 --- /dev/null +++ b/server/aws-lsp-identity/src/sts/cache/stsCache.ts @@ -0,0 +1,12 @@ +import { AssumeRoleCommandOutput } from '@aws-sdk/client-sts' +import { DuckTyper } from '../../duckTyper' + +export type StsCredential = Pick + +export interface StsCache { + getStsCredential(name: string): Promise + setStsCredential(name: string, credentials: StsCredential): Promise + removeStsCredential(name: string): Promise +} + +export const stsCredentialDuckTyper = new DuckTyper().requireProperty('Credentials').requireProperty('AssumedRoleUser') diff --git a/server/aws-lsp-identity/src/sts/stsAutoRefresher.test.ts b/server/aws-lsp-identity/src/sts/stsAutoRefresher.test.ts new file mode 100644 index 0000000000..d5b378a41d --- /dev/null +++ b/server/aws-lsp-identity/src/sts/stsAutoRefresher.test.ts @@ -0,0 +1,137 @@ +import { expect, use } from 'chai' +import { StsAutoRefresher } from './stsAutoRefresher' +import { StubbedInstance, stubInterface } from 'ts-sinon' +import { restore, spy } from 'sinon' +import { AwsErrorCodes, Logging, Telemetry } from '@aws/language-server-runtimes/server-interface' +import { AwsError, Observability } from '@aws/lsp-core' +import { StsCredential } from './cache/stsCache' +import { RefreshingStsCache } from './cache/refreshingStsCache' + +// eslint-disable-next-line +use(require('chai-as-promised')) + +let observability: StubbedInstance + +const profileName = 'someprofile' +const now = Date.now() + +function createStsCredential(expiresAsOffsetMillis: number): StsCredential { + return { + Credentials: { + AccessKeyId: 'someaccesskeyid', + SecretAccessKey: 'somesecretaccesskey', + SessionToken: 'somesessiontoken', + Expiration: new Date(now + expiresAsOffsetMillis), + }, + AssumedRoleUser: { + Arn: 'arn:aws:sts::123456789012:assumed-role/somerole/somesession', + AssumedRoleId: 'someassumedroleid', + }, + } satisfies StsCredential +} + +function refreshStsCredential(): Promise { + return Promise.resolve({ + Credentials: { + AccessKeyId: 'newaccesskeyid', + SecretAccessKey: 'newsecretaccesskey', + SessionToken: 'newsessiontoken', + Expiration: new Date(now + 60 * 60 * 1000 /* 1 hour in relative seconds */), + }, + AssumedRoleUser: { + Arn: 'arn:aws:sts::123456789012:assumed-role/newrole/newsession', + AssumedRoleId: 'newassumedroleid', + }, + } satisfies StsCredential) +} + +function stubStsCache(stsCredential?: StsCredential): RefreshingStsCache { + return stubInterface({ + getStsCredential: stsCredential + ? Promise.resolve(stsCredential) + : Promise.reject(new AwsError('Test: No STS credential', AwsErrorCodes.E_INVALID_STS_CREDENTIAL)), + }) +} + +describe('StsAutoRefresher', () => { + beforeEach(() => { + observability = stubInterface() + observability.logging = stubInterface() + observability.telemetry = stubInterface() + }) + + afterEach(() => { + restore() + }) + + it('watch does nothing if STS credential is not loaded from cache.', async () => { + const stsCache = stubStsCache() + using sut = new StsAutoRefresher(stsCache, () => {}, observability) + + expect(Object.keys(sut['timeouts']).length).to.equal(0) + + await sut.watch(profileName, refreshStsCredential) + + expect(Object.keys(sut['timeouts']).length).to.equal(0) + }) + + it('watch does nothing if STS credential is expired.', async () => { + const stsCache = stubStsCache(createStsCredential(-10000)) + using sut = new StsAutoRefresher(stsCache, () => {}, observability) + + expect(Object.keys(sut['timeouts']).length).to.equal(0) + + await sut.watch(profileName, refreshStsCredential) + + expect(Object.keys(sut['timeouts']).length).to.equal(0) + }) + + it('watch schedules refresh in refresh window prior to expiration.', async () => { + const setTimeoutSpy = spy(global, 'setTimeout') + + // Before the refresh window + const stsCache = stubStsCache(createStsCredential(60 * 60 * 1000)) + using sut = new StsAutoRefresher(stsCache, () => {}, observability) + + expect(Object.keys(sut['timeouts']).length).to.equal(0) + + await sut.watch(profileName, refreshStsCredential) + + expect(Object.keys(sut['timeouts']).length).to.equal(1) + + expect(setTimeoutSpy.calledOnce).to.be.true + expect(setTimeoutSpy.lastCall.args[1]) + .to.be.greaterThan(55 * 60 * 1000) + .and.lessThan(60 * 60 * 1000) + }) + + it('watch schedules refresh retry in retry window after last attempt.', async () => { + const setTimeoutSpy = spy(global, 'setTimeout') + + // In the refresh window + const stsCache = stubStsCache(createStsCredential(4 * 60 * 1000)) + using sut = new StsAutoRefresher(stsCache, () => {}, observability) + + expect(Object.keys(sut['timeouts']).length).to.equal(0) + + await sut.watch(profileName, refreshStsCredential) + + expect(Object.keys(sut['timeouts']).length).to.equal(1) + + expect(setTimeoutSpy.calledOnce).to.be.true + expect(setTimeoutSpy.lastCall.args[1]) + .to.be.greaterThan(30 * 1000) + .and.lessThan(40 * 1000) + }) + + it('unwatch does nothing if profileName is not watched.', () => { + const stsCache = stubStsCache() + using sut = new StsAutoRefresher(stsCache, () => {}, observability) + + expect(Object.keys(sut['timeouts']).length).to.equal(0) + + sut.unwatch(refreshStsCredential.name) + + expect(Object.keys(sut['timeouts']).length).to.equal(0) + }) +}) diff --git a/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts b/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts new file mode 100644 index 0000000000..ab5007924f --- /dev/null +++ b/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts @@ -0,0 +1,115 @@ +import { StsCache, StsCredential } from './cache/stsCache' +import { Observability } from '@aws/lsp-core' +import { StsCredentialChangedKind, StsCredentialChangedParams } from '@aws/language-server-runtimes/protocol' + +// Modified to match SSO token refresh behavior +const refreshWindowMillis = 5 * 60 * 1000 // 5 minutes (matching SSO) +const retryCooldownWindowMillis = 30000 // 30 seconds (matching SSO) +const bufferedRefreshWindowMillis = refreshWindowMillis * 0.95 // 4.75 minutes +const bufferedRetryCooldownWindowMillis = retryCooldownWindowMillis * 1.05 // 31.5 seconds +const maxRefreshJitterMillis = 10000 // 10 seconds (matching SSO) +const maxRetryCooldownJitterMillis = 3000 // 3 seconds (matching SSO) + +export type RaiseStsChanged = (params: StsCredentialChangedParams) => void + +interface StsCredentialDetail { + lastRefreshMillis: number +} + +export class StsAutoRefresher implements Disposable { + private readonly timeouts: Record = {} + private readonly stsCredentialDetails: Record = {} + + constructor( + private readonly stsCache: StsCache, + private readonly raiseStsCredentialChanged: RaiseStsChanged, + private readonly observability: Observability + ) {} + + [Symbol.dispose](): void { + for (const stsSessionName of Object.keys(this.timeouts)) { + this.unwatch(stsSessionName) + } + } + + async watch(name: string, refreshCallback: () => Promise): Promise { + try { + this.unwatch(name) + + const stsCredentials = await this.stsCache.getStsCredential(name).catch(_ => undefined) + + if (!stsCredentials || !stsCredentials.Credentials?.Expiration) { + this.observability.logging.log( + 'STS credentials do not exist or have no expiration, will not be auto-refreshed.' + ) + return + } + + const nowMillis = Date.now() + const expirationMillis = new Date(stsCredentials.Credentials?.Expiration).getTime() + + // Get or create StsCredentialDetail (matching SSO pattern) + const stsCredentialDetail = + this.stsCredentialDetails[name] ?? (this.stsCredentialDetails[name] = { lastRefreshMillis: 0 }) + + let delayMs: number + + if (nowMillis < expirationMillis - refreshWindowMillis) { + // Before refresh window, schedule to run in refresh window with jitter + delayMs = expirationMillis - bufferedRefreshWindowMillis - nowMillis + delayMs += Math.random() * maxRefreshJitterMillis + } else if (expirationMillis - refreshWindowMillis < nowMillis && nowMillis < expirationMillis) { + // In refresh window - check if we're still in retry cooldown + const retryAfterMillis = stsCredentialDetail.lastRefreshMillis + retryCooldownWindowMillis + if (nowMillis < retryAfterMillis) { + this.observability.logging.log('STS credentials in retry cooldown window. Scheduling next retry.') + delayMs = retryAfterMillis - nowMillis + } else { + // Ready to refresh - use buffered retry cooldown with jitter + delayMs = bufferedRetryCooldownWindowMillis + delayMs += Math.random() * maxRetryCooldownJitterMillis + } + } else { + // Expired + this.observability.logging.log('STS credentials have expired and will not be auto-refreshed.') + return + } + + this.observability.logging.info(`Auto-refreshing STS credentials in ${delayMs} milliseconds.`) + this.timeouts[name] = setTimeout(async () => { + try { + // Update last refresh attempt time (matching SSO pattern) + stsCredentialDetail.lastRefreshMillis = Date.now() + + const newCredentials = await refreshCallback() + this.observability.logging.log(`Generated new STS credentials`) + await this.stsCache.setStsCredential(name, newCredentials) + + // Continue watching with the new credentials (allows multiple refreshes) + this.watch(name, refreshCallback) + + this.raiseStsCredentialChanged({ kind: StsCredentialChangedKind.Refreshed, stsCredentialId: name }) + } catch (error) { + this.observability.logging.log(`Failed to refresh STS credentials: ${error}`) + + // On error, continue watching to retry later (matching SSO pattern) + this.watch(name, refreshCallback) + } + }, delayMs) + } catch (e) { + this.observability.logging.log(`Error setting up STS auto-refresh: ${e}`) + throw e + } + } + + unwatch(stsSessionName: string): void { + const timeout = this.timeouts[stsSessionName] + if (timeout) { + clearTimeout(timeout) + delete this.timeouts[stsSessionName] + // Also clean up the credential detail + delete this.stsCredentialDetails[stsSessionName] + this.observability.logging.log('STS credentials unwatched and will not be auto-refreshed.') + } + } +} From 8cc0c7e5cbdc6623e6e42bbf2f4471cda789e0b0 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Thu, 10 Jul 2025 18:09:40 -0400 Subject: [PATCH 16/37] refactor: prefix IAM-related profiles with 'Iam' --- server/aws-lsp-identity/src/language-server/identityService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 4c939b7a87..95db1c1b06 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -178,7 +178,7 @@ export class IdentityService { let credentials: IamCredentials // Assume the role matching the found ARN - if (profile.kinds.includes(ProfileKind.RoleSourceProfile)) { + if (profile.kinds.includes(ProfileKind.IamRoleSourceProfile)) { const sourceProfile = profileData.profiles.find(p => p.name === profile.settings?.source_profile) if ( sourceProfile && From 8e401ec0257ba0c765b98128251e7b277b369b02 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Fri, 11 Jul 2025 11:55:14 -0400 Subject: [PATCH 17/37] refactor: move profile fields into profileTypes object --- .../profiles/profileService.test.ts | 13 ---- .../profiles/profileService.ts | 77 +++++++++++++------ .../profiles/sharedConfigProfileStore.test.ts | 9 --- .../profiles/sharedConfigProfileStore.ts | 38 +++------ 4 files changed, 62 insertions(+), 75 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts index bf928a1119..adc77dc1be 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts @@ -338,19 +338,6 @@ describe('ProfileService', async () => { ) }) - it('updateProfile throws on missing region for role instance profile', async () => { - const profile = { - kinds: [ProfileKind.RoleInstanceProfile], - name: 'profile-name', - settings: { - role_arn: 'role-arn', - credential_source: 'Ec2InstanceMetadata', - }, - } - - await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Region required on profile.') - }) - it('updateProfile throws on missing credential process for process profile', async () => { const profile = { kinds: [ProfileKind.ProcessProfile], diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts index 68ed446f04..afa855de8a 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts @@ -39,6 +39,7 @@ export const ProfileFields = { credential_source: 'credential_source', source_profile: 'source_profile', mfa_serial: 'mfa_serial', + external_id: 'external_id', } as const export const SsoSessionFields = { @@ -47,30 +48,57 @@ export const SsoSessionFields = { sso_start_url: 'sso_start_url', } as const -export const profileDuckTypers = { - SsoTokenProfile: new DuckTyper() - .requireProperty(ProfileFields.sso_session) - .disallowProperty(ProfileFields.sso_account_id) - .disallowProperty(ProfileFields.sso_role_name), - IamUserProfile: new DuckTyper() - .requireProperty(ProfileFields.aws_access_key_id) - .requireProperty(ProfileFields.aws_secret_access_key) - .optionalProperty(ProfileFields.aws_session_token), - RoleSourceProfile: new DuckTyper() - .requireProperty(ProfileFields.role_arn) - .requireProperty(ProfileFields.source_profile) - .optionalProperty(ProfileFields.role_session_name) - .optionalProperty(ProfileFields.mfa_serial) - .disallowProperty(ProfileFields.credential_source), - RoleInstanceProfile: new DuckTyper() - .requireProperty(ProfileFields.role_arn) - .requireProperty(ProfileFields.credential_source) - .requireProperty(ProfileFields.region) - .optionalProperty(ProfileFields.role_session_name) - .disallowProperty(ProfileFields.source_profile), - ProcessProfile: new DuckTyper().requireProperty(ProfileFields.credential_process), - Unknown: new DuckTyper(), -} +export const profileTypes = { + SsoTokenProfile: { + kind: ProfileKind.SsoTokenProfile, + required: [ProfileFields.sso_session], + optional: [ProfileFields.region], + disallowed: [ProfileFields.sso_account_id, ProfileFields.sso_role_name], + }, + IamUserProfile: { + kind: ProfileKind.IamUserProfile, + required: [ProfileFields.aws_access_key_id, ProfileFields.aws_secret_access_key], + optional: [ProfileFields.aws_session_token], + disallowed: [], + }, + IamRoleSourceProfile: { + kind: ProfileKind.IamRoleSourceProfile, + required: [ProfileFields.role_arn, ProfileFields.source_profile], + optional: [ProfileFields.external_id, ProfileFields.role_session_name, ProfileFields.mfa_serial], + disallowed: [ProfileFields.credential_source], + }, + IamRoleInstanceProfile: { + kind: ProfileKind.IamRoleInstanceProfile, + required: [ProfileFields.role_arn, ProfileFields.credential_source], + optional: [ProfileFields.external_id, ProfileFields.role_session_name, ProfileFields.region], + disallowed: [ProfileFields.source_profile], + }, + IamProcessProfile: { + kind: ProfileKind.IamProcessProfile, + required: [ProfileFields.credential_process], + optional: [], + disallowed: [], + }, +} as const + +export const profileDuckTypers = Object.fromEntries( + Object.entries(profileTypes).map(([key, def]) => [ + key, + (() => { + const typer = new DuckTyper() + for (const field of def.required) { + typer.requireProperty(field) + } + for (const field of def.optional) { + typer.optionalProperty(field) + } + for (const field of def.disallowed) { + typer.disallowProperty(field) + } + return typer + })(), + ]) +) export const ssoSessionDuckTyper = new DuckTyper() .requireProperty(SsoSessionFields.sso_start_url) @@ -183,7 +211,6 @@ export class ProfileService { if (profile.kinds.includes(ProfileKind.RoleInstanceProfile)) { this.throwOnInvalidProfile(!profileSettings.role_arn, 'Role ARN required on profile.') - this.throwOnInvalidProfile(!profileSettings.region, 'Region required on profile.') this.throwOnInvalidProfile(!profileSettings.credential_source, 'Credential source required on profile.') } diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts index 003b306faa..227003dbd8 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts @@ -94,7 +94,6 @@ describe('SharedConfigProfileStore', async () => { settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', - aws_session_token: undefined, }, }, { @@ -186,7 +185,6 @@ describe('SharedConfigProfileStore', async () => { settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', - aws_session_token: undefined, }, }, { @@ -283,7 +281,6 @@ describe('SharedConfigProfileStore', async () => { settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', - aws_session_token: undefined, }, }, { @@ -341,7 +338,6 @@ describe('SharedConfigProfileStore', async () => { settings: { role_arn: 'new-role-arn', credential_source: 'new-source', - region: 'new-region', }, }, { @@ -401,7 +397,6 @@ describe('SharedConfigProfileStore', async () => { settings: { aws_access_key_id: 'AAAAAAAA', aws_secret_access_key: 'BBBBBBBB', - aws_session_token: undefined, }, }, { @@ -426,8 +421,6 @@ describe('SharedConfigProfileStore', async () => { settings: { role_arn: 'new-role-arn', credential_source: 'new-source', - region: 'new-region', - role_session_name: undefined, }, }, { @@ -436,8 +429,6 @@ describe('SharedConfigProfileStore', async () => { settings: { role_arn: 'new-role-arn', source_profile: 'new-source-profile', - mfa_serial: undefined, - role_session_name: undefined, }, }, { diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts index fb4f167d69..0b612a46a0 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts @@ -53,34 +53,16 @@ export class SharedConfigProfileStore implements ProfileStore { settings: {}, } // Add the kinds and settings for each matched profile type - if (profileDuckTypers.SsoTokenProfile.eval(settings)) { - profile.kinds.push(ProfileKind.SsoTokenProfile) - profile.settings!.region = settings.region - profile.settings!.sso_session = settings.sso_session - } - if (profileDuckTypers.ProcessProfile.eval(settings)) { - profile.kinds.push(ProfileKind.ProcessProfile) - profile.settings!.credential_process = settings.credential_process - } - if (profileDuckTypers.RoleSourceProfile.eval(settings)) { - profile.kinds.push(ProfileKind.RoleSourceProfile) - profile.settings!.role_arn = settings.role_arn - profile.settings!.source_profile = settings.source_profile - profile.settings!.mfa_serial = settings.mfa_serial - profile.settings!.role_session_name = settings.role_session_name - } - if (profileDuckTypers.RoleInstanceProfile.eval(settings)) { - profile.kinds.push(ProfileKind.RoleInstanceProfile) - profile.settings!.role_arn = settings.role_arn - profile.settings!.region = settings.region - profile.settings!.credential_source = settings.credential_source - profile.settings!.role_session_name = settings.role_session_name - } - if (profileDuckTypers.IamUserProfile.eval(settings)) { - profile.kinds.push(ProfileKind.IamUserProfile) - profile.settings!.aws_access_key_id = settings.aws_access_key_id - profile.settings!.aws_secret_access_key = settings.aws_secret_access_key - profile.settings!.aws_session_token = settings.aws_session_token + for (const [profileType, fields] of Object.entries(profileTypes)) { + if (profileDuckTypers[profileType].eval(settings)) { + profile.kinds.push(fields.kind) + const relevantFields = [...fields.required, ...fields.optional] + for (const field of relevantFields) { + if (settings[field] !== undefined) { + profile.settings![field] = settings[field] + } + } + } } // If the profile does not match any profile type, mark it as an unknown profile if (profile.kinds.length === 0) { From 21dda338f3cf7ee8615ce848d044d58e6b38ec0d Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Fri, 11 Jul 2025 15:33:52 -0400 Subject: [PATCH 18/37] refactor: move parent credentials logic into generateStsCredentials --- .../src/language-server/identityService.ts | 73 +++++++++---------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 95db1c1b06..e0d6d89a06 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -179,25 +179,11 @@ export class IdentityService { let credentials: IamCredentials // Assume the role matching the found ARN if (profile.kinds.includes(ProfileKind.IamRoleSourceProfile)) { - const sourceProfile = profileData.profiles.find(p => p.name === profile.settings?.source_profile) - if ( - sourceProfile && - sourceProfile.settings?.aws_access_key_id && - sourceProfile.settings.aws_secret_access_key - ) { - credentials = await this.getAssumedRoleCredential( - profile, - { - accessKeyId: sourceProfile.settings?.aws_access_key_id, - secretAccessKey: sourceProfile.settings?.aws_secret_access_key, - sessionToken: sourceProfile.settings?.aws_session_token, - }, - options.generateOnInvalidStsCredential, - params.mfaCode - ) - } else { - throw new AwsError('Source IAM credentials not found', AwsErrorCodes.E_INVALID_PROFILE) - } + credentials = await this.getAssumedRoleCredential( + profile, + options.generateOnInvalidStsCredential, + params.mfaCode + ) } // Get the credentials directly from the profile else if (profile.kinds.includes(ProfileKind.IamUserProfile)) { @@ -232,7 +218,6 @@ export class IdentityService { private async getAssumedRoleCredential( profile: Profile, - parentCredentials: IamCredentials, generateOnInvalidStsCredential: boolean, mfaCode?: string ): Promise { @@ -240,15 +225,8 @@ export class IdentityService { throw new AwsError('Profile settings not found when assuming role.', AwsErrorCodes.E_INVALID_PROFILE) } - // Create STS client for role assumption - const stsClient = new STSClient({ - region: profile.settings.region || 'us-east-1', - credentials: parentCredentials, - }) - // Try to get the STS credentials from cache let result: IamCredentials - const roleArn = profile.settings.role_arn! const stsCredentials = await this.stsCache.getStsCredential(profile.name).catch(_ => undefined) if (stsCredentials?.Credentials) { @@ -260,7 +238,7 @@ export class IdentityService { } } else if (generateOnInvalidStsCredential) { // Generate STS credentials - const response = await this.generateStsCredential(stsClient, roleArn, profile.settings.mfa_serial, mfaCode) + const response = await this.generateStsCredential(profile, mfaCode) if (!response.Credentials) { throw new AwsError( 'Failed to assume role: No credentials returned', @@ -286,7 +264,7 @@ export class IdentityService { // Set up auto-refresh if MFA is disabled if (!profile.settings.mfa_serial) { await this.stsAutoRefresher - .watch(profile.name, () => this.generateStsCredential(stsClient, roleArn)) + .watch(profile.name, () => this.generateStsCredential(profile)) .catch(reason => { this.observability.logging.log(`Unable to auto-refresh STS credentials. ${reason}`) }) @@ -295,16 +273,37 @@ export class IdentityService { return result } - private async generateStsCredential( - stsClient: STSClient, - roleArn: string, - mfaSerial?: string, - mfaCode?: string - ): Promise { + private async generateStsCredential(profile: Profile, mfaCode?: string): Promise { try { - const mfaFields = mfaSerial && mfaCode ? { SerialNumber: mfaSerial, TokenCode: mfaCode } : {} + let parentCredentials: IamCredentials + if (profile.kinds.includes(ProfileKind.IamRoleSourceProfile)) { + const profileData = await this.profileStore.load() + const sourceProfile = profileData.profiles.find(p => p.name === profile.settings?.source_profile) + // TODO: use other profile kinds while preventing infinite cycles + if (profile.kinds.includes(ProfileKind.IamUserProfile)) { + parentCredentials = { + accessKeyId: sourceProfile!.settings!.aws_access_key_id!, + secretAccessKey: sourceProfile!.settings!.aws_secret_access_key!, + sessionToken: sourceProfile?.settings?.aws_session_token, + } + } else { + throw new AwsError('Source credentials not found', AwsErrorCodes.E_INVALID_PROFILE) + } + } else { + throw new AwsError('Source credentials not found', AwsErrorCodes.E_INVALID_PROFILE) + } + + const stsClient = new STSClient({ + region: profile.settings?.region || 'us-east-1', + credentials: parentCredentials, + }) + + const mfaFields = + profile.settings?.mfa_serial && mfaCode + ? { SerialNumber: profile.settings?.mfa_serial, TokenCode: mfaCode } + : {} const command = new AssumeRoleCommand({ - RoleArn: roleArn, + RoleArn: profile.settings?.role_arn, RoleSessionName: `session-${Date.now()}`, DurationSeconds: 3600, ...mfaFields, From 7101c99ffe5205744d513437610c70aff7cfd773 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Fri, 11 Jul 2025 17:08:47 -0400 Subject: [PATCH 19/37] fix: check source profile instead of original profile --- server/aws-lsp-identity/src/language-server/identityService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index e0d6d89a06..9e5f852151 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -280,7 +280,7 @@ export class IdentityService { const profileData = await this.profileStore.load() const sourceProfile = profileData.profiles.find(p => p.name === profile.settings?.source_profile) // TODO: use other profile kinds while preventing infinite cycles - if (profile.kinds.includes(ProfileKind.IamUserProfile)) { + if (sourceProfile?.kinds.includes(ProfileKind.IamUserProfile)) { parentCredentials = { accessKeyId: sourceProfile!.settings!.aws_access_key_id!, secretAccessKey: sourceProfile!.settings!.aws_secret_access_key!, From a8f5a6c912dc5d99d68abaf2297d3a3c515a6cdb Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Mon, 14 Jul 2025 12:43:33 -0400 Subject: [PATCH 20/37] feat: allow any IAM profile type to be used as source profile --- .../language-server/identityService.test.ts | 76 ++++++++++++++++++- .../src/language-server/identityService.ts | 60 +++++++++------ 2 files changed, 108 insertions(+), 28 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index 1bc0f027ad..e13e6c7d99 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -14,8 +14,9 @@ import { import { SSOToken } from '@smithy/shared-ini-file-loader' import { Logging, Telemetry } from '@aws/language-server-runtimes/server-interface' import { Observability } from '@aws/lsp-core' -import { StsCache, StsCredential } from '../sts/cache/stsCache' +import { StsCache } from '../sts/cache/stsCache' import { StsAutoRefresher } from '../sts/stsAutoRefresher' +import { STSClient } from '@aws-sdk/client-sts' // eslint-disable-next-line use(require('chai-as-promised')) @@ -83,6 +84,46 @@ describe('IdentityService', () => { credential_process: 'my-process', }, }, + { + kinds: [ProfileKind.IamRoleSourceProfile], + name: 'cyclic-profile-1', + settings: { + role_arn: 'my-role-arn', + source_profile: 'cyclic-profile-1', + }, + }, + { + kinds: [ProfileKind.IamRoleSourceProfile], + name: 'cyclic-profile-2', + settings: { + role_arn: 'my-role-arn', + source_profile: 'cyclic-profile-3', + }, + }, + { + kinds: [ProfileKind.IamRoleSourceProfile], + name: 'cyclic-profile-3', + settings: { + role_arn: 'my-role-arn', + source_profile: 'cyclic-profile-2', + }, + }, + { + kinds: [ProfileKind.IamRoleSourceProfile], + name: 'base-profile', + settings: { + role_arn: 'my-role-arn', + source_profile: 'intermediate-profile', + }, + }, + { + kinds: [ProfileKind.IamRoleSourceProfile], + name: 'intermediate-profile', + settings: { + role_arn: 'my-role-arn', + source_profile: 'my-iam-profile', + }, + }, ], ssoSessions: [ { @@ -160,8 +201,7 @@ describe('IdentityService', () => { const validatePermissionsStub = stub(sut as any, 'validatePermissions') validatePermissionsStub.resolves(true) - const generateStsCredentialStub = stub(sut as any, 'generateStsCredential') - generateStsCredentialStub.resolves({ + stub(STSClient.prototype, 'send').resolves({ Credentials: { AccessKeyId: 'role-access-key', SecretAccessKey: 'role-secret-key', @@ -172,7 +212,7 @@ describe('IdentityService', () => { Arn: 'role-arn', AssumedRoleId: 'role-id', }, - } as StsCredential) + }) }) afterEach(() => { @@ -403,6 +443,34 @@ describe('IdentityService', () => { expect(actual.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') expect(stsAutoRefresher.watch.notCalled).to.be.true }) + + it('Can login with chained role source profiles.', async () => { + const actual = await sut.getIamCredential({ profileName: 'base-profile' }, CancellationToken.None) + + expect(actual.credentials.accessKeyId).to.equal('role-access-key') + expect(actual.credentials.secretAccessKey).to.equal('role-secret-key') + expect(actual.credentials.sessionToken).to.equal('role-session-token') + expect(actual.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') + expect(stsAutoRefresher.watch.called).to.be.true + }) + + it('Throws when role source profile points to itself.', async () => { + const error = await expect( + sut.getIamCredential({ profileName: 'cyclic-profile-1' }, CancellationToken.None) + ).rejectedWith(Error) + + expect(error.message).to.equal('Source profile chain exceeded max length.') + expect(stsAutoRefresher.watch.calledOnce).to.be.false + }) + + it('Throws when role source profiles form cycle.', async () => { + const error = await expect( + sut.getIamCredential({ profileName: 'cyclic-profile-2' }, CancellationToken.None) + ).rejectedWith(Error) + + expect(error.message).to.equal('Source profile chain exceeded max length.') + expect(stsAutoRefresher.watch.calledOnce).to.be.false + }) }) describe('invalidateSsoToken', () => { diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 9e5f852151..97b900e856 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -20,7 +20,6 @@ import { Profile, ProfileKind, } from '@aws/language-server-runtimes/server-interface' - import { normalizeSettingList, ProfileStore } from './profiles/profileService' import { authorizationCodePkceFlow, awsBuilderIdReservedName, awsBuilderIdSsoRegion } from '../sso' import { SsoCache, SsoClientRegistration } from '../sso/cache' @@ -46,12 +45,15 @@ type SsoTokenSource = IamIdentityCenterSsoTokenSource | AwsBuilderIdSsoTokenSour type AuthFlows = Record Promise> type Handlers = SsoHandlers & IamHandlers +const sourceProfileRecursionMax = 5 const flows: AuthFlows = { [AuthorizationFlowKind.DeviceCode]: deviceCodeFlow, [AuthorizationFlowKind.Pkce]: authorizationCodePkceFlow, } export class IdentityService { + private sourceProfileRecursionCount = 0 + constructor( private readonly profileStore: ProfileStore, private readonly ssoCache: SsoCache, @@ -196,11 +198,14 @@ export class IdentityService { throw new AwsError('Credentials could not be found for profile', AwsErrorCodes.E_INVALID_PROFILE) } - // Validate permissions - if (options.permissionSet.length > 0) { - const response = await simulatePermissions(credentials, options.permissionSet, profile.settings?.region) - if (!response?.EvaluationResults?.every(result => result.EvalDecision === 'allowed')) { - throw new AwsError(`Credentials have insufficient permissions.`, AwsErrorCodes.E_INVALID_PROFILE) + // Validate permissions on user or assumed role + if (options.validatePermissions) { + const hasPermissions = await this.validatePermissions(credentials, profile.settings?.region) + if (!hasPermissions) { + throw new AwsError( + `User or assumed role has insufficient permissions.`, + AwsErrorCodes.E_INVALID_PROFILE + ) } } @@ -211,6 +216,7 @@ export class IdentityService { updateCredentialsParams: { data: credentials, encrypted: false }, } } catch (e) { + this.sourceProfileRecursionCount = 0 emitMetric('Failed', e) throw e } @@ -273,26 +279,32 @@ export class IdentityService { return result } - private async generateStsCredential(profile: Profile, mfaCode?: string): Promise { - try { - let parentCredentials: IamCredentials - if (profile.kinds.includes(ProfileKind.IamRoleSourceProfile)) { - const profileData = await this.profileStore.load() - const sourceProfile = profileData.profiles.find(p => p.name === profile.settings?.source_profile) - // TODO: use other profile kinds while preventing infinite cycles - if (sourceProfile?.kinds.includes(ProfileKind.IamUserProfile)) { - parentCredentials = { - accessKeyId: sourceProfile!.settings!.aws_access_key_id!, - secretAccessKey: sourceProfile!.settings!.aws_secret_access_key!, - sessionToken: sourceProfile?.settings?.aws_session_token, - } - } else { - throw new AwsError('Source credentials not found', AwsErrorCodes.E_INVALID_PROFILE) - } + private async getParentCredential(profile: Profile): Promise { + let parentCredentials: IamCredentials + if (profile.kinds.includes(ProfileKind.IamRoleSourceProfile)) { + const parentOptions = { + profileName: profile.settings!.source_profile!, + // Do not validate Q permissions on source profile + options: { validatePermissions: false }, + } + // Obtain parent profile credentials if recursion count hasn't been exceeded from IamRoleSourceProfile chains + if (this.sourceProfileRecursionCount <= sourceProfileRecursionMax) { + this.sourceProfileRecursionCount += 1 + const parentResult = await this.getIamCredential(parentOptions, CancellationToken.None) + this.sourceProfileRecursionCount = 0 + parentCredentials = parentResult.credentials } else { - throw new AwsError('Source credentials not found', AwsErrorCodes.E_INVALID_PROFILE) + throw new AwsError('Source profile chain exceeded max length.', AwsErrorCodes.E_INVALID_PROFILE) } + } else { + throw new AwsError('Source credentials not found', AwsErrorCodes.E_INVALID_PROFILE) + } + return parentCredentials + } + private async generateStsCredential(profile: Profile, mfaCode?: string): Promise { + try { + const parentCredentials = await this.getParentCredential(profile) const stsClient = new STSClient({ region: profile.settings?.region || 'us-east-1', credentials: parentCredentials, @@ -313,7 +325,7 @@ export class IdentityService { return { Credentials, AssumedRoleUser } } catch (e) { this.observability.logging.log(`Error generating STS credentials.`) - throw new AwsError(`Error generating STS credentials.`, AwsErrorCodes.E_CANNOT_CREATE_STS_CREDENTIAL) + throw new AwsError((e as any).message, AwsErrorCodes.E_CANNOT_CREATE_STS_CREDENTIAL) } } From 27c78debdf1320fc322b580908f3facea66f18a3 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Mon, 14 Jul 2025 16:22:43 -0400 Subject: [PATCH 21/37] refactor: move MFA code retrieval into separate request --- .../src/language-server/identityServer.ts | 1 + .../language-server/identityService.test.ts | 26 ++--- .../src/language-server/identityService.ts | 104 ++++++++++++++---- 3 files changed, 91 insertions(+), 40 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/identityServer.ts b/server/aws-lsp-identity/src/language-server/identityServer.ts index 7c7b761fb4..cbf821d825 100644 --- a/server/aws-lsp-identity/src/language-server/identityServer.ts +++ b/server/aws-lsp-identity/src/language-server/identityServer.ts @@ -74,6 +74,7 @@ export class IdentityServer extends ServerBase { stsCache, stsAutoRefresher, { showUrl, showMessageRequest, showProgress }, + this.features.identityManagement.sendGetMfaCode, this.getClientName(params), this.observability ) diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index e13e6c7d99..7fe1ef0201 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -17,6 +17,7 @@ import { Observability } from '@aws/lsp-core' import { StsCache } from '../sts/cache/stsCache' import { StsAutoRefresher } from '../sts/stsAutoRefresher' import { STSClient } from '@aws-sdk/client-sts' +import { IAMClient } from '@aws-sdk/client-iam' // eslint-disable-next-line use(require('chai-as-promised')) @@ -190,6 +191,7 @@ describe('IdentityService', () => { showProgress: _ => Promise.resolve(), sendGetMfaCode: () => Promise.resolve({ code: 'mfa-code' }), }, + () => Promise.resolve({ code: 'mfa-code' }), 'My Client', observability, { @@ -198,9 +200,6 @@ describe('IdentityService', () => { } ) - const validatePermissionsStub = stub(sut as any, 'validatePermissions') - validatePermissionsStub.resolves(true) - stub(STSClient.prototype, 'send').resolves({ Credentials: { AccessKeyId: 'role-access-key', @@ -212,6 +211,11 @@ describe('IdentityService', () => { Arn: 'role-arn', AssumedRoleId: 'role-id', }, + Arn: 'role-arn', + }) + + stub(IAMClient.prototype, 'send').resolves({ + EvaluationResults: [], }) }) @@ -428,22 +432,6 @@ describe('IdentityService', () => { expect(stsAutoRefresher.watch.calledOnce).to.be.false }) - it('Can assume role with MFA.', async () => { - const actual = await sut.getIamCredential( - { - profileName: 'my-mfa-profile', - mfaCode: '123456', - }, - CancellationToken.None - ) - - expect(actual.credentials.accessKeyId).to.equal('role-access-key') - expect(actual.credentials.secretAccessKey).to.equal('role-secret-key') - expect(actual.credentials.sessionToken).to.equal('role-session-token') - expect(actual.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') - expect(stsAutoRefresher.watch.notCalled).to.be.true - }) - it('Can login with chained role source profiles.', async () => { const actual = await sut.getIamCredential({ profileName: 'base-profile' }, CancellationToken.None) diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 97b900e856..26a59525da 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -19,6 +19,8 @@ import { SsoTokenSourceKind, Profile, ProfileKind, + GetMfaCodeParams, + GetMfaCodeResult, } from '@aws/language-server-runtimes/server-interface' import { normalizeSettingList, ProfileStore } from './profiles/profileService' import { authorizationCodePkceFlow, awsBuilderIdReservedName, awsBuilderIdSsoRegion } from '../sso' @@ -35,11 +37,11 @@ import { } from '../sso/utils' import { IamHandlers, simulatePermissions } from '../iam/utils' import { AwsError, Observability } from '@aws/lsp-core' -import { GetCallerIdentityCommand, STSClient, AssumeRoleCommand } from '@aws-sdk/client-sts' +import { GetCallerIdentityCommand, STSClient, AssumeRoleCommand, AssumeRoleCommandInput } from '@aws-sdk/client-sts' import { __ServiceException } from '@aws-sdk/client-sso-oidc/dist-types/models/SSOOIDCServiceException' import { deviceCodeFlow } from '../sso/deviceCode/deviceCodeFlow' import { SSOToken } from '@smithy/shared-ini-file-loader' -import { IamProvider } from '../iam/iamProvider' +import { IAMClient, SimulatePrincipalPolicyCommand, SimulatePrincipalPolicyCommandOutput } from '@aws-sdk/client-iam' type SsoTokenSource = IamIdentityCenterSsoTokenSource | AwsBuilderIdSsoTokenSource type AuthFlows = Record Promise> @@ -50,6 +52,23 @@ const flows: AuthFlows = { [AuthorizationFlowKind.DeviceCode]: deviceCodeFlow, [AuthorizationFlowKind.Pkce]: authorizationCodePkceFlow, } +const qPermissions = [ + 'q:StartConversation', + 'q:SendMessage', + 'q:GetConversation', + 'q:ListConversations', + 'q:UpdateConversation', + 'q:DeleteConversation', + 'q:PassRequest', + 'q:StartTroubleshootingAnalysis', + 'q:StartTroubleshootingResolutionExplanation', + 'q:GetTroubleshootingResults', + 'q:UpdateTroubleshootingCommandResult', + 'q:GetIdentityMetaData', + 'q:GenerateCodeFromCommands', + 'q:UsePlugin', + 'codewhisperer:GenerateRecommendations', +] export class IdentityService { private sourceProfileRecursionCount = 0 @@ -61,6 +80,7 @@ export class IdentityService { private readonly stsCache: StsCache, private readonly stsAutoRefresher: StsAutoRefresher, private readonly handlers: SsoFlowParams['handlers'], + private readonly sendGetMfaCode: (params: GetMfaCodeParams) => Promise, private readonly clientName: string, private readonly observability: Observability, private readonly authFlows: AuthFlows = flows @@ -181,11 +201,7 @@ export class IdentityService { let credentials: IamCredentials // Assume the role matching the found ARN if (profile.kinds.includes(ProfileKind.IamRoleSourceProfile)) { - credentials = await this.getAssumedRoleCredential( - profile, - options.generateOnInvalidStsCredential, - params.mfaCode - ) + credentials = await this.getAssumedRoleCredential(profile, options.generateOnInvalidStsCredential) } // Get the credentials directly from the profile else if (profile.kinds.includes(ProfileKind.IamUserProfile)) { @@ -200,8 +216,8 @@ export class IdentityService { // Validate permissions on user or assumed role if (options.validatePermissions) { - const hasPermissions = await this.validatePermissions(credentials, profile.settings?.region) - if (!hasPermissions) { + const response = await this.simulatePermissions(credentials, qPermissions, profile.settings?.region) + if (!response?.EvaluationResults?.every(result => result.EvalDecision === 'allowed')) { throw new AwsError( `User or assumed role has insufficient permissions.`, AwsErrorCodes.E_INVALID_PROFILE @@ -224,8 +240,7 @@ export class IdentityService { private async getAssumedRoleCredential( profile: Profile, - generateOnInvalidStsCredential: boolean, - mfaCode?: string + generateOnInvalidStsCredential: boolean ): Promise { if (!profile.settings) { throw new AwsError('Profile settings not found when assuming role.', AwsErrorCodes.E_INVALID_PROFILE) @@ -244,7 +259,7 @@ export class IdentityService { } } else if (generateOnInvalidStsCredential) { // Generate STS credentials - const response = await this.generateStsCredential(profile, mfaCode) + const response = await this.generateStsCredential(profile) if (!response.Credentials) { throw new AwsError( 'Failed to assume role: No credentials returned', @@ -302,7 +317,7 @@ export class IdentityService { return parentCredentials } - private async generateStsCredential(profile: Profile, mfaCode?: string): Promise { + private async generateStsCredential(profile: Profile): Promise { try { const parentCredentials = await this.getParentCredential(profile) const stsClient = new STSClient({ @@ -310,22 +325,35 @@ export class IdentityService { credentials: parentCredentials, }) - const mfaFields = - profile.settings?.mfa_serial && mfaCode - ? { SerialNumber: profile.settings?.mfa_serial, TokenCode: mfaCode } - : {} - const command = new AssumeRoleCommand({ + // Add MFA fields to assume role request if MultiFactorAuthPresent is required + const assumeRoleInput: AssumeRoleCommandInput = { RoleArn: profile.settings?.role_arn, RoleSessionName: `session-${Date.now()}`, DurationSeconds: 3600, - ...mfaFields, - }) + } + const response = await this.simulatePermissions( + parentCredentials, + ['sts:AssumeRole'], + profile.settings?.region + ) + if (response.EvaluationResults?.[0]?.MissingContextValues?.includes('aws:MultiFactorAuthPresent')) { + if (!profile.settings?.mfa_serial) { + throw new AwsError( + 'MFA serial required when assuming role with MultiFactorAuthPresent condition', + AwsErrorCodes.E_INVALID_PROFILE + ) + } + assumeRoleInput.SerialNumber = profile.settings?.mfa_serial + // Request an MFA code from the language client + assumeRoleInput.TokenCode = (await this.sendGetMfaCode({})).code + } + const command = new AssumeRoleCommand(assumeRoleInput) const { Credentials, AssumedRoleUser } = await stsClient.send(command) return { Credentials, AssumedRoleUser } } catch (e) { this.observability.logging.log(`Error generating STS credentials.`) - throw new AwsError((e as any).message, AwsErrorCodes.E_CANNOT_CREATE_STS_CREDENTIAL) + throw e } } @@ -473,4 +501,38 @@ export class IdentityService { return ssoSession } + + // Returns whether the identity associated with the provided credentials has sufficient permissions + private async simulatePermissions( + credentials: IamCredentials, + permissions: string[], + region?: string + ): Promise { + // Get the identity associated with the credentials + const stsClient = new STSClient({ region: region || 'us-east-1', credentials: credentials }) + const identity = await stsClient.send(new GetCallerIdentityCommand({})) + if (!identity.Arn) { + throw new AwsError('Caller identity ARN not found.', AwsErrorCodes.E_INVALID_PROFILE) + } + + // Check the permissions attached to the identity + const iamClient = new IAMClient({ region: region || 'us-east-1', credentials: credentials }) + return await iamClient.send( + new SimulatePrincipalPolicyCommand({ + PolicySourceArn: this.convertToIamArn(identity.Arn), + ActionNames: permissions, + }) + ) + } + + // Converts an assumed role ARN into an IAM role ARN + private convertToIamArn(arn: string) { + if (arn.includes(':assumed-role/')) { + const parts = arn.split(':') + const roleName = parts[5].split('/')[1] + return `arn:aws:iam::${parts[4]}:role/${roleName}` + } else { + return arn + } + } } From f1e7c484200525aeb7084f10feb2ffb0ec79346e Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Mon, 14 Jul 2025 17:14:28 -0400 Subject: [PATCH 22/37] fix: add timeout to mfa request --- .../src/language-server/identityService.ts | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 26a59525da..c941d48a3f 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -48,6 +48,7 @@ type AuthFlows = Record Promis type Handlers = SsoHandlers & IamHandlers const sourceProfileRecursionMax = 5 +const mfaTimeout = 2 * 60 * 1000 // 2 minutes const flows: AuthFlows = { [AuthorizationFlowKind.DeviceCode]: deviceCodeFlow, [AuthorizationFlowKind.Pkce]: authorizationCodePkceFlow, @@ -337,15 +338,29 @@ export class IdentityService { profile.settings?.region ) if (response.EvaluationResults?.[0]?.MissingContextValues?.includes('aws:MultiFactorAuthPresent')) { + // Get the MFA device serial number from the profile if (!profile.settings?.mfa_serial) { throw new AwsError( - 'MFA serial required when assuming role with MultiFactorAuthPresent condition', - AwsErrorCodes.E_INVALID_PROFILE + 'MFA serial required when assuming role with MultiFactorAuthPresent permission condition', + AwsErrorCodes.E_MFA_REQUIRED ) } assumeRoleInput.SerialNumber = profile.settings?.mfa_serial // Request an MFA code from the language client - assumeRoleInput.TokenCode = (await this.sendGetMfaCode({})).code + const timeout = new Promise((_, reject) => + setTimeout( + () => reject(new AwsError('MFA code request timed out', AwsErrorCodes.E_MFA_REQUIRED)), + mfaTimeout + ) + ) + const response = await Promise.race([this.sendGetMfaCode({}), timeout]) + if (!response.code) { + throw new AwsError( + 'MFA code required when assuming role with MultiFactorAuthPresent permission condition', + AwsErrorCodes.E_MFA_REQUIRED + ) + } + assumeRoleInput.TokenCode = response.code } const command = new AssumeRoleCommand(assumeRoleInput) From c959675a11b11b95bac9d1bb18d537830cd9e3fe Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Mon, 14 Jul 2025 17:58:09 -0400 Subject: [PATCH 23/37] fix: add parameters to mfa request --- .../src/language-server/identityService.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index c941d48a3f..6f536fa3ea 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -353,7 +353,13 @@ export class IdentityService { mfaTimeout ) ) - const response = await Promise.race([this.sendGetMfaCode({}), timeout]) + const response = await Promise.race([ + this.sendGetMfaCode({ + mfaSerial: profile.settings?.mfa_serial, + profileName: profile.name, + }), + timeout, + ]) if (!response.code) { throw new AwsError( 'MFA code required when assuming role with MultiFactorAuthPresent permission condition', From 127fd3bf7e80bc8e65cfd8c80cf053ad4273c2b8 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Thu, 17 Jul 2025 12:29:34 -0400 Subject: [PATCH 24/37] fix: incorporate PR feedback --- .../aws-lsp-identity/src/iam/iamProvider.ts | 188 +++++++++++-- server/aws-lsp-identity/src/iam/utils.ts | 31 ++- .../src/language-server/identityServer.ts | 9 +- .../language-server/identityService.test.ts | 30 ++- .../src/language-server/identityService.ts | 247 ++---------------- .../profiles/sharedConfigProfileStore.test.ts | 16 +- .../profiles/sharedConfigProfileStore.ts | 2 + 7 files changed, 252 insertions(+), 271 deletions(-) diff --git a/server/aws-lsp-identity/src/iam/iamProvider.ts b/server/aws-lsp-identity/src/iam/iamProvider.ts index 42c3cdc7d5..cf1cd46d88 100644 --- a/server/aws-lsp-identity/src/iam/iamProvider.ts +++ b/server/aws-lsp-identity/src/iam/iamProvider.ts @@ -1,29 +1,177 @@ -import { AwsErrorCodes, IamCredentials, Profile, ProfileKind } from '@aws/language-server-runtimes/server-interface' -import { AwsError, Observability } from '@aws/lsp-core' -import { ProfileStore } from '../language-server/profiles/profileService' +import { AwsErrorCodes, IamCredentials, ProfileKind } from '@aws/language-server-runtimes/server-interface' +import { AwsError } from '@aws/lsp-core' +import { AssumeRoleCommand, AssumeRoleCommandInput, STSClient } from '@aws-sdk/client-sts' +import { IamFlowParams, simulatePermissions } from './utils' +import { StsCredential } from '../sts/cache/stsCache' + +const sourceProfileRecursionMax = 5 +const mfaTimeout = 2 * 60 * 1000 // 2 minutes export class IamProvider { - constructor( - private readonly observability: Observability, // In case we need telemetry and logging in the future - private readonly profileStore: ProfileStore // Will be used when assuming role with source_profile - ) {} - - async getCredential(profile: Profile, callStsOnInvalidIamCredential: boolean): Promise { - let credentials: IamCredentials - // Get the credentials directly from the profile - if (profile.kinds.includes(ProfileKind.IamCredentialsProfile)) { - credentials = { - accessKeyId: profile.settings!.aws_access_key_id!, - secretAccessKey: profile.settings!.aws_secret_access_key!, - sessionToken: profile.settings!.aws_session_token!, + private sourceProfileRecursionCount = 0 + + async getCredential(params: IamFlowParams): Promise { + try { + let credentials: IamCredentials + // Assume the role matching the found ARN + if (params.profile.kinds.includes(ProfileKind.IamSourceProfileProfile)) { + credentials = await this.getAssumedRoleCredential(params) + } + // Get the credentials directly from the profile + else if (params.profile.kinds.includes(ProfileKind.IamCredentialsProfile)) { + credentials = { + accessKeyId: params.profile.settings!.aws_access_key_id!, + secretAccessKey: params.profile.settings!.aws_secret_access_key!, + sessionToken: params.profile.settings!.aws_session_token!, + } + } else { + throw new AwsError( + 'Credentials could not be found for provided profile kind', + AwsErrorCodes.E_INVALID_PROFILE + ) + } + + return credentials + } catch (e) { + this.sourceProfileRecursionCount = 0 + throw e + } + } + + private async getAssumedRoleCredential(params: IamFlowParams): Promise { + if (!params.profile.settings) { + throw new AwsError('Profile settings not found when assuming role.', AwsErrorCodes.E_INVALID_PROFILE) + } + + // Try to get the STS credentials from cache + let result: IamCredentials + const stsCredentials = await params.stsCache.getStsCredential(params.profile.name).catch(_ => undefined) + + if (stsCredentials?.Credentials) { + result = { + accessKeyId: stsCredentials.Credentials.AccessKeyId!, + secretAccessKey: stsCredentials.Credentials.SecretAccessKey!, + sessionToken: stsCredentials.Credentials.SessionToken!, + expiration: stsCredentials.Credentials.Expiration!, + } + } else if (params.callStsOnInvalidIamCredential) { + // Generate STS credentials + const response = await this.generateStsCredential(params) + if (!response.Credentials) { + throw new AwsError( + 'Failed to assume role: No credentials returned', + AwsErrorCodes.E_INVALID_STS_CREDENTIAL + ) + } + // Cache STS credentials + await params.stsCache.setStsCredential(params.profile.name, response) + result = { + accessKeyId: response.Credentials.AccessKeyId!, + secretAccessKey: response.Credentials.SecretAccessKey!, + sessionToken: response.Credentials.SessionToken!, // Always present in STS response + expiration: response.Credentials.Expiration!, } } else { - throw new AwsError( - 'Credentials could not be found for provided profile kind', - AwsErrorCodes.E_INVALID_PROFILE + // If we could not get the cached STS credential and cannot generate a new credential, give up + params.observability.logging.log( + 'STS credential not found an generateOnInvalidStsCredential = false, returning no credential.' ) + throw new AwsError('STS credential not found.', AwsErrorCodes.E_INVALID_STS_CREDENTIAL) + } + + // Set up auto-refresh if MFA is disabled + if (!params.profile.settings.mfa_serial) { + await params.stsAutoRefresher + .watch(params.profile.name, () => this.generateStsCredential(params)) + .catch(reason => { + params.observability.logging.log(`Unable to auto-refresh STS credentials. ${reason}`) + }) } - return credentials + return result + } + + private async getParentCredential(params: IamFlowParams): Promise { + let parentCredentials: IamCredentials + if (params.profile.kinds.includes(ProfileKind.IamSourceProfileProfile)) { + // Get the source profile + const profileData = await params.profileStore.load() + const sourceProfile = profileData.profiles.find(p => p.name === params.profile.settings!.source_profile!) + if (!sourceProfile) { + params.observability.logging.log('Source profile not found.') + throw new AwsError('Source profile not found.', AwsErrorCodes.E_PROFILE_NOT_FOUND) + } + // Obtain parent profile credentials if IamRoleSourceProfile chain isn't too long + if (this.sourceProfileRecursionCount <= sourceProfileRecursionMax) { + this.sourceProfileRecursionCount += 1 + parentCredentials = await this.getCredential({ ...params, profile: sourceProfile }) + this.sourceProfileRecursionCount = 0 + } else { + throw new AwsError('Source profile chain exceeded max length.', AwsErrorCodes.E_INVALID_PROFILE) + } + } else { + throw new AwsError('Source credentials not found', AwsErrorCodes.E_INVALID_PROFILE) + } + return parentCredentials + } + + private async generateStsCredential(params: IamFlowParams): Promise { + try { + const parentCredentials = await this.getParentCredential(params) + const stsClient = new STSClient({ + region: params.profile.settings?.region || 'us-east-1', + credentials: parentCredentials, + }) + + // Add MFA fields to assume role request if MultiFactorAuthPresent is required + const assumeRoleInput: AssumeRoleCommandInput = { + RoleArn: params.profile.settings?.role_arn, + RoleSessionName: `session-${Date.now()}`, + DurationSeconds: 3600, + } + const response = await simulatePermissions( + parentCredentials, + ['sts:AssumeRole'], + params.profile.settings?.region + ) + if (response.EvaluationResults?.[0]?.MissingContextValues?.includes('aws:MultiFactorAuthPresent')) { + // Get the MFA device serial number from the profile + if (!params.profile.settings?.mfa_serial) { + throw new AwsError( + 'MFA serial required when assuming role with MultiFactorAuthPresent permission condition', + AwsErrorCodes.E_MFA_REQUIRED + ) + } + assumeRoleInput.SerialNumber = params.profile.settings?.mfa_serial + // Request an MFA code from the language client + const timeout = new Promise((_, reject) => + setTimeout( + () => reject(new AwsError('MFA code request timed out', AwsErrorCodes.E_MFA_REQUIRED)), + mfaTimeout + ) + ) + const response = await Promise.race([ + params.handlers.sendGetMfaCode({ + mfaSerial: params.profile.settings?.mfa_serial, + profileName: params.profile.name, + }), + timeout, + ]) + if (!response.code) { + throw new AwsError( + 'MFA code required when assuming role with MultiFactorAuthPresent permission condition', + AwsErrorCodes.E_MFA_REQUIRED + ) + } + assumeRoleInput.TokenCode = response.code + } + + const command = new AssumeRoleCommand(assumeRoleInput) + const { Credentials, AssumedRoleUser } = await stsClient.send(command) + return { Credentials, AssumedRoleUser } + } catch (e) { + params.observability.logging.log(`Error generating STS credentials.`) + throw e + } } } diff --git a/server/aws-lsp-identity/src/iam/utils.ts b/server/aws-lsp-identity/src/iam/utils.ts index 06467906a9..e18ceea2c6 100644 --- a/server/aws-lsp-identity/src/iam/utils.ts +++ b/server/aws-lsp-identity/src/iam/utils.ts @@ -2,11 +2,16 @@ import { IAMClient, SimulatePrincipalPolicyCommand, SimulatePrincipalPolicyComma import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts' import { AwsErrorCodes, + CancellationToken, GetMfaCodeParams, GetMfaCodeResult, IamCredentials, + Profile, } from '@aws/language-server-runtimes/server-interface' -import { AwsError } from '@aws/lsp-core' +import { AwsError, Observability } from '@aws/lsp-core' +import { StsCache } from '../sts/cache/stsCache' +import { StsAutoRefresher } from '../sts/stsAutoRefresher' +import { ProfileStore } from '../language-server/profiles/profileService' // Simulate permissions on the identity associated with the credentials export async function simulatePermissions( @@ -25,14 +30,36 @@ export async function simulatePermissions( const iamClient = new IAMClient({ region: region || 'us-east-1', credentials: credentials }) return await iamClient.send( new SimulatePrincipalPolicyCommand({ - PolicySourceArn: identity.Arn, + PolicySourceArn: convertToIamArn(identity.Arn), ActionNames: permissions, }) ) } +// Converts an assumed role ARN into an IAM role ARN +function convertToIamArn(arn: string) { + if (arn.includes(':assumed-role/')) { + const parts = arn.split(':') + const roleName = parts[5].split('/')[1] + return `arn:aws:iam::${parts[4]}:role/${roleName}` + } else { + return arn + } +} + export type SendGetMfaCode = (params: GetMfaCodeParams) => Promise export type IamHandlers = { sendGetMfaCode: SendGetMfaCode } + +export type IamFlowParams = { + profile: Profile + callStsOnInvalidIamCredential: boolean + profileStore: ProfileStore + stsCache: StsCache + stsAutoRefresher: StsAutoRefresher + handlers: IamHandlers + token: CancellationToken + observability: Observability +} diff --git a/server/aws-lsp-identity/src/language-server/identityServer.ts b/server/aws-lsp-identity/src/language-server/identityServer.ts index cbf821d825..c0f3cd290a 100644 --- a/server/aws-lsp-identity/src/language-server/identityServer.ts +++ b/server/aws-lsp-identity/src/language-server/identityServer.ts @@ -46,8 +46,7 @@ export class IdentityServer extends ServerBase { const showMessageRequest: ShowMessageRequest = (params: ShowMessageRequestParams) => this.features.lsp.window.showMessageRequest(params) const showProgress: ShowProgress = this.features.lsp.sendProgress - const sendGetMfaCode: SendGetMfaCode = (params: GetMfaCodeParams) => - this.features.identityManagement.sendGetMfaCode(params) + const sendGetMfaCode: SendGetMfaCode = this.features.identityManagement.sendGetMfaCode // Initialize dependencies const profileStore = new SharedConfigProfileStore(this.observability) @@ -59,13 +58,13 @@ export class IdentityServer extends ServerBase { ) const autoRefresher = new SsoTokenAutoRefresher(ssoCache, this.observability) - const stsCache = new RefreshingStsCache(new FileSystemStsCache(this.observability), this.observability) const stsAutoRefresher = new StsAutoRefresher( stsCache, this.features.identityManagement.sendStsCredentialChanged, this.observability ) + const iamProvider = new IamProvider() const identityService = new IdentityService( profileStore, @@ -73,8 +72,8 @@ export class IdentityServer extends ServerBase { autoRefresher, stsCache, stsAutoRefresher, - { showUrl, showMessageRequest, showProgress }, - this.features.identityManagement.sendGetMfaCode, + iamProvider, + { showUrl, showMessageRequest, showProgress, sendGetMfaCode }, this.getClientName(params), this.observability ) diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index 7fe1ef0201..5c0a343e0d 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -18,6 +18,7 @@ import { StsCache } from '../sts/cache/stsCache' import { StsAutoRefresher } from '../sts/stsAutoRefresher' import { STSClient } from '@aws-sdk/client-sts' import { IAMClient } from '@aws-sdk/client-iam' +import { IamProvider } from '../iam/iamProvider' // eslint-disable-next-line use(require('chai-as-promised')) @@ -29,6 +30,7 @@ let ssoCache: StubbedInstance let stsCache: StubbedInstance let autoRefresher: StubbedInstance let stsAutoRefresher: StubbedInstance +let iamProvider: IamProvider let observability: StubbedInstance let authFlowFn: SinonSpy @@ -168,6 +170,8 @@ describe('IdentityService', () => { unwatch: undefined, }) as StubbedInstance + iamProvider = new IamProvider() + authFlowFn = spy(() => Promise.resolve({ accessToken: 'my-access-token', @@ -185,13 +189,13 @@ describe('IdentityService', () => { autoRefresher, stsCache, stsAutoRefresher, + iamProvider, { showUrl: _ => {}, showMessageRequest: _ => Promise.resolve({ title: 'client-response' }), showProgress: _ => Promise.resolve(), sendGetMfaCode: () => Promise.resolve({ code: 'mfa-code' }), }, - () => Promise.resolve({ code: 'mfa-code' }), 'My Client', observability, { @@ -387,10 +391,10 @@ describe('IdentityService', () => { it('Can login with assumed role.', async () => { const actual = await sut.getIamCredential({ profileName: 'my-role-profile' }, CancellationToken.None) - expect(actual.credentials.accessKeyId).to.equal('role-access-key') - expect(actual.credentials.secretAccessKey).to.equal('role-secret-key') - expect(actual.credentials.sessionToken).to.equal('role-session-token') - expect(actual.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') + expect(actual.credential.credentials.accessKeyId).to.equal('role-access-key') + expect(actual.credential.credentials.secretAccessKey).to.equal('role-secret-key') + expect(actual.credential.credentials.sessionToken).to.equal('role-session-token') + expect(actual.credential.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') expect(stsAutoRefresher.watch.calledOnce).to.be.true }) @@ -410,10 +414,10 @@ describe('IdentityService', () => { })) as any const actual = await sut.getIamCredential({ profileName: 'my-role-profile' }, CancellationToken.None) - expect(actual.credentials.accessKeyId).to.equal('other-access-key') - expect(actual.credentials.secretAccessKey).to.equal('other-secret-key') - expect(actual.credentials.sessionToken).to.equal('other-session-token') - expect(actual.credentials.expiration?.toISOString()).to.equal('2024-10-25T18:09:20.455Z') + expect(actual.credential.credentials.accessKeyId).to.equal('other-access-key') + expect(actual.credential.credentials.secretAccessKey).to.equal('other-secret-key') + expect(actual.credential.credentials.sessionToken).to.equal('other-session-token') + expect(actual.credential.credentials.expiration?.toISOString()).to.equal('2024-10-25T18:09:20.455Z') expect(stsAutoRefresher.watch.calledOnce).to.be.true }) @@ -435,10 +439,10 @@ describe('IdentityService', () => { it('Can login with chained role source profiles.', async () => { const actual = await sut.getIamCredential({ profileName: 'base-profile' }, CancellationToken.None) - expect(actual.credentials.accessKeyId).to.equal('role-access-key') - expect(actual.credentials.secretAccessKey).to.equal('role-secret-key') - expect(actual.credentials.sessionToken).to.equal('role-session-token') - expect(actual.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') + expect(actual.credential.credentials.accessKeyId).to.equal('role-access-key') + expect(actual.credential.credentials.secretAccessKey).to.equal('role-secret-key') + expect(actual.credential.credentials.sessionToken).to.equal('role-session-token') + expect(actual.credential.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') expect(stsAutoRefresher.watch.called).to.be.true }) diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 6f536fa3ea..0671ad7e9d 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -17,16 +17,12 @@ import { MetricEvent, SsoSession, SsoTokenSourceKind, - Profile, - ProfileKind, - GetMfaCodeParams, - GetMfaCodeResult, } from '@aws/language-server-runtimes/server-interface' import { normalizeSettingList, ProfileStore } from './profiles/profileService' import { authorizationCodePkceFlow, awsBuilderIdReservedName, awsBuilderIdSsoRegion } from '../sso' import { SsoCache, SsoClientRegistration } from '../sso/cache' import { SsoTokenAutoRefresher } from './ssoTokenAutoRefresher' -import { StsCache, StsCredential } from '../sts/cache/stsCache' +import { StsCache } from '../sts/cache/stsCache' import { StsAutoRefresher } from '../sts/stsAutoRefresher' import { throwOnInvalidClientRegistration, @@ -35,53 +31,31 @@ import { SsoFlowParams, SsoHandlers, } from '../sso/utils' -import { IamHandlers, simulatePermissions } from '../iam/utils' +import { IamFlowParams, IamHandlers, simulatePermissions } from '../iam/utils' import { AwsError, Observability } from '@aws/lsp-core' -import { GetCallerIdentityCommand, STSClient, AssumeRoleCommand, AssumeRoleCommandInput } from '@aws-sdk/client-sts' import { __ServiceException } from '@aws-sdk/client-sso-oidc/dist-types/models/SSOOIDCServiceException' import { deviceCodeFlow } from '../sso/deviceCode/deviceCodeFlow' import { SSOToken } from '@smithy/shared-ini-file-loader' -import { IAMClient, SimulatePrincipalPolicyCommand, SimulatePrincipalPolicyCommandOutput } from '@aws-sdk/client-iam' +import { IamProvider } from '../iam/iamProvider' type SsoTokenSource = IamIdentityCenterSsoTokenSource | AwsBuilderIdSsoTokenSource type AuthFlows = Record Promise> type Handlers = SsoHandlers & IamHandlers -const sourceProfileRecursionMax = 5 -const mfaTimeout = 2 * 60 * 1000 // 2 minutes const flows: AuthFlows = { [AuthorizationFlowKind.DeviceCode]: deviceCodeFlow, [AuthorizationFlowKind.Pkce]: authorizationCodePkceFlow, } -const qPermissions = [ - 'q:StartConversation', - 'q:SendMessage', - 'q:GetConversation', - 'q:ListConversations', - 'q:UpdateConversation', - 'q:DeleteConversation', - 'q:PassRequest', - 'q:StartTroubleshootingAnalysis', - 'q:StartTroubleshootingResolutionExplanation', - 'q:GetTroubleshootingResults', - 'q:UpdateTroubleshootingCommandResult', - 'q:GetIdentityMetaData', - 'q:GenerateCodeFromCommands', - 'q:UsePlugin', - 'codewhisperer:GenerateRecommendations', -] export class IdentityService { - private sourceProfileRecursionCount = 0 - constructor( private readonly profileStore: ProfileStore, private readonly ssoCache: SsoCache, private readonly autoRefresher: SsoTokenAutoRefresher, private readonly stsCache: StsCache, private readonly stsAutoRefresher: StsAutoRefresher, - private readonly handlers: SsoFlowParams['handlers'], - private readonly sendGetMfaCode: (params: GetMfaCodeParams) => Promise, + private readonly iamProvider: IamProvider, + private readonly handlers: Handlers, private readonly clientName: string, private readonly observability: Observability, private readonly authFlows: AuthFlows = flows @@ -199,185 +173,38 @@ export class IdentityService { throw new AwsError('Profile not found.', AwsErrorCodes.E_PROFILE_NOT_FOUND) } - let credentials: IamCredentials - // Assume the role matching the found ARN - if (profile.kinds.includes(ProfileKind.IamRoleSourceProfile)) { - credentials = await this.getAssumedRoleCredential(profile, options.generateOnInvalidStsCredential) - } - // Get the credentials directly from the profile - else if (profile.kinds.includes(ProfileKind.IamUserProfile)) { - credentials = { - accessKeyId: profile.settings!.aws_access_key_id!, - secretAccessKey: profile.settings!.aws_secret_access_key!, - sessionToken: profile.settings!.aws_session_token!, - } - } else { - throw new AwsError('Credentials could not be found for profile', AwsErrorCodes.E_INVALID_PROFILE) + const flowOpts: IamFlowParams = { + profile: profile, + callStsOnInvalidIamCredential: options.callStsOnInvalidIamCredential, + profileStore: this.profileStore, + stsCache: this.stsCache, + stsAutoRefresher: this.stsAutoRefresher, + handlers: { sendGetMfaCode: this.handlers.sendGetMfaCode }, + token: token, + observability: this.observability, } + const credentials = await this.iamProvider.getCredential(flowOpts) - // Validate permissions on user or assumed role - if (options.validatePermissions) { - const response = await this.simulatePermissions(credentials, qPermissions, profile.settings?.region) + // Validate permissions + if (options.permissionSet.length > 0) { + const response = await simulatePermissions(credentials, options.permissionSet, profile.settings?.region) if (!response?.EvaluationResults?.every(result => result.EvalDecision === 'allowed')) { - throw new AwsError( - `User or assumed role has insufficient permissions.`, - AwsErrorCodes.E_INVALID_PROFILE - ) + throw new AwsError(`Credentials have insufficient permissions.`, AwsErrorCodes.E_INVALID_PROFILE) } } emitMetric('Succeeded') return { - credential: { id: params.profileName, kinds: profile.kinds, credentials: credentials }, + credential: { id: params.profileName, credentials: credentials }, updateCredentialsParams: { data: credentials, encrypted: false }, } } catch (e) { - this.sourceProfileRecursionCount = 0 emitMetric('Failed', e) throw e } } - private async getAssumedRoleCredential( - profile: Profile, - generateOnInvalidStsCredential: boolean - ): Promise { - if (!profile.settings) { - throw new AwsError('Profile settings not found when assuming role.', AwsErrorCodes.E_INVALID_PROFILE) - } - - // Try to get the STS credentials from cache - let result: IamCredentials - const stsCredentials = await this.stsCache.getStsCredential(profile.name).catch(_ => undefined) - - if (stsCredentials?.Credentials) { - result = { - accessKeyId: stsCredentials.Credentials.AccessKeyId!, - secretAccessKey: stsCredentials.Credentials.SecretAccessKey!, - sessionToken: stsCredentials.Credentials.SessionToken!, - expiration: stsCredentials.Credentials.Expiration!, - } - } else if (generateOnInvalidStsCredential) { - // Generate STS credentials - const response = await this.generateStsCredential(profile) - if (!response.Credentials) { - throw new AwsError( - 'Failed to assume role: No credentials returned', - AwsErrorCodes.E_INVALID_STS_CREDENTIAL - ) - } - // Cache STS credentials - await this.stsCache.setStsCredential(profile.name, response) - result = { - accessKeyId: response.Credentials.AccessKeyId!, - secretAccessKey: response.Credentials.SecretAccessKey!, - sessionToken: response.Credentials.SessionToken!, // Always present in STS response - expiration: response.Credentials.Expiration!, - } - } else { - // If we could not get the cached STS credential and cannot generate a new credential, give up - this.observability.logging.log( - 'STS credential not found an generateOnInvalidStsCredential = false, returning no credential.' - ) - throw new AwsError('STS credential not found.', AwsErrorCodes.E_INVALID_STS_CREDENTIAL) - } - - // Set up auto-refresh if MFA is disabled - if (!profile.settings.mfa_serial) { - await this.stsAutoRefresher - .watch(profile.name, () => this.generateStsCredential(profile)) - .catch(reason => { - this.observability.logging.log(`Unable to auto-refresh STS credentials. ${reason}`) - }) - } - - return result - } - - private async getParentCredential(profile: Profile): Promise { - let parentCredentials: IamCredentials - if (profile.kinds.includes(ProfileKind.IamRoleSourceProfile)) { - const parentOptions = { - profileName: profile.settings!.source_profile!, - // Do not validate Q permissions on source profile - options: { validatePermissions: false }, - } - // Obtain parent profile credentials if recursion count hasn't been exceeded from IamRoleSourceProfile chains - if (this.sourceProfileRecursionCount <= sourceProfileRecursionMax) { - this.sourceProfileRecursionCount += 1 - const parentResult = await this.getIamCredential(parentOptions, CancellationToken.None) - this.sourceProfileRecursionCount = 0 - parentCredentials = parentResult.credentials - } else { - throw new AwsError('Source profile chain exceeded max length.', AwsErrorCodes.E_INVALID_PROFILE) - } - } else { - throw new AwsError('Source credentials not found', AwsErrorCodes.E_INVALID_PROFILE) - } - return parentCredentials - } - - private async generateStsCredential(profile: Profile): Promise { - try { - const parentCredentials = await this.getParentCredential(profile) - const stsClient = new STSClient({ - region: profile.settings?.region || 'us-east-1', - credentials: parentCredentials, - }) - - // Add MFA fields to assume role request if MultiFactorAuthPresent is required - const assumeRoleInput: AssumeRoleCommandInput = { - RoleArn: profile.settings?.role_arn, - RoleSessionName: `session-${Date.now()}`, - DurationSeconds: 3600, - } - const response = await this.simulatePermissions( - parentCredentials, - ['sts:AssumeRole'], - profile.settings?.region - ) - if (response.EvaluationResults?.[0]?.MissingContextValues?.includes('aws:MultiFactorAuthPresent')) { - // Get the MFA device serial number from the profile - if (!profile.settings?.mfa_serial) { - throw new AwsError( - 'MFA serial required when assuming role with MultiFactorAuthPresent permission condition', - AwsErrorCodes.E_MFA_REQUIRED - ) - } - assumeRoleInput.SerialNumber = profile.settings?.mfa_serial - // Request an MFA code from the language client - const timeout = new Promise((_, reject) => - setTimeout( - () => reject(new AwsError('MFA code request timed out', AwsErrorCodes.E_MFA_REQUIRED)), - mfaTimeout - ) - ) - const response = await Promise.race([ - this.sendGetMfaCode({ - mfaSerial: profile.settings?.mfa_serial, - profileName: profile.name, - }), - timeout, - ]) - if (!response.code) { - throw new AwsError( - 'MFA code required when assuming role with MultiFactorAuthPresent permission condition', - AwsErrorCodes.E_MFA_REQUIRED - ) - } - assumeRoleInput.TokenCode = response.code - } - - const command = new AssumeRoleCommand(assumeRoleInput) - const { Credentials, AssumedRoleUser } = await stsClient.send(command) - return { Credentials, AssumedRoleUser } - } catch (e) { - this.observability.logging.log(`Error generating STS credentials.`) - throw e - } - } - async invalidateSsoToken( params: InvalidateSsoTokenParams, token: CancellationToken @@ -522,38 +349,4 @@ export class IdentityService { return ssoSession } - - // Returns whether the identity associated with the provided credentials has sufficient permissions - private async simulatePermissions( - credentials: IamCredentials, - permissions: string[], - region?: string - ): Promise { - // Get the identity associated with the credentials - const stsClient = new STSClient({ region: region || 'us-east-1', credentials: credentials }) - const identity = await stsClient.send(new GetCallerIdentityCommand({})) - if (!identity.Arn) { - throw new AwsError('Caller identity ARN not found.', AwsErrorCodes.E_INVALID_PROFILE) - } - - // Check the permissions attached to the identity - const iamClient = new IAMClient({ region: region || 'us-east-1', credentials: credentials }) - return await iamClient.send( - new SimulatePrincipalPolicyCommand({ - PolicySourceArn: this.convertToIamArn(identity.Arn), - ActionNames: permissions, - }) - ) - } - - // Converts an assumed role ARN into an IAM role ARN - private convertToIamArn(arn: string) { - if (arn.includes(':assumed-role/')) { - const parts = arn.split(':') - const roleName = parts[5].split('/')[1] - return `arn:aws:iam::${parts[4]}:role/${roleName}` - } else { - return arn - } - } } diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts index 227003dbd8..d8783974c5 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts @@ -99,7 +99,9 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: {}, + settings: { + region: undefined, + }, }, { kinds: [ProfileKind.SsoTokenProfile], @@ -190,7 +192,9 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: {}, + settings: { + region: undefined, + }, }, { kinds: [ProfileKind.SsoTokenProfile], @@ -286,7 +290,9 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: {}, + settings: { + region: undefined, + }, }, { kinds: ['SsoTokenProfile'], @@ -434,7 +440,9 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: {}, + settings: { + region: undefined, + }, }, ], ssoSessions: [ diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts index 0b612a46a0..a1cdbcca28 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts @@ -67,6 +67,8 @@ export class SharedConfigProfileStore implements ProfileStore { // If the profile does not match any profile type, mark it as an unknown profile if (profile.kinds.length === 0) { profile.kinds.push(ProfileKind.Unknown) + // Dummy field to avoid deleting profile when loading and saving 0 changes to the profile + profile.settings!['region'] = settings['region'] } result.profiles.push(profile) break From 2b76c0d17b7a3c61f4876b258ced0f4bb5083209 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Thu, 17 Jul 2025 16:20:57 -0400 Subject: [PATCH 25/37] refactor: move unit tests from identityService to iamProvider --- .../src/iam/iamProvider.test.ts | 316 ++++++++++++++++++ .../language-server/identityService.test.ts | 188 +---------- 2 files changed, 331 insertions(+), 173 deletions(-) create mode 100644 server/aws-lsp-identity/src/iam/iamProvider.test.ts diff --git a/server/aws-lsp-identity/src/iam/iamProvider.test.ts b/server/aws-lsp-identity/src/iam/iamProvider.test.ts new file mode 100644 index 0000000000..96fb09db1b --- /dev/null +++ b/server/aws-lsp-identity/src/iam/iamProvider.test.ts @@ -0,0 +1,316 @@ +import { expect, use } from 'chai' +import { StubbedInstance, stubInterface } from 'ts-sinon' +import { ProfileData, ProfileStore } from '../language-server/profiles/profileService' +import { createStubInstance, restore, SinonStub, stub } from 'sinon' +import { CancellationToken, Profile, ProfileKind } from '@aws/language-server-runtimes/protocol' +import { Logging, Telemetry } from '@aws/language-server-runtimes/server-interface' +import { IamCredentials, Observability } from '@aws/lsp-core' +import { StsCache } from '../sts/cache/stsCache' +import { StsAutoRefresher } from '../sts/stsAutoRefresher' +import { IamProvider } from '../iam/iamProvider' +import { IamFlowParams } from './utils' +import * as iamUtils from '../iam/utils' +import { STSClient } from '@aws-sdk/client-sts' +import { SimulatePrincipalPolicyCommandOutput } from '@aws-sdk/client-iam' + +// eslint-disable-next-line +use(require('chai-as-promised')) + +let sut: IamProvider +let defaultParams: IamFlowParams +let defaultProfile: Profile +let profileStore: StubbedInstance +let stsCache: StubbedInstance +let stsAutoRefresher: StubbedInstance +let handlers: StubbedInstance +let observability: StubbedInstance +let token: StubbedInstance +let simulatePermissionsStub: SinonStub< + [credentials: IamCredentials, permissions: string[], region?: string | undefined], + Promise +> + +describe('IamProvider', () => { + beforeEach(() => { + defaultProfile = { + kinds: [ProfileKind.Unknown], + name: 'default-profile', + } + + profileStore = stubInterface({ + load: Promise.resolve({ + profiles: [ + { + kinds: [ProfileKind.IamSourceProfileProfile], + name: 'cyclic-profile-1', + settings: { + role_arn: 'my-role-arn', + source_profile: 'cyclic-profile-1', + }, + }, + { + kinds: [ProfileKind.IamSourceProfileProfile], + name: 'cyclic-profile-2', + settings: { + role_arn: 'my-role-arn', + source_profile: 'cyclic-profile-3', + }, + }, + { + kinds: [ProfileKind.IamSourceProfileProfile], + name: 'cyclic-profile-3', + settings: { + role_arn: 'my-role-arn', + source_profile: 'cyclic-profile-2', + }, + }, + { + kinds: [ProfileKind.IamSourceProfileProfile], + name: 'base-profile', + settings: { + role_arn: 'my-role-arn', + source_profile: 'intermediate-profile', + }, + }, + { + kinds: [ProfileKind.IamSourceProfileProfile], + name: 'intermediate-profile', + settings: { + role_arn: 'my-role-arn', + source_profile: 'my-iam-profile', + }, + }, + { + kinds: [ProfileKind.IamCredentialsProfile], + name: 'my-iam-profile', + settings: { + aws_access_key_id: 'my-access-key', + aws_secret_access_key: 'my-secret-key', + }, + }, + ], + ssoSessions: [], + } satisfies ProfileData), + }) + + stsCache = stubInterface({ + getStsCredential: Promise.resolve(undefined), + setStsCredential: Promise.resolve(), + removeStsCredential: Promise.resolve(), + }) + + stsAutoRefresher = createStubInstance(StsAutoRefresher, { + watch: Promise.resolve(), + unwatch: undefined, + }) as StubbedInstance + + observability = stubInterface() + observability.logging = stubInterface() + observability.telemetry = stubInterface() + + handlers = stubInterface({ + sendGetMfaCode: Promise.resolve({ code: 'mfa-code' }), + }) + + token = stubInterface() + + defaultParams = { + profile: defaultProfile, + callStsOnInvalidIamCredential: true, + profileStore: profileStore, + stsCache: stsCache, + stsAutoRefresher: stsAutoRefresher, + handlers: handlers, + token: token, + observability: observability, + } + + sut = new IamProvider() + + simulatePermissionsStub = stub(iamUtils, 'simulatePermissions') + simulatePermissionsStub.resolves({ + $metadata: {}, + EvaluationResults: [], + }) + + stub(STSClient.prototype, 'send').resolves({ + Credentials: { + AccessKeyId: 'role-access-key', + SecretAccessKey: 'role-secret-key', + SessionToken: 'role-session-token', + Expiration: new Date('2024-09-25T18:09:20.455Z'), + }, + AssumedRoleUser: { + Arn: 'role-arn', + AssumedRoleId: 'role-id', + }, + }) + }) + + afterEach(() => { + restore() + }) + + describe('getCredential', () => { + it('Can get credentials from profile', async () => { + const profile: Profile = { + kinds: [ProfileKind.IamCredentialsProfile], + name: 'iam-profile', + settings: { + aws_access_key_id: 'access-key', + aws_secret_access_key: 'secret-key', + aws_session_token: 'session-token', + }, + } + const actual = await sut.getCredential({ ...defaultParams, profile: profile }) + + expect(actual.accessKeyId).to.equal('access-key') + expect(actual.secretAccessKey).to.equal('secret-key') + expect(actual.sessionToken).to.equal('session-token') + }) + + it('Can generate credentials by assuming role.', async () => { + const profile: Profile = { + kinds: [ProfileKind.IamSourceProfileProfile], + name: 'my-role-profile', + settings: { + role_arn: 'my-role-arn', + source_profile: 'my-iam-profile', + }, + } + const actual = await sut.getCredential({ ...defaultParams, profile: profile }) + + expect(actual.accessKeyId).to.equal('role-access-key') + expect(actual.secretAccessKey).to.equal('role-secret-key') + expect(actual.sessionToken).to.equal('role-session-token') + expect(actual.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') + expect(stsAutoRefresher.watch.calledOnce).to.be.true + }) + + it('Can generate credentials with MFA.', async () => { + simulatePermissionsStub.resolves({ + $metadata: {}, + EvaluationResults: [ + { + EvalActionName: 'name', + EvalResourceName: 'resource', + EvalDecision: 'implicitDeny', + MissingContextValues: ['aws:MultiFactorAuthPresent'], + }, + ], + }) + const profile: Profile = { + kinds: [ProfileKind.IamSourceProfileProfile], + name: 'my-mfa-profile', + settings: { + role_arn: 'my-role-arn', + source_profile: 'my-iam-profile', + mfa_serial: 'my-device-arn', + }, + } + const actual = await sut.getCredential({ ...defaultParams, profile: profile }) + + expect(actual.accessKeyId).to.equal('role-access-key') + expect(actual.secretAccessKey).to.equal('role-secret-key') + expect(actual.sessionToken).to.equal('role-session-token') + expect(actual.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') + expect(handlers.sendGetMfaCode.calledOnce).to.be.true + }) + + it('Returns existing STS credential.', async () => { + const profile: Profile = { + kinds: [ProfileKind.IamSourceProfileProfile], + name: 'my-role-profile', + settings: { + role_arn: 'my-role-arn', + source_profile: 'my-iam-profile', + }, + } + stsCache.getStsCredential = (() => + Promise.resolve({ + Credentials: { + AccessKeyId: 'other-access-key', + SecretAccessKey: 'other-secret-key', + SessionToken: 'other-session-token', + Expiration: new Date('2024-10-25T18:09:20.455Z'), + }, + AssumedRoleUser: { + Arn: 'other-role-arn', + AssumedRoleId: 'other-role-id', + }, + })) as any + const actual = await sut.getCredential({ ...defaultParams, profile: profile }) + + expect(actual.accessKeyId).to.equal('other-access-key') + expect(actual.secretAccessKey).to.equal('other-secret-key') + expect(actual.sessionToken).to.equal('other-session-token') + expect(actual.expiration?.toISOString()).to.equal('2024-10-25T18:09:20.455Z') + expect(stsAutoRefresher.watch.calledOnce).to.be.true + }) + + it('Throws when no STS credential cached and callStsOnInvalidIamCredential is false.', async () => { + const profile: Profile = { + kinds: [ProfileKind.IamSourceProfileProfile], + name: 'my-role-profile', + settings: { + role_arn: 'my-role-arn', + source_profile: 'my-iam-profile', + }, + } + const error = await expect( + sut.getCredential({ ...defaultParams, profile: profile, callStsOnInvalidIamCredential: false }) + ).rejectedWith(Error) + + expect(error.message).to.equal('STS credential not found.') + expect(stsAutoRefresher.watch.calledOnce).to.be.false + }) + + it('Can login with chained IamSourceProfileProfiles.', async () => { + const profile: Profile = { + kinds: [ProfileKind.IamSourceProfileProfile], + name: 'base-profile', + settings: { + role_arn: 'my-role-arn', + source_profile: 'intermediate-profile', + }, + } + const actual = await sut.getCredential({ ...defaultParams, profile: profile }) + + expect(actual.accessKeyId).to.equal('role-access-key') + expect(actual.secretAccessKey).to.equal('role-secret-key') + expect(actual.sessionToken).to.equal('role-session-token') + expect(actual.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') + expect(stsAutoRefresher.watch.called).to.be.true + }) + + it('Throws when IamSourceProfileProfile points to itself.', async () => { + const profile: Profile = { + kinds: [ProfileKind.IamSourceProfileProfile], + name: 'cyclic-profile-1', + settings: { + role_arn: 'my-role-arn', + source_profile: 'cyclic-profile-1', + }, + } + const error = await expect(sut.getCredential({ ...defaultParams, profile: profile })).rejectedWith(Error) + + expect(error.message).to.equal('Source profile chain exceeded max length.') + expect(stsAutoRefresher.watch.calledOnce).to.be.false + }) + + it('Throws when IamSourceProfileProfile form cycle.', async () => { + const profile: Profile = { + kinds: [ProfileKind.IamSourceProfileProfile], + name: 'cyclic-profile-2', + settings: { + role_arn: 'my-role-arn', + source_profile: 'cyclic-profile-3', + }, + } + const error = await expect(sut.getCredential({ ...defaultParams, profile: profile })).rejectedWith(Error) + + expect(error.message).to.equal('Source profile chain exceeded max length.') + expect(stsAutoRefresher.watch.calledOnce).to.be.false + }) + }) +}) diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index 5c0a343e0d..df3f6f9ba0 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -8,6 +8,7 @@ import { createStubInstance, restore, spy, SinonSpy, stub } from 'sinon' import { AuthorizationFlowKind, CancellationToken, + IamCredentials, ProfileKind, SsoTokenSourceKind, } from '@aws/language-server-runtimes/protocol' @@ -16,9 +17,8 @@ import { Logging, Telemetry } from '@aws/language-server-runtimes/server-interfa import { Observability } from '@aws/lsp-core' import { StsCache } from '../sts/cache/stsCache' import { StsAutoRefresher } from '../sts/stsAutoRefresher' -import { STSClient } from '@aws-sdk/client-sts' -import { IAMClient } from '@aws-sdk/client-iam' import { IamProvider } from '../iam/iamProvider' +import * as iamUtils from '../iam/utils' // eslint-disable-next-line use(require('chai-as-promised')) @@ -30,7 +30,7 @@ let ssoCache: StubbedInstance let stsCache: StubbedInstance let autoRefresher: StubbedInstance let stsAutoRefresher: StubbedInstance -let iamProvider: IamProvider +let iamProvider: StubbedInstance let observability: StubbedInstance let authFlowFn: SinonSpy @@ -54,79 +54,6 @@ describe('IdentityService', () => { aws_secret_access_key: 'my-secret-key', }, }, - { - kinds: [ProfileKind.IamCredentialsProfile], - name: 'my-sts-profile', - settings: { - aws_access_key_id: 'my-access-key', - aws_secret_access_key: 'my-secret-key', - aws_session_token: 'my-session-token', - }, - }, - { - kinds: [ProfileKind.IamSourceProfileProfile], - name: 'my-role-profile', - settings: { - role_arn: 'my-role-arn', - source_profile: 'my-iam-profile', - }, - }, - { - kinds: [ProfileKind.IamSourceProfileProfile], - name: 'my-mfa-profile', - settings: { - role_arn: 'my-role-arn', - source_profile: 'my-iam-profile', - mfa_serial: 'my-device-arn', - }, - }, - { - kinds: [ProfileKind.IamCredentialProcessProfile], - name: 'my-process-profile', - settings: { - credential_process: 'my-process', - }, - }, - { - kinds: [ProfileKind.IamRoleSourceProfile], - name: 'cyclic-profile-1', - settings: { - role_arn: 'my-role-arn', - source_profile: 'cyclic-profile-1', - }, - }, - { - kinds: [ProfileKind.IamRoleSourceProfile], - name: 'cyclic-profile-2', - settings: { - role_arn: 'my-role-arn', - source_profile: 'cyclic-profile-3', - }, - }, - { - kinds: [ProfileKind.IamRoleSourceProfile], - name: 'cyclic-profile-3', - settings: { - role_arn: 'my-role-arn', - source_profile: 'cyclic-profile-2', - }, - }, - { - kinds: [ProfileKind.IamRoleSourceProfile], - name: 'base-profile', - settings: { - role_arn: 'my-role-arn', - source_profile: 'intermediate-profile', - }, - }, - { - kinds: [ProfileKind.IamRoleSourceProfile], - name: 'intermediate-profile', - settings: { - role_arn: 'my-role-arn', - source_profile: 'my-iam-profile', - }, - }, ], ssoSessions: [ { @@ -170,7 +97,12 @@ describe('IdentityService', () => { unwatch: undefined, }) as StubbedInstance - iamProvider = new IamProvider() + iamProvider = createStubInstance(IamProvider, { + getCredential: Promise.resolve({ + accessKeyId: 'access-key', + secretAccessKey: 'secret-key', + } as IamCredentials), + }) as StubbedInstance authFlowFn = spy(() => Promise.resolve({ @@ -204,21 +136,8 @@ describe('IdentityService', () => { } ) - stub(STSClient.prototype, 'send').resolves({ - Credentials: { - AccessKeyId: 'role-access-key', - SecretAccessKey: 'role-secret-key', - SessionToken: 'role-session-token', - Expiration: new Date('2024-09-25T18:09:20.455Z'), - }, - AssumedRoleUser: { - Arn: 'role-arn', - AssumedRoleId: 'role-id', - }, - Arn: 'role-arn', - }) - - stub(IAMClient.prototype, 'send').resolves({ + stub(iamUtils, 'simulatePermissions').resolves({ + $metadata: {}, EvaluationResults: [], }) }) @@ -380,88 +299,11 @@ describe('IdentityService', () => { }) describe('getIamCredential', () => { - it('Can login with access key, secret key, and session token.', async () => { - const actual = await sut.getIamCredential({ profileName: 'my-sts-profile' }, CancellationToken.None) - - expect(actual.credential.credentials.accessKeyId).to.equal('my-access-key') - expect(actual.credential.credentials.secretAccessKey).to.equal('my-secret-key') - expect(actual.credential.credentials.sessionToken).to.equal('my-session-token') - }) - - it('Can login with assumed role.', async () => { - const actual = await sut.getIamCredential({ profileName: 'my-role-profile' }, CancellationToken.None) - - expect(actual.credential.credentials.accessKeyId).to.equal('role-access-key') - expect(actual.credential.credentials.secretAccessKey).to.equal('role-secret-key') - expect(actual.credential.credentials.sessionToken).to.equal('role-session-token') - expect(actual.credential.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') - expect(stsAutoRefresher.watch.calledOnce).to.be.true - }) - - it('Returns existing STS credential.', async () => { - stsCache.getStsCredential = (() => - Promise.resolve({ - Credentials: { - AccessKeyId: 'other-access-key', - SecretAccessKey: 'other-secret-key', - SessionToken: 'other-session-token', - Expiration: new Date('2024-10-25T18:09:20.455Z'), - }, - AssumedRoleUser: { - Arn: 'other-role-arn', - AssumedRoleId: 'other-role-id', - }, - })) as any - const actual = await sut.getIamCredential({ profileName: 'my-role-profile' }, CancellationToken.None) - - expect(actual.credential.credentials.accessKeyId).to.equal('other-access-key') - expect(actual.credential.credentials.secretAccessKey).to.equal('other-secret-key') - expect(actual.credential.credentials.sessionToken).to.equal('other-session-token') - expect(actual.credential.credentials.expiration?.toISOString()).to.equal('2024-10-25T18:09:20.455Z') - expect(stsAutoRefresher.watch.calledOnce).to.be.true - }) - - it('Throws when no STS credential cached and generateOnInvalidStsCredential is false.', async () => { - const error = await expect( - sut.getIamCredential( - { - profileName: 'my-role-profile', - options: { generateOnInvalidStsCredential: false }, - }, - CancellationToken.None - ) - ).rejectedWith(Error) - - expect(error.message).to.equal('STS credential not found.') - expect(stsAutoRefresher.watch.calledOnce).to.be.false - }) - - it('Can login with chained role source profiles.', async () => { - const actual = await sut.getIamCredential({ profileName: 'base-profile' }, CancellationToken.None) - - expect(actual.credential.credentials.accessKeyId).to.equal('role-access-key') - expect(actual.credential.credentials.secretAccessKey).to.equal('role-secret-key') - expect(actual.credential.credentials.sessionToken).to.equal('role-session-token') - expect(actual.credential.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') - expect(stsAutoRefresher.watch.called).to.be.true - }) - - it('Throws when role source profile points to itself.', async () => { - const error = await expect( - sut.getIamCredential({ profileName: 'cyclic-profile-1' }, CancellationToken.None) - ).rejectedWith(Error) - - expect(error.message).to.equal('Source profile chain exceeded max length.') - expect(stsAutoRefresher.watch.calledOnce).to.be.false - }) - - it('Throws when role source profiles form cycle.', async () => { - const error = await expect( - sut.getIamCredential({ profileName: 'cyclic-profile-2' }, CancellationToken.None) - ).rejectedWith(Error) + it('Can login with IAM credentials.', async () => { + const actual = await sut.getIamCredential({ profileName: 'my-iam-profile' }, CancellationToken.None) - expect(error.message).to.equal('Source profile chain exceeded max length.') - expect(stsAutoRefresher.watch.calledOnce).to.be.false + expect(actual.credential.credentials.accessKeyId).to.equal('access-key') + expect(actual.credential.credentials.secretAccessKey).to.equal('secret-key') }) }) From 471c64475951cf9ba9efd90105cfab12d837a2ca Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Thu, 17 Jul 2025 17:00:47 -0400 Subject: [PATCH 26/37] fix: clear MFA timeout --- server/aws-lsp-identity/src/iam/iamProvider.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/server/aws-lsp-identity/src/iam/iamProvider.ts b/server/aws-lsp-identity/src/iam/iamProvider.ts index cf1cd46d88..de2faec933 100644 --- a/server/aws-lsp-identity/src/iam/iamProvider.ts +++ b/server/aws-lsp-identity/src/iam/iamProvider.ts @@ -144,11 +144,13 @@ export class IamProvider { } assumeRoleInput.SerialNumber = params.profile.settings?.mfa_serial // Request an MFA code from the language client - const timeout = new Promise((_, reject) => - setTimeout( - () => reject(new AwsError('MFA code request timed out', AwsErrorCodes.E_MFA_REQUIRED)), - mfaTimeout - ) + let timeoutId: NodeJS.Timeout | undefined + const timeout = new Promise( + (_, reject) => + (timeoutId = setTimeout( + () => reject(new AwsError('MFA code request timed out', AwsErrorCodes.E_MFA_REQUIRED)), + mfaTimeout + )) ) const response = await Promise.race([ params.handlers.sendGetMfaCode({ @@ -157,6 +159,7 @@ export class IamProvider { }), timeout, ]) + clearTimeout(timeoutId) if (!response.code) { throw new AwsError( 'MFA code required when assuming role with MultiFactorAuthPresent permission condition', From 55f58f3b7e1ffd4aa69c8e87c8e3bca55eb83d63 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Fri, 18 Jul 2025 10:50:41 -0400 Subject: [PATCH 27/37] fix: make STS changes compliant with disk-cache SEP --- .../src/iam/iamProvider.test.ts | 52 ++++--- .../aws-lsp-identity/src/iam/iamProvider.ts | 77 +++++++---- server/aws-lsp-identity/src/iam/utils.ts | 6 + .../language-server/identityService.test.ts | 20 +-- .../src/language-server/identityService.ts | 22 +-- .../src/sts/cache/fileSystemStsCache.test.ts | 127 ++++++++---------- .../src/sts/cache/fileSystemStsCache.ts | 66 ++++++--- .../src/sts/cache/refreshingStsCache.test.ts | 40 +++--- .../src/sts/cache/refreshingStsCache.ts | 16 +-- .../src/sts/cache/stsCache.ts | 11 +- .../src/sts/stsAutoRefresher.test.ts | 45 +++---- .../src/sts/stsAutoRefresher.ts | 16 ++- 12 files changed, 254 insertions(+), 244 deletions(-) diff --git a/server/aws-lsp-identity/src/iam/iamProvider.test.ts b/server/aws-lsp-identity/src/iam/iamProvider.test.ts index 96fb09db1b..12667adcd0 100644 --- a/server/aws-lsp-identity/src/iam/iamProvider.test.ts +++ b/server/aws-lsp-identity/src/iam/iamProvider.test.ts @@ -164,9 +164,9 @@ describe('IamProvider', () => { } const actual = await sut.getCredential({ ...defaultParams, profile: profile }) - expect(actual.accessKeyId).to.equal('access-key') - expect(actual.secretAccessKey).to.equal('secret-key') - expect(actual.sessionToken).to.equal('session-token') + expect(actual.credentials.accessKeyId).to.equal('access-key') + expect(actual.credentials.secretAccessKey).to.equal('secret-key') + expect(actual.credentials.sessionToken).to.equal('session-token') }) it('Can generate credentials by assuming role.', async () => { @@ -180,10 +180,10 @@ describe('IamProvider', () => { } const actual = await sut.getCredential({ ...defaultParams, profile: profile }) - expect(actual.accessKeyId).to.equal('role-access-key') - expect(actual.secretAccessKey).to.equal('role-secret-key') - expect(actual.sessionToken).to.equal('role-session-token') - expect(actual.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') + expect(actual.credentials.accessKeyId).to.equal('role-access-key') + expect(actual.credentials.secretAccessKey).to.equal('role-secret-key') + expect(actual.credentials.sessionToken).to.equal('role-session-token') + expect(actual.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') expect(stsAutoRefresher.watch.calledOnce).to.be.true }) @@ -210,10 +210,10 @@ describe('IamProvider', () => { } const actual = await sut.getCredential({ ...defaultParams, profile: profile }) - expect(actual.accessKeyId).to.equal('role-access-key') - expect(actual.secretAccessKey).to.equal('role-secret-key') - expect(actual.sessionToken).to.equal('role-session-token') - expect(actual.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') + expect(actual.credentials.accessKeyId).to.equal('role-access-key') + expect(actual.credentials.secretAccessKey).to.equal('role-secret-key') + expect(actual.credentials.sessionToken).to.equal('role-session-token') + expect(actual.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') expect(handlers.sendGetMfaCode.calledOnce).to.be.true }) @@ -228,23 +228,17 @@ describe('IamProvider', () => { } stsCache.getStsCredential = (() => Promise.resolve({ - Credentials: { - AccessKeyId: 'other-access-key', - SecretAccessKey: 'other-secret-key', - SessionToken: 'other-session-token', - Expiration: new Date('2024-10-25T18:09:20.455Z'), - }, - AssumedRoleUser: { - Arn: 'other-role-arn', - AssumedRoleId: 'other-role-id', - }, + accessKeyId: 'other-access-key', + secretAccessKey: 'other-secret-key', + sessionToken: 'other-session-token', + expiration: new Date('2024-10-25T18:09:20.455Z'), })) as any const actual = await sut.getCredential({ ...defaultParams, profile: profile }) - expect(actual.accessKeyId).to.equal('other-access-key') - expect(actual.secretAccessKey).to.equal('other-secret-key') - expect(actual.sessionToken).to.equal('other-session-token') - expect(actual.expiration?.toISOString()).to.equal('2024-10-25T18:09:20.455Z') + expect(actual.credentials.accessKeyId).to.equal('other-access-key') + expect(actual.credentials.secretAccessKey).to.equal('other-secret-key') + expect(actual.credentials.sessionToken).to.equal('other-session-token') + expect(actual.credentials.expiration?.toISOString()).to.equal('2024-10-25T18:09:20.455Z') expect(stsAutoRefresher.watch.calledOnce).to.be.true }) @@ -276,10 +270,10 @@ describe('IamProvider', () => { } const actual = await sut.getCredential({ ...defaultParams, profile: profile }) - expect(actual.accessKeyId).to.equal('role-access-key') - expect(actual.secretAccessKey).to.equal('role-secret-key') - expect(actual.sessionToken).to.equal('role-session-token') - expect(actual.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') + expect(actual.credentials.accessKeyId).to.equal('role-access-key') + expect(actual.credentials.secretAccessKey).to.equal('role-secret-key') + expect(actual.credentials.sessionToken).to.equal('role-session-token') + expect(actual.credentials.expiration?.toISOString()).to.equal('2024-09-25T18:09:20.455Z') expect(stsAutoRefresher.watch.called).to.be.true }) diff --git a/server/aws-lsp-identity/src/iam/iamProvider.ts b/server/aws-lsp-identity/src/iam/iamProvider.ts index de2faec933..9cb57bfbb5 100644 --- a/server/aws-lsp-identity/src/iam/iamProvider.ts +++ b/server/aws-lsp-identity/src/iam/iamProvider.ts @@ -1,8 +1,14 @@ -import { AwsErrorCodes, IamCredentials, ProfileKind } from '@aws/language-server-runtimes/server-interface' +import { + AwsErrorCodes, + IamCredential, + IamCredentialId, + IamCredentials, + ProfileKind, +} from '@aws/language-server-runtimes/server-interface' import { AwsError } from '@aws/lsp-core' import { AssumeRoleCommand, AssumeRoleCommandInput, STSClient } from '@aws-sdk/client-sts' import { IamFlowParams, simulatePermissions } from './utils' -import { StsCredential } from '../sts/cache/stsCache' +import { createHash } from 'crypto' const sourceProfileRecursionMax = 5 const mfaTimeout = 2 * 60 * 1000 // 2 minutes @@ -10,12 +16,19 @@ const mfaTimeout = 2 * 60 * 1000 // 2 minutes export class IamProvider { private sourceProfileRecursionCount = 0 - async getCredential(params: IamFlowParams): Promise { + async getCredential(params: IamFlowParams): Promise { try { + let id: IamCredentialId = '' let credentials: IamCredentials // Assume the role matching the found ARN if (params.profile.kinds.includes(ProfileKind.IamSourceProfileProfile)) { - credentials = await this.getAssumedRoleCredential(params) + const key = JSON.stringify({ + RoleArn: params.profile.settings?.role_arn, + RoleSessionName: params.profile.settings?.role_session_name, + SerialNumber: params.profile.settings?.mfa_serial, + }) + id = createHash('sha1').update(key).digest('hex') + credentials = await this.getAssumedRoleCredential(id, params) } // Get the credentials directly from the profile else if (params.profile.kinds.includes(ProfileKind.IamCredentialsProfile)) { @@ -31,50 +44,44 @@ export class IamProvider { ) } - return credentials + return { id: id, kinds: params.profile.kinds, credentials: credentials } } catch (e) { this.sourceProfileRecursionCount = 0 throw e } } - private async getAssumedRoleCredential(params: IamFlowParams): Promise { + private async getAssumedRoleCredential(id: IamCredentialId, params: IamFlowParams): Promise { if (!params.profile.settings) { throw new AwsError('Profile settings not found when assuming role.', AwsErrorCodes.E_INVALID_PROFILE) } // Try to get the STS credentials from cache let result: IamCredentials - const stsCredentials = await params.stsCache.getStsCredential(params.profile.name).catch(_ => undefined) + const credential = await params.stsCache.getStsCredential(id).catch(_ => undefined) - if (stsCredentials?.Credentials) { + if (credential) { result = { - accessKeyId: stsCredentials.Credentials.AccessKeyId!, - secretAccessKey: stsCredentials.Credentials.SecretAccessKey!, - sessionToken: stsCredentials.Credentials.SessionToken!, - expiration: stsCredentials.Credentials.Expiration!, + accessKeyId: credential.accessKeyId, + secretAccessKey: credential.secretAccessKey, + sessionToken: credential.sessionToken, + expiration: credential.expiration, } } else if (params.callStsOnInvalidIamCredential) { // Generate STS credentials const response = await this.generateStsCredential(params) - if (!response.Credentials) { - throw new AwsError( - 'Failed to assume role: No credentials returned', - AwsErrorCodes.E_INVALID_STS_CREDENTIAL - ) - } // Cache STS credentials - await params.stsCache.setStsCredential(params.profile.name, response) + await params.stsCache.setStsCredential(id, response) result = { - accessKeyId: response.Credentials.AccessKeyId!, - secretAccessKey: response.Credentials.SecretAccessKey!, - sessionToken: response.Credentials.SessionToken!, // Always present in STS response - expiration: response.Credentials.Expiration!, + accessKeyId: response.accessKeyId, + secretAccessKey: response.secretAccessKey, + sessionToken: response.sessionToken, + expiration: response.expiration, } } else { // If we could not get the cached STS credential and cannot generate a new credential, give up params.observability.logging.log( - 'STS credential not found an generateOnInvalidStsCredential = false, returning no credential.' + 'STS credential not found an callStsOnInvalidIamCredential = false, returning no credential.' ) throw new AwsError('STS credential not found.', AwsErrorCodes.E_INVALID_STS_CREDENTIAL) } @@ -82,7 +89,7 @@ export class IamProvider { // Set up auto-refresh if MFA is disabled if (!params.profile.settings.mfa_serial) { await params.stsAutoRefresher - .watch(params.profile.name, () => this.generateStsCredential(params)) + .watch(id, () => this.generateStsCredential(params)) .catch(reason => { params.observability.logging.log(`Unable to auto-refresh STS credentials. ${reason}`) }) @@ -104,7 +111,8 @@ export class IamProvider { // Obtain parent profile credentials if IamRoleSourceProfile chain isn't too long if (this.sourceProfileRecursionCount <= sourceProfileRecursionMax) { this.sourceProfileRecursionCount += 1 - parentCredentials = await this.getCredential({ ...params, profile: sourceProfile }) + const response = await this.getCredential({ ...params, profile: sourceProfile }) + parentCredentials = response.credentials this.sourceProfileRecursionCount = 0 } else { throw new AwsError('Source profile chain exceeded max length.', AwsErrorCodes.E_INVALID_PROFILE) @@ -115,7 +123,7 @@ export class IamProvider { return parentCredentials } - private async generateStsCredential(params: IamFlowParams): Promise { + private async generateStsCredential(params: IamFlowParams): Promise { try { const parentCredentials = await this.getParentCredential(params) const stsClient = new STSClient({ @@ -170,8 +178,19 @@ export class IamProvider { } const command = new AssumeRoleCommand(assumeRoleInput) - const { Credentials, AssumedRoleUser } = await stsClient.send(command) - return { Credentials, AssumedRoleUser } + const { Credentials } = await stsClient.send(command) + if (!Credentials?.AccessKeyId || !Credentials.SecretAccessKey) { + throw new AwsError( + 'Failed to generate credentials for assumed role', + AwsErrorCodes.E_CANNOT_CREATE_STS_CREDENTIAL + ) + } + return { + accessKeyId: Credentials.AccessKeyId, + secretAccessKey: Credentials.SecretAccessKey, + sessionToken: Credentials.SessionToken, + expiration: Credentials.Expiration, + } } catch (e) { params.observability.logging.log(`Error generating STS credentials.`) throw e diff --git a/server/aws-lsp-identity/src/iam/utils.ts b/server/aws-lsp-identity/src/iam/utils.ts index e18ceea2c6..2e60851fde 100644 --- a/server/aws-lsp-identity/src/iam/utils.ts +++ b/server/aws-lsp-identity/src/iam/utils.ts @@ -47,6 +47,12 @@ function convertToIamArn(arn: string) { } } +export function throwOnInvalidCredentialId(iamCredentialId?: string): asserts iamCredentialId is string { + if (!iamCredentialId?.trim()) { + throw new AwsError('IAM credential id is invalid.', AwsErrorCodes.E_INVALID_STS_CREDENTIAL) + } +} + export type SendGetMfaCode = (params: GetMfaCodeParams) => Promise export type IamHandlers = { diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index df3f6f9ba0..9940940b12 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -8,7 +8,7 @@ import { createStubInstance, restore, spy, SinonSpy, stub } from 'sinon' import { AuthorizationFlowKind, CancellationToken, - IamCredentials, + IamCredential, ProfileKind, SsoTokenSourceKind, } from '@aws/language-server-runtimes/protocol' @@ -99,9 +99,13 @@ describe('IdentityService', () => { iamProvider = createStubInstance(IamProvider, { getCredential: Promise.resolve({ - accessKeyId: 'access-key', - secretAccessKey: 'secret-key', - } as IamCredentials), + id: 'id', + kinds: [], + credentials: { + accessKeyId: 'access-key', + secretAccessKey: 'secret-key', + }, + } as IamCredential), }) as StubbedInstance authFlowFn = spy(() => @@ -322,15 +326,15 @@ describe('IdentityService', () => { }) describe('invalidateStsCredential', () => { - it('Removes on valid profile name', async () => { - await sut.invalidateStsCredential({ profileName: 'my-role-profile' }, CancellationToken.None) + it('Removes on valid name', async () => { + await sut.invalidateStsCredential({ iamCredentialId: 'my-role-profile' }, CancellationToken.None) expect(stsCache.removeStsCredential.called).is.true }) - it('Throws on invalid profile name', async () => { + it('Throws on invalid name', async () => { await expect( - sut.invalidateStsCredential({ profileName: ' ' }, CancellationToken.None) + sut.invalidateStsCredential({ iamCredentialId: ' ' }, CancellationToken.None) ).to.be.rejectedWith() expect(stsCache.removeStsCredential.notCalled).is.true diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 0671ad7e9d..05b1f1bc2d 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -31,7 +31,7 @@ import { SsoFlowParams, SsoHandlers, } from '../sso/utils' -import { IamFlowParams, IamHandlers, simulatePermissions } from '../iam/utils' +import { IamFlowParams, IamHandlers, simulatePermissions, throwOnInvalidCredentialId } from '../iam/utils' import { AwsError, Observability } from '@aws/lsp-core' import { __ServiceException } from '@aws-sdk/client-sso-oidc/dist-types/models/SSOOIDCServiceException' import { deviceCodeFlow } from '../sso/deviceCode/deviceCodeFlow' @@ -183,11 +183,15 @@ export class IdentityService { token: token, observability: this.observability, } - const credentials = await this.iamProvider.getCredential(flowOpts) + const credential = await this.iamProvider.getCredential(flowOpts) // Validate permissions if (options.permissionSet.length > 0) { - const response = await simulatePermissions(credentials, options.permissionSet, profile.settings?.region) + const response = await simulatePermissions( + credential.credentials, + options.permissionSet, + profile.settings?.region + ) if (!response?.EvaluationResults?.every(result => result.EvalDecision === 'allowed')) { throw new AwsError(`Credentials have insufficient permissions.`, AwsErrorCodes.E_INVALID_PROFILE) } @@ -196,8 +200,8 @@ export class IdentityService { emitMetric('Succeeded') return { - credential: { id: params.profileName, credentials: credentials }, - updateCredentialsParams: { data: credentials, encrypted: false }, + credential: credential, + updateCredentialsParams: { data: credential.credentials, encrypted: false }, } } catch (e) { emitMetric('Failed', e) @@ -253,13 +257,11 @@ export class IdentityService { }) try { - if (!params?.profileName?.trim()) { - throw new AwsError('Profile name is invalid.', AwsErrorCodes.E_INVALID_PROFILE) - } + throwOnInvalidCredentialId(params.iamCredentialId) - this.stsAutoRefresher.unwatch(params.profileName) + this.stsAutoRefresher.unwatch(params.iamCredentialId) - await this.stsCache.removeStsCredential(params.profileName) + await this.stsCache.removeStsCredential(params.iamCredentialId) emitMetric('Succeeded') this.observability.logging.log('Successfully invalidated STS credentials.') diff --git a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts index 13f2a2f1a1..a047b038eb 100644 --- a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts +++ b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts @@ -3,12 +3,11 @@ import mock = require('mock-fs') import { FileSystemStsCache, getStsCredentialFilepath } from './fileSystemStsCache' import { expect, use } from 'chai' import { DirectoryItems } from 'mock-fs/lib/filesystem' -import { Logging, Telemetry } from '@aws/language-server-runtimes/server-interface' +import { Logging, IamCredentials, Telemetry } from '@aws/language-server-runtimes/server-interface' import { access } from 'fs/promises' import * as fs from 'fs' import { StubbedInstance, stubInterface } from 'ts-sinon' import { Observability } from '@aws/lsp-core' -import { StsCredential } from './stsCache' // eslint-disable-next-line use(require('chai-as-promised')) @@ -17,29 +16,30 @@ let sut: FileSystemStsCache let observability: StubbedInstance -const profileName: string = 'someprofile' - -const stsCredential: StsCredential = { - Credentials: { - AccessKeyId: 'someaccesskeyid', - SecretAccessKey: 'somesecretaccesskey', - SessionToken: 'somesessiontoken', - Expiration: new Date('2024-09-25T18:09:20.455Z'), - }, - AssumedRoleUser: { - Arn: 'arn:aws:sts::123456789012:assumed-role/somerole/somesession', - AssumedRoleId: 'someassumedroleid', - }, +const id: string = 'someid' + +const credential: IamCredentials = { + accessKeyId: 'someaccesskeyid', + secretAccessKey: 'somesecretaccesskey', + sessionToken: 'somesessiontoken', + expiration: new Date('2024-09-25T18:09:20.455Z'), } -function setupTest(args?: { profileName?: string; stsCredential?: StsCredential }): void { +function setupTest(args?: { id?: string; credential?: IamCredentials }): void { // Just for sanity, safe to call restore if mock not currently active mock.restore() - args = { ...{ profileName, stsCredential }, ...args } + args = { ...{ id, credential }, ...args } const mockConfig: DirectoryItems = {} - mockConfig[getStsCredentialFilepath(args.profileName!)] = JSON.stringify(args.stsCredential) + mockConfig[getStsCredentialFilepath(args.id!)] = JSON.stringify({ + Credentials: { + AccessKeyId: credential.accessKeyId, + SecretAccessKey: credential.secretAccessKey, + SessionToken: credential.sessionToken, + Expiration: credential.expiration, + }, + }) mock(mockConfig) } @@ -62,18 +62,18 @@ describe('FileSystemStsCache', () => { }) it('removeStsCredential deletes a valid credential', async () => { - const filename = getStsCredentialFilepath(profileName) + const filename = getStsCredentialFilepath(id) setupTest() await expectFileExists(filename).to.not.be.rejectedWith() - await sut.removeStsCredential(profileName) + await sut.removeStsCredential(id) await expectFileExists(filename).to.be.rejectedWith() }) it('removeStsCredential does nothing on invalid/non-existent credential', async () => { - const filename = getStsCredentialFilepath(profileName) + const filename = getStsCredentialFilepath(id) setupTest() await expectFileExists(filename).to.not.be.rejectedWith() @@ -84,23 +84,22 @@ describe('FileSystemStsCache', () => { }) it('removeStsCredential throws on invalid profile name', async () => { - await expect(sut.removeStsCredential(null!)).to.be.rejectedWith() + await expect(sut.removeStsCredential(' ')).to.be.rejectedWith() }) it('getStsCredential returns valid credential', async () => { setupTest() - const actual = await sut.getStsCredential(profileName) + const actual = await sut.getStsCredential(id) + console.log('testabc') + console.log(actual?.expiration) + console.log(typeof actual?.expiration) expect(actual).to.not.be.null.and.not.undefined - expect(actual?.Credentials?.AccessKeyId).to.equal(stsCredential.Credentials?.AccessKeyId) - expect(actual?.Credentials?.SecretAccessKey).to.equal(stsCredential.Credentials?.SecretAccessKey) - expect(actual?.Credentials?.SessionToken).to.equal(stsCredential.Credentials?.SessionToken) - expect(actual?.Credentials?.Expiration?.toISOString()).to.equal( - stsCredential.Credentials?.Expiration?.toISOString() - ) - expect(actual?.AssumedRoleUser?.Arn).to.equal(stsCredential.AssumedRoleUser?.Arn) - expect(actual?.AssumedRoleUser?.AssumedRoleId).to.equal(stsCredential.AssumedRoleUser?.AssumedRoleId) + expect(actual?.accessKeyId).to.equal(credential.accessKeyId) + expect(actual?.secretAccessKey).to.equal(credential.secretAccessKey) + expect(actual?.sessionToken).to.equal(credential.sessionToken) + expect(actual?.expiration?.toISOString()).to.equal(credential.expiration?.toISOString()) }) it('getStsCredential returns undefined when file does not exist', async () => { @@ -112,79 +111,63 @@ describe('FileSystemStsCache', () => { }) it('getStsCredential returns undefined on invalid credential', async () => { - setupTest({ profileName: 'invalid-profile', stsCredential: {} as StsCredential }) + setupTest({ id: 'invalid-profile', credential: {} as IamCredentials }) - const actual = await sut.getStsCredential(profileName) + const actual = await sut.getStsCredential(id) expect(actual).to.be.undefined }) it('setStsCredential writes new valid credential', async () => { setupTest() - await sut.setStsCredential(profileName, stsCredential) + await sut.setStsCredential(id, credential) - const actual = await sut.getStsCredential(profileName) + const actual = await sut.getStsCredential(id) expect(actual).to.not.be.null.and.not.undefined - expect(actual?.Credentials?.AccessKeyId).to.equal(stsCredential.Credentials?.AccessKeyId) - expect(actual?.Credentials?.SecretAccessKey).to.equal(stsCredential.Credentials?.SecretAccessKey) - expect(actual?.Credentials?.SessionToken).to.equal(stsCredential.Credentials?.SessionToken) - expect(actual?.Credentials?.Expiration?.toISOString()).to.equal( - stsCredential.Credentials?.Expiration?.toISOString() - ) - expect(actual?.AssumedRoleUser?.Arn).to.equal(stsCredential.AssumedRoleUser?.Arn) - expect(actual?.AssumedRoleUser?.AssumedRoleId).to.equal(stsCredential.AssumedRoleUser?.AssumedRoleId) + expect(actual?.accessKeyId).to.equal(credential.accessKeyId) + expect(actual?.secretAccessKey).to.equal(credential.secretAccessKey) + expect(actual?.sessionToken).to.equal(credential.sessionToken) + expect(actual?.expiration?.toISOString()).to.equal(credential.expiration?.toISOString()) }) it('setStsCredential writes new valid credential when ~/.aws does not exist', async () => { mock.restore() mock({}) - await sut.setStsCredential(profileName, stsCredential) + await sut.setStsCredential(id, credential) - const actual = await sut.getStsCredential(profileName) + const actual = await sut.getStsCredential(id) expect(actual).to.not.be.null.and.not.undefined - expect(actual?.Credentials?.AccessKeyId).to.equal(stsCredential.Credentials?.AccessKeyId) - expect(actual?.Credentials?.SecretAccessKey).to.equal(stsCredential.Credentials?.SecretAccessKey) - expect(actual?.Credentials?.SessionToken).to.equal(stsCredential.Credentials?.SessionToken) - expect(actual?.Credentials?.Expiration?.toISOString()).to.equal( - stsCredential.Credentials?.Expiration?.toISOString() - ) - expect(actual?.AssumedRoleUser?.Arn).to.equal(stsCredential.AssumedRoleUser?.Arn) - expect(actual?.AssumedRoleUser?.AssumedRoleId).to.equal(stsCredential.AssumedRoleUser?.AssumedRoleId) + expect(actual?.accessKeyId).to.equal(credential.accessKeyId) + expect(actual?.secretAccessKey).to.equal(credential.secretAccessKey) + expect(actual?.sessionToken).to.equal(credential.sessionToken) + expect(actual?.expiration?.toISOString()).to.equal(credential.expiration?.toISOString()) }) it('setStsCredential writes updated existing credential', async () => { setupTest() - await sut.setStsCredential(profileName, { - Credentials: { - AccessKeyId: 'newaccesskeyid', - SecretAccessKey: 'newsecretaccesskey', - SessionToken: 'newsessiontoken', - Expiration: new Date('2024-10-14T12:00:00.000Z'), - }, - AssumedRoleUser: { - Arn: 'newarn', - AssumedRoleId: 'newroleid', - }, + await sut.setStsCredential(id, { + accessKeyId: 'newaccesskeyid', + secretAccessKey: 'newsecretaccesskey', + sessionToken: 'newsessiontoken', + expiration: new Date('2024-10-14T12:00:00.000Z'), }) - const actual = await sut.getStsCredential(profileName) + const actual = await sut.getStsCredential(id) expect(actual).to.not.be.null.and.not.undefined - expect(actual?.Credentials?.AccessKeyId).to.equal('newaccesskeyid') - expect(actual?.Credentials?.SecretAccessKey).to.equal('newsecretaccesskey') - expect(actual?.Credentials?.SessionToken).to.equal('newsessiontoken') - expect(actual?.Credentials?.Expiration?.toISOString()).to.equal('2024-10-14T12:00:00.000Z') - expect(actual?.AssumedRoleUser?.Arn).to.equal('newarn') - expect(actual?.AssumedRoleUser?.AssumedRoleId).to.equal('newroleid') + expect(actual?.accessKeyId).to.equal('newaccesskeyid') + expect(actual?.secretAccessKey).to.equal('newsecretaccesskey') + expect(actual?.sessionToken).to.equal('newsessiontoken') + expect(actual?.expiration?.toISOString()).to.equal('2024-10-14T12:00:00.000Z') }) it('setStsCredential returns without error on invalid credential', async () => { setupTest() - await sut.setStsCredential(profileName, {} as StsCredential) // no throw + await sut.setStsCredential(id, {} as IamCredentials) // no throw }) }) diff --git a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts index 00da58c4ab..20c60539a5 100644 --- a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts +++ b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts @@ -1,27 +1,34 @@ -import { StsCache, StsCredential, stsCredentialDuckTyper } from './stsCache' +import { StsCache } from './stsCache' import { AwsError, Observability } from '@aws/lsp-core' -import { AwsErrorCodes } from '@aws/language-server-runtimes/protocol' +import { AwsErrorCodes, IamCredentials } from '@aws/language-server-runtimes/protocol' import path, { join } from 'path' import { mkdir, readFile, unlink, writeFile } from 'fs/promises' -import { createHash } from 'crypto' import { getHomeDir } from '@smithy/shared-ini-file-loader' +import { throwOnInvalidCredentialId } from '../../iam/utils' export class FileSystemStsCache implements StsCache { constructor(private readonly observability: Observability) {} async removeStsCredential(name: string): Promise { + throwOnInvalidCredentialId(name) + await unlink(getStsCredentialFilepath(name)).catch(reason => this.ignoreDoesNotExistOrThrow(reason)) } - async getStsCredential(name: string): Promise { + async getStsCredential(name: string): Promise { return await getStsCredentialFromFile(name) - .then(stsCredential => { - if (stsCredentialDuckTyper.eval(stsCredential)) { + .then(credential => { + if ( + credential.accessKeyId && + credential.secretAccessKey && + credential.sessionToken && + credential.expiration + ) { // Ensure Expiration is a Date object - if (typeof stsCredential.Credentials?.Expiration === 'string') { - stsCredential.Credentials.Expiration = new Date(stsCredential.Credentials.Expiration) + if (typeof credential.expiration === 'string') { + credential = { ...credential, expiration: new Date(credential.expiration) } } - return stsCredential + return credential } else { return undefined } @@ -29,13 +36,15 @@ export class FileSystemStsCache implements StsCache { .catch(reason => this.ignoreDoesNotExistOrThrow(reason)) } - async setStsCredential(name: string, credentials: StsCredential): Promise { - if (!stsCredentialDuckTyper.eval(credentials)) { + async setStsCredential(name: string, credential: IamCredentials): Promise { + if ( + !(credential.accessKeyId && credential.secretAccessKey && credential.sessionToken && credential.expiration) + ) { this.observability.logging.log('File read from STS cache is not an STS credential.') return } - await writeStsObjectToFile(name, credentials).catch(reason => { + await writeStsObjectToFile(name, credential).catch(reason => { throw AwsError.wrap(reason, AwsErrorCodes.E_CANNOT_WRITE_SSO_CACHE) }) } @@ -54,26 +63,41 @@ export class FileSystemStsCache implements StsCache { // Based on: // https://github.com/smithy-lang/smithy-typescript/blob/main/packages/shared-ini-file-loader/src/getSSOTokenFilepath.ts -export function getStsCredentialFilepath(id: string) { - const hasher = createHash('sha1') - const cacheName = hasher.update(id).digest('hex') - return join(getHomeDir(), '.aws', 'cli', 'cache', `${cacheName}.json`) +export function getStsCredentialFilepath(id: string): string { + return join(getHomeDir(), '.aws', 'flare', 'cache', `${id}.json`) } // Based on: // https://github.com/smithy-lang/smithy-typescript/blob/main/packages/shared-ini-file-loader/src/getSSOTokenFromFile.ts -async function getStsCredentialFromFile(id: string) { +async function getStsCredentialFromFile(id: string): Promise { const stsCredentialFilepath = getStsCredentialFilepath(id) - const stsCredentialText = await readFile(stsCredentialFilepath, 'utf8') - return JSON.parse(stsCredentialText) as StsCredential + const text = await readFile(stsCredentialFilepath, 'utf8') + const json = JSON.parse(text) + return { + accessKeyId: json.Credentials.AccessKeyId, + secretAccessKey: json.Credentials.SecretAccessKey, + sessionToken: json.Credentials.SessionToken, + expiration: json.Credentials.Expiration, + } as IamCredentials } // Based on: // https://github.com/aws/aws-sdk-js-v3/blob/6e61f0e78ff7a9e3b1f2cd651bde5fc656d85ba9/packages/token-providers/src/writeSSOTokenToFile.ts -async function writeStsObjectToFile(id: string, credentials: StsCredential): Promise { +async function writeStsObjectToFile(id: string, credentials: IamCredentials): Promise { const filepath = getStsCredentialFilepath(id) await mkdir(path.dirname(filepath), { mode: 0o755, recursive: true }) - const json = JSON.stringify(credentials, null, 2) + const json = JSON.stringify( + { + Credentials: { + AccessKeyId: credentials.accessKeyId, + SecretAccessKey: credentials.secretAccessKey, + SessionToken: credentials.sessionToken, + Expiration: credentials.expiration, + }, + }, + null, + 2 + ) return await writeFile(filepath, json, { encoding: 'utf-8', flush: true, mode: 0o600 }) } diff --git a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.test.ts b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.test.ts index 858817179e..d18dc67179 100644 --- a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.test.ts +++ b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.test.ts @@ -2,9 +2,9 @@ import { expect, use } from 'chai' import { restore } from 'sinon' import { stubInterface } from 'ts-sinon' import { RefreshingStsCache } from './refreshingStsCache' -import { Logging, Telemetry } from '@aws/language-server-runtimes/server-interface' +import { Logging, IamCredentials, Telemetry } from '@aws/language-server-runtimes/server-interface' import { Observability } from '@aws/lsp-core' -import { StsCache, StsCredential } from './stsCache' +import { StsCache } from './stsCache' // eslint-disable-next-line use(require('chai-as-promised')) @@ -13,24 +13,18 @@ let observability: Observability const profileName: string = 'someprofile' -function createStsCredential(expiresAsOffsetMillis: number): StsCredential { +function createStsCredential(expiresAsOffsetMillis: number): IamCredentials { return { - Credentials: { - AccessKeyId: 'someaccesskeyid', - SecretAccessKey: 'somesecretaccesskey', - SessionToken: 'somesessiontoken', - Expiration: new Date(Date.now() + expiresAsOffsetMillis), - }, - AssumedRoleUser: { - Arn: 'arn:aws:sts::123456789012:assumed-role/somerole/somesession', - AssumedRoleId: 'someassumedroleid', - }, - } as StsCredential + accessKeyId: 'someaccesskeyid', + secretAccessKey: 'somesecretaccesskey', + sessionToken: 'somesessiontoken', + expiration: new Date(Date.now() + expiresAsOffsetMillis), + } satisfies IamCredentials } -function stubStsCache(stsCredential?: StsCredential): StsCache { +function stubStsCache(credential?: IamCredentials): StsCache { return stubInterface({ - getStsCredential: Promise.resolve(stsCredential), + getStsCredential: Promise.resolve(credential), }) } @@ -56,19 +50,17 @@ describe('RefreshingStsCache', () => { }) it('Returns existing STS credential before refresh window (5 minutes before expiration).', async () => { - const stsCredential = createStsCredential(6 * 60 * 1000 /* 6 minutes before */) - const stsCache = stubStsCache(stsCredential) + const credential = createStsCredential(6 * 60 * 1000 /* 6 minutes before */) + const stsCache = stubStsCache(credential) const sut = new RefreshingStsCache(stsCache, observability) const actual = await sut.getStsCredential(profileName) expect(actual).to.not.be.null.and.not.empty - expect(actual?.Credentials?.AccessKeyId).to.equal(stsCredential.Credentials?.AccessKeyId) - expect(actual?.Credentials?.SecretAccessKey).to.equal(stsCredential.Credentials?.SecretAccessKey) - expect(actual?.Credentials?.SessionToken).to.equal(stsCredential.Credentials?.SessionToken) - expect(actual?.Credentials?.Expiration).to.equal(stsCredential.Credentials?.Expiration) - expect(actual?.AssumedRoleUser?.Arn).to.equal(stsCredential.AssumedRoleUser?.Arn) - expect(actual?.AssumedRoleUser?.AssumedRoleId).to.equal(stsCredential.AssumedRoleUser?.AssumedRoleId) + expect(actual?.accessKeyId).to.equal(credential.accessKeyId) + expect(actual?.secretAccessKey).to.equal(credential.secretAccessKey) + expect(actual?.sessionToken).to.equal(credential.sessionToken) + expect(actual?.expiration).to.equal(credential.expiration) }) }) }) diff --git a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts index b6f27fc8bb..23daba88c1 100644 --- a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts +++ b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts @@ -1,5 +1,5 @@ -import { StsCache, StsCredential } from './stsCache' -import { AwsErrorCodes } from '@aws/language-server-runtimes/server-interface' +import { StsCache } from './stsCache' +import { AwsErrorCodes, IamCredentials } from '@aws/language-server-runtimes/server-interface' import { AwsError, Observability } from '@aws/lsp-core' interface StsCredentialDetail { @@ -23,27 +23,27 @@ export class RefreshingStsCache implements StsCache { await this.next.removeStsCredential(name) } - async getStsCredential(name: string): Promise { + async getStsCredential(name: string): Promise { this.observability.logging.log('Retrieving STS Credential.') if (!name.trim()) { throw new AwsError('Profile name is invalid.', AwsErrorCodes.E_INVALID_PROFILE) } - const stsCredential = await this.next.getStsCredential(name) + const credential = await this.next.getStsCredential(name) - if (!stsCredential || !stsCredential.Credentials?.Expiration) { + if (!credential?.expiration) { this.observability.logging.log('STS Credential not found.') return undefined } const nowMillis = Date.now() - const expirationMillis = new Date(stsCredential.Credentials.Expiration).getTime() + const expirationMillis = new Date(credential.expiration).getTime() // Check if credential is still valid (not in refresh window) if (nowMillis < expirationMillis) { this.observability.logging.log('STS credential before refresh window. Returning current STS credential.') - return stsCredential + return credential } else { // Credential is in refresh window or expired this.observability.logging.log('STS credential has expired.') @@ -51,7 +51,7 @@ export class RefreshingStsCache implements StsCache { } } - async setStsCredential(name: string, credentials: StsCredential): Promise { + async setStsCredential(name: string, credentials: IamCredentials): Promise { this.observability.logging.log('Storing STS Credential.') await this.next.setStsCredential(name, credentials) } diff --git a/server/aws-lsp-identity/src/sts/cache/stsCache.ts b/server/aws-lsp-identity/src/sts/cache/stsCache.ts index bf403d66b3..d89e8dbc4e 100644 --- a/server/aws-lsp-identity/src/sts/cache/stsCache.ts +++ b/server/aws-lsp-identity/src/sts/cache/stsCache.ts @@ -1,12 +1,7 @@ -import { AssumeRoleCommandOutput } from '@aws-sdk/client-sts' -import { DuckTyper } from '../../duckTyper' - -export type StsCredential = Pick +import { IamCredentials } from '@aws/language-server-runtimes/protocol' export interface StsCache { - getStsCredential(name: string): Promise - setStsCredential(name: string, credentials: StsCredential): Promise + getStsCredential(name: string): Promise + setStsCredential(name: string, credentials: IamCredentials): Promise removeStsCredential(name: string): Promise } - -export const stsCredentialDuckTyper = new DuckTyper().requireProperty('Credentials').requireProperty('AssumedRoleUser') diff --git a/server/aws-lsp-identity/src/sts/stsAutoRefresher.test.ts b/server/aws-lsp-identity/src/sts/stsAutoRefresher.test.ts index d5b378a41d..7250a405a1 100644 --- a/server/aws-lsp-identity/src/sts/stsAutoRefresher.test.ts +++ b/server/aws-lsp-identity/src/sts/stsAutoRefresher.test.ts @@ -2,9 +2,8 @@ import { expect, use } from 'chai' import { StsAutoRefresher } from './stsAutoRefresher' import { StubbedInstance, stubInterface } from 'ts-sinon' import { restore, spy } from 'sinon' -import { AwsErrorCodes, Logging, Telemetry } from '@aws/language-server-runtimes/server-interface' +import { AwsErrorCodes, IamCredentials, Logging, Telemetry } from '@aws/language-server-runtimes/server-interface' import { AwsError, Observability } from '@aws/lsp-core' -import { StsCredential } from './cache/stsCache' import { RefreshingStsCache } from './cache/refreshingStsCache' // eslint-disable-next-line @@ -15,40 +14,28 @@ let observability: StubbedInstance const profileName = 'someprofile' const now = Date.now() -function createStsCredential(expiresAsOffsetMillis: number): StsCredential { +function createStsCredential(expiresAsOffsetMillis: number): IamCredentials { return { - Credentials: { - AccessKeyId: 'someaccesskeyid', - SecretAccessKey: 'somesecretaccesskey', - SessionToken: 'somesessiontoken', - Expiration: new Date(now + expiresAsOffsetMillis), - }, - AssumedRoleUser: { - Arn: 'arn:aws:sts::123456789012:assumed-role/somerole/somesession', - AssumedRoleId: 'someassumedroleid', - }, - } satisfies StsCredential + accessKeyId: 'someaccesskeyid', + secretAccessKey: 'somesecretaccesskey', + sessionToken: 'somesessiontoken', + expiration: new Date(now + expiresAsOffsetMillis), + } satisfies IamCredentials } -function refreshStsCredential(): Promise { +function refreshStsCredential(): Promise { return Promise.resolve({ - Credentials: { - AccessKeyId: 'newaccesskeyid', - SecretAccessKey: 'newsecretaccesskey', - SessionToken: 'newsessiontoken', - Expiration: new Date(now + 60 * 60 * 1000 /* 1 hour in relative seconds */), - }, - AssumedRoleUser: { - Arn: 'arn:aws:sts::123456789012:assumed-role/newrole/newsession', - AssumedRoleId: 'newassumedroleid', - }, - } satisfies StsCredential) + accessKeyId: 'newaccesskeyid', + secretAccessKey: 'newsecretaccesskey', + sessionToken: 'newsessiontoken', + expiration: new Date(now + 60 * 60 * 1000 /* 1 hour in relative seconds */), + } satisfies IamCredentials) } -function stubStsCache(stsCredential?: StsCredential): RefreshingStsCache { +function stubStsCache(credential?: IamCredentials): RefreshingStsCache { return stubInterface({ - getStsCredential: stsCredential - ? Promise.resolve(stsCredential) + getStsCredential: credential + ? Promise.resolve(credential) : Promise.reject(new AwsError('Test: No STS credential', AwsErrorCodes.E_INVALID_STS_CREDENTIAL)), }) } diff --git a/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts b/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts index ab5007924f..5336ce55e2 100644 --- a/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts +++ b/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts @@ -1,6 +1,10 @@ -import { StsCache, StsCredential } from './cache/stsCache' +import { StsCache } from './cache/stsCache' import { Observability } from '@aws/lsp-core' -import { StsCredentialChangedKind, StsCredentialChangedParams } from '@aws/language-server-runtimes/protocol' +import { + IamCredentials, + StsCredentialChangedKind, + StsCredentialChangedParams, +} from '@aws/language-server-runtimes/protocol' // Modified to match SSO token refresh behavior const refreshWindowMillis = 5 * 60 * 1000 // 5 minutes (matching SSO) @@ -32,13 +36,13 @@ export class StsAutoRefresher implements Disposable { } } - async watch(name: string, refreshCallback: () => Promise): Promise { + async watch(name: string, refreshCallback: () => Promise): Promise { try { this.unwatch(name) - const stsCredentials = await this.stsCache.getStsCredential(name).catch(_ => undefined) + const credential = await this.stsCache.getStsCredential(name).catch(_ => undefined) - if (!stsCredentials || !stsCredentials.Credentials?.Expiration) { + if (!credential?.expiration) { this.observability.logging.log( 'STS credentials do not exist or have no expiration, will not be auto-refreshed.' ) @@ -46,7 +50,7 @@ export class StsAutoRefresher implements Disposable { } const nowMillis = Date.now() - const expirationMillis = new Date(stsCredentials.Credentials?.Expiration).getTime() + const expirationMillis = new Date(credential.expiration).getTime() // Get or create StsCredentialDetail (matching SSO pattern) const stsCredentialDetail = From 228dbad99fd8afc01c84544c8a3a1d70a46db5ac Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Fri, 18 Jul 2025 11:02:53 -0400 Subject: [PATCH 28/37] fix: remove unnecessary logs --- .../aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts index a047b038eb..df3c4d135a 100644 --- a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts +++ b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts @@ -91,9 +91,6 @@ describe('FileSystemStsCache', () => { setupTest() const actual = await sut.getStsCredential(id) - console.log('testabc') - console.log(actual?.expiration) - console.log(typeof actual?.expiration) expect(actual).to.not.be.null.and.not.undefined expect(actual?.accessKeyId).to.equal(credential.accessKeyId) From 5cb8876fa2859b0f68502f1b94a015a4dd894b4d Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Mon, 21 Jul 2025 10:06:08 -0400 Subject: [PATCH 29/37] fix: remove dummy settings from unknown profiles and wrap sendGetMfaCode in lambda --- .../src/language-server/identityServer.ts | 3 ++- .../profiles/sharedConfigProfileStore.test.ts | 16 ++++------------ .../profiles/sharedConfigProfileStore.ts | 2 -- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/server/aws-lsp-identity/src/language-server/identityServer.ts b/server/aws-lsp-identity/src/language-server/identityServer.ts index c0f3cd290a..6723569625 100644 --- a/server/aws-lsp-identity/src/language-server/identityServer.ts +++ b/server/aws-lsp-identity/src/language-server/identityServer.ts @@ -46,7 +46,8 @@ export class IdentityServer extends ServerBase { const showMessageRequest: ShowMessageRequest = (params: ShowMessageRequestParams) => this.features.lsp.window.showMessageRequest(params) const showProgress: ShowProgress = this.features.lsp.sendProgress - const sendGetMfaCode: SendGetMfaCode = this.features.identityManagement.sendGetMfaCode + const sendGetMfaCode: SendGetMfaCode = (params: GetMfaCodeParams) => + this.features.identityManagement.sendGetMfaCode(params) // Initialize dependencies const profileStore = new SharedConfigProfileStore(this.observability) diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts index d8783974c5..227003dbd8 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts @@ -99,9 +99,7 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: { - region: undefined, - }, + settings: {}, }, { kinds: [ProfileKind.SsoTokenProfile], @@ -192,9 +190,7 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: { - region: undefined, - }, + settings: {}, }, { kinds: [ProfileKind.SsoTokenProfile], @@ -290,9 +286,7 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: { - region: undefined, - }, + settings: {}, }, { kinds: ['SsoTokenProfile'], @@ -440,9 +434,7 @@ describe('SharedConfigProfileStore', async () => { { kinds: [ProfileKind.Unknown], name: 'subsettings', - settings: { - region: undefined, - }, + settings: {}, }, ], ssoSessions: [ diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts index a1cdbcca28..0b612a46a0 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.ts @@ -67,8 +67,6 @@ export class SharedConfigProfileStore implements ProfileStore { // If the profile does not match any profile type, mark it as an unknown profile if (profile.kinds.length === 0) { profile.kinds.push(ProfileKind.Unknown) - // Dummy field to avoid deleting profile when loading and saving 0 changes to the profile - profile.settings!['region'] = settings['region'] } result.profiles.push(profile) break From 4f2bb03fbf943c3c71d3793b419205273efbf3c1 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Mon, 21 Jul 2025 11:35:08 -0400 Subject: [PATCH 30/37] fix: use profile session name if it exists --- server/aws-lsp-identity/src/iam/iamProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/aws-lsp-identity/src/iam/iamProvider.ts b/server/aws-lsp-identity/src/iam/iamProvider.ts index 9cb57bfbb5..5eade4ebcc 100644 --- a/server/aws-lsp-identity/src/iam/iamProvider.ts +++ b/server/aws-lsp-identity/src/iam/iamProvider.ts @@ -134,7 +134,7 @@ export class IamProvider { // Add MFA fields to assume role request if MultiFactorAuthPresent is required const assumeRoleInput: AssumeRoleCommandInput = { RoleArn: params.profile.settings?.role_arn, - RoleSessionName: `session-${Date.now()}`, + RoleSessionName: params.profile.settings?.role_session_name || `session-${Date.now()}`, DurationSeconds: 3600, } const response = await simulatePermissions( From d97700499de01efb83bbe8d00dcf1bedfa779a1c Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Tue, 22 Jul 2025 12:37:55 -0400 Subject: [PATCH 31/37] fix: incorporate STS PR feedback --- .../src/iam/iamProvider.test.ts | 24 ++------- .../aws-lsp-identity/src/iam/iamProvider.ts | 28 +++++----- server/aws-lsp-identity/src/iam/utils.ts | 52 +++++++++++++++---- .../src/language-server/identityServer.ts | 20 ++++--- .../language-server/identityService.test.ts | 22 ++++++-- .../src/language-server/identityService.ts | 10 ++-- .../src/sso/cache/refreshingSsoCache.ts | 8 ++- server/aws-lsp-identity/src/sso/utils.ts | 6 ++- .../src/sts/cache/fileSystemStsCache.ts | 2 +- .../src/sts/cache/refreshingStsCache.ts | 4 +- 10 files changed, 109 insertions(+), 67 deletions(-) diff --git a/server/aws-lsp-identity/src/iam/iamProvider.test.ts b/server/aws-lsp-identity/src/iam/iamProvider.test.ts index 12667adcd0..b6237905c7 100644 --- a/server/aws-lsp-identity/src/iam/iamProvider.test.ts +++ b/server/aws-lsp-identity/src/iam/iamProvider.test.ts @@ -11,7 +11,6 @@ import { IamProvider } from '../iam/iamProvider' import { IamFlowParams } from './utils' import * as iamUtils from '../iam/utils' import { STSClient } from '@aws-sdk/client-sts' -import { SimulatePrincipalPolicyCommandOutput } from '@aws-sdk/client-iam' // eslint-disable-next-line use(require('chai-as-promised')) @@ -25,9 +24,9 @@ let stsAutoRefresher: StubbedInstance let handlers: StubbedInstance let observability: StubbedInstance let token: StubbedInstance -let simulatePermissionsStub: SinonStub< +let checkMfaRequiredStub: SinonStub< [credentials: IamCredentials, permissions: string[], region?: string | undefined], - Promise + Promise > describe('IamProvider', () => { @@ -127,11 +126,8 @@ describe('IamProvider', () => { sut = new IamProvider() - simulatePermissionsStub = stub(iamUtils, 'simulatePermissions') - simulatePermissionsStub.resolves({ - $metadata: {}, - EvaluationResults: [], - }) + checkMfaRequiredStub = stub(iamUtils, 'checkMfaRequired') + checkMfaRequiredStub.resolves(false) stub(STSClient.prototype, 'send').resolves({ Credentials: { @@ -188,17 +184,7 @@ describe('IamProvider', () => { }) it('Can generate credentials with MFA.', async () => { - simulatePermissionsStub.resolves({ - $metadata: {}, - EvaluationResults: [ - { - EvalActionName: 'name', - EvalResourceName: 'resource', - EvalDecision: 'implicitDeny', - MissingContextValues: ['aws:MultiFactorAuthPresent'], - }, - ], - }) + checkMfaRequiredStub.resolves(true) const profile: Profile = { kinds: [ProfileKind.IamSourceProfileProfile], name: 'my-mfa-profile', diff --git a/server/aws-lsp-identity/src/iam/iamProvider.ts b/server/aws-lsp-identity/src/iam/iamProvider.ts index 5eade4ebcc..f90357b88e 100644 --- a/server/aws-lsp-identity/src/iam/iamProvider.ts +++ b/server/aws-lsp-identity/src/iam/iamProvider.ts @@ -7,21 +7,31 @@ import { } from '@aws/language-server-runtimes/server-interface' import { AwsError } from '@aws/lsp-core' import { AssumeRoleCommand, AssumeRoleCommandInput, STSClient } from '@aws-sdk/client-sts' -import { IamFlowParams, simulatePermissions } from './utils' +import { checkMfaRequired, IamFlowParams } from './utils' import { createHash } from 'crypto' const sourceProfileRecursionMax = 5 const mfaTimeout = 2 * 60 * 1000 // 2 minutes export class IamProvider { + readonly defaultRegion = 'us-east-1' private sourceProfileRecursionCount = 0 async getCredential(params: IamFlowParams): Promise { try { let id: IamCredentialId = '' let credentials: IamCredentials + + // Get the credentials directly from the profile + if (params.profile.kinds.includes(ProfileKind.IamCredentialsProfile)) { + credentials = { + accessKeyId: params.profile.settings!.aws_access_key_id!, + secretAccessKey: params.profile.settings!.aws_secret_access_key!, + sessionToken: params.profile.settings!.aws_session_token!, + } + } // Assume the role matching the found ARN - if (params.profile.kinds.includes(ProfileKind.IamSourceProfileProfile)) { + else if (params.profile.kinds.includes(ProfileKind.IamSourceProfileProfile)) { const key = JSON.stringify({ RoleArn: params.profile.settings?.role_arn, RoleSessionName: params.profile.settings?.role_session_name, @@ -29,14 +39,6 @@ export class IamProvider { }) id = createHash('sha1').update(key).digest('hex') credentials = await this.getAssumedRoleCredential(id, params) - } - // Get the credentials directly from the profile - else if (params.profile.kinds.includes(ProfileKind.IamCredentialsProfile)) { - credentials = { - accessKeyId: params.profile.settings!.aws_access_key_id!, - secretAccessKey: params.profile.settings!.aws_secret_access_key!, - sessionToken: params.profile.settings!.aws_session_token!, - } } else { throw new AwsError( 'Credentials could not be found for provided profile kind', @@ -127,7 +129,7 @@ export class IamProvider { try { const parentCredentials = await this.getParentCredential(params) const stsClient = new STSClient({ - region: params.profile.settings?.region || 'us-east-1', + region: params.profile.settings?.region || this.defaultRegion, credentials: parentCredentials, }) @@ -137,12 +139,12 @@ export class IamProvider { RoleSessionName: params.profile.settings?.role_session_name || `session-${Date.now()}`, DurationSeconds: 3600, } - const response = await simulatePermissions( + const mfaRequired = await checkMfaRequired( parentCredentials, ['sts:AssumeRole'], params.profile.settings?.region ) - if (response.EvaluationResults?.[0]?.MissingContextValues?.includes('aws:MultiFactorAuthPresent')) { + if (mfaRequired) { // Get the MFA device serial number from the profile if (!params.profile.settings?.mfa_serial) { throw new AwsError( diff --git a/server/aws-lsp-identity/src/iam/utils.ts b/server/aws-lsp-identity/src/iam/utils.ts index 2e60851fde..4ae0af07ba 100644 --- a/server/aws-lsp-identity/src/iam/utils.ts +++ b/server/aws-lsp-identity/src/iam/utils.ts @@ -7,27 +7,62 @@ import { GetMfaCodeResult, IamCredentials, Profile, + StsCredentialChangedParams, } from '@aws/language-server-runtimes/server-interface' import { AwsError, Observability } from '@aws/lsp-core' import { StsCache } from '../sts/cache/stsCache' import { StsAutoRefresher } from '../sts/stsAutoRefresher' import { ProfileStore } from '../language-server/profiles/profileService' -// Simulate permissions on the identity associated with the credentials -export async function simulatePermissions( +export async function validatePermissions( + credentials: IamCredentials, + permissions: string[], + region?: string +): Promise { + const response = await simulatePermissions(credentials, permissions, region) + // If evaluation results are missing, assume caller does not have sufficient permissions + if (!response.EvaluationResults) { + return false + } + return response.EvaluationResults.every(result => result.EvalDecision === 'allowed') +} + +export async function checkMfaRequired( credentials: IamCredentials, permissions: string[], region?: string +): Promise { + const response = await simulatePermissions(credentials, permissions, region) + // If evaluation results are missing, assume caller does not need MFA + if (!response.EvaluationResults) { + return false + } + return response.EvaluationResults?.some(result => + result?.MissingContextValues?.includes('aws:MultiFactorAuthPresent') + ) +} + +export function throwOnInvalidCredentialId(iamCredentialId?: string): asserts iamCredentialId is string { + if (typeof iamCredentialId?.trim !== 'function' || !iamCredentialId?.trim()) { + throw new AwsError('IAM credential id is invalid.', AwsErrorCodes.E_INVALID_STS_CREDENTIAL) + } +} + +// Simulate permissions on the identity associated with the credentials +async function simulatePermissions( + credentials: IamCredentials, + permissions: string[], + region: string = 'us-east-1' ): Promise { // Convert the credentials into an identity - const stsClient = new STSClient({ region: region || 'us-east-1', credentials: credentials }) + const stsClient = new STSClient({ region: region, credentials: credentials }) const identity = await stsClient.send(new GetCallerIdentityCommand({})) if (!identity.Arn) { - throw new AwsError('Caller identity ARN not found.', AwsErrorCodes.E_INVALID_PROFILE) + throw new AwsError('Caller identity ARN not found.', AwsErrorCodes.E_CALLER_IDENTITY_NOT_FOUND) } // Simulate permissions on the identity - const iamClient = new IAMClient({ region: region || 'us-east-1', credentials: credentials }) + const iamClient = new IAMClient({ region: region, credentials: credentials }) return await iamClient.send( new SimulatePrincipalPolicyCommand({ PolicySourceArn: convertToIamArn(identity.Arn), @@ -47,13 +82,8 @@ function convertToIamArn(arn: string) { } } -export function throwOnInvalidCredentialId(iamCredentialId?: string): asserts iamCredentialId is string { - if (!iamCredentialId?.trim()) { - throw new AwsError('IAM credential id is invalid.', AwsErrorCodes.E_INVALID_STS_CREDENTIAL) - } -} - export type SendGetMfaCode = (params: GetMfaCodeParams) => Promise +export type SendStsCredentialChanged = (params: StsCredentialChangedParams) => void export type IamHandlers = { sendGetMfaCode: SendGetMfaCode diff --git a/server/aws-lsp-identity/src/language-server/identityServer.ts b/server/aws-lsp-identity/src/language-server/identityServer.ts index 6723569625..db5f2a5d78 100644 --- a/server/aws-lsp-identity/src/language-server/identityServer.ts +++ b/server/aws-lsp-identity/src/language-server/identityServer.ts @@ -13,6 +13,8 @@ import { ShowMessageRequestParams, GetIamCredentialParams, GetMfaCodeParams, + StsCredentialChangedParams, + SsoTokenChangedParams, } from '@aws/language-server-runtimes/server-interface' import { SharedConfigProfileStore } from './profiles/sharedConfigProfileStore' import { IdentityService } from './identityService' @@ -23,8 +25,8 @@ import { FileSystemStsCache } from '../sts/cache/fileSystemStsCache' import { StsAutoRefresher } from '../sts/stsAutoRefresher' import { AwsError, ServerBase } from '@aws/lsp-core' import { Features } from '@aws/language-server-runtimes/server-interface/server' -import { ShowUrl, ShowMessageRequest, ShowProgress } from '../sso/utils' -import { SendGetMfaCode } from '../iam/utils' +import { ShowUrl, ShowMessageRequest, ShowProgress, SendSsoTokenChanged } from '../sso/utils' +import { SendGetMfaCode, SendStsCredentialChanged } from '../iam/utils' import { IamProvider } from '../iam/iamProvider' export class IdentityServer extends ServerBase { @@ -49,22 +51,24 @@ export class IdentityServer extends ServerBase { const sendGetMfaCode: SendGetMfaCode = (params: GetMfaCodeParams) => this.features.identityManagement.sendGetMfaCode(params) + // Callbacks for client->server JSON-RPC calls + const sendSsoTokenChanged: SendSsoTokenChanged = (params: SsoTokenChangedParams) => + this.features.identityManagement.sendSsoTokenChanged(params) + const sendStsCredentialChanged: SendStsCredentialChanged = (params: StsCredentialChangedParams) => + this.features.identityManagement.sendStsCredentialChanged(params) + // Initialize dependencies const profileStore = new SharedConfigProfileStore(this.observability) const ssoCache = new RefreshingSsoCache( new FileSystemSsoCache(this.observability), - this.features.identityManagement.sendSsoTokenChanged, + sendSsoTokenChanged, this.observability ) const autoRefresher = new SsoTokenAutoRefresher(ssoCache, this.observability) const stsCache = new RefreshingStsCache(new FileSystemStsCache(this.observability), this.observability) - const stsAutoRefresher = new StsAutoRefresher( - stsCache, - this.features.identityManagement.sendStsCredentialChanged, - this.observability - ) + const stsAutoRefresher = new StsAutoRefresher(stsCache, sendStsCredentialChanged, this.observability) const iamProvider = new IamProvider() const identityService = new IdentityService( diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index 9940940b12..9ae2692025 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -4,11 +4,12 @@ import { awsBuilderIdReservedName, SsoCache, SsoClientRegistration } from '../ss import { IdentityService } from './identityService' import { ProfileData, ProfileStore } from './profiles/profileService' import { SsoTokenAutoRefresher } from './ssoTokenAutoRefresher' -import { createStubInstance, restore, spy, SinonSpy, stub } from 'sinon' +import { createStubInstance, restore, spy, SinonSpy, stub, SinonStub } from 'sinon' import { AuthorizationFlowKind, CancellationToken, IamCredential, + IamCredentials, ProfileKind, SsoTokenSourceKind, } from '@aws/language-server-runtimes/protocol' @@ -33,6 +34,10 @@ let stsAutoRefresher: StubbedInstance let iamProvider: StubbedInstance let observability: StubbedInstance let authFlowFn: SinonSpy +let validatePermissionsStub: SinonStub< + [credentials: IamCredentials, permissions: string[], region?: string | undefined], + Promise +> describe('IdentityService', () => { beforeEach(() => { @@ -140,10 +145,8 @@ describe('IdentityService', () => { } ) - stub(iamUtils, 'simulatePermissions').resolves({ - $metadata: {}, - EvaluationResults: [], - }) + validatePermissionsStub = stub(iamUtils, 'validatePermissions') + validatePermissionsStub.resolves(true) }) afterEach(() => { @@ -309,6 +312,15 @@ describe('IdentityService', () => { expect(actual.credential.credentials.accessKeyId).to.equal('access-key') expect(actual.credential.credentials.secretAccessKey).to.equal('secret-key') }) + + it('Throws when permissions are insufficient', async () => { + validatePermissionsStub.resolves(false) + const error = await expect( + sut.getIamCredential({ profileName: 'my-iam-profile' }, CancellationToken.None) + ).rejectedWith(Error) + + expect(error.message).to.equal('Credentials have insufficient permissions.') + }) }) describe('invalidateSsoToken', () => { diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 05b1f1bc2d..d442a66e0f 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -31,7 +31,7 @@ import { SsoFlowParams, SsoHandlers, } from '../sso/utils' -import { IamFlowParams, IamHandlers, simulatePermissions, throwOnInvalidCredentialId } from '../iam/utils' +import { IamFlowParams, IamHandlers, throwOnInvalidCredentialId, validatePermissions } from '../iam/utils' import { AwsError, Observability } from '@aws/lsp-core' import { __ServiceException } from '@aws-sdk/client-sso-oidc/dist-types/models/SSOOIDCServiceException' import { deviceCodeFlow } from '../sso/deviceCode/deviceCodeFlow' @@ -108,7 +108,7 @@ export class IdentityService { clientName: this.clientName, clientRegistration, ssoSession, - handlers: this.handlers as Pick, + handlers: this.handlers as SsoHandlers, token, observability: this.observability, } @@ -179,7 +179,7 @@ export class IdentityService { profileStore: this.profileStore, stsCache: this.stsCache, stsAutoRefresher: this.stsAutoRefresher, - handlers: { sendGetMfaCode: this.handlers.sendGetMfaCode }, + handlers: this.handlers as IamHandlers, token: token, observability: this.observability, } @@ -187,12 +187,12 @@ export class IdentityService { // Validate permissions if (options.permissionSet.length > 0) { - const response = await simulatePermissions( + const hasPermissions = await validatePermissions( credential.credentials, options.permissionSet, profile.settings?.region ) - if (!response?.EvaluationResults?.every(result => result.EvalDecision === 'allowed')) { + if (!hasPermissions) { throw new AwsError(`Credentials have insufficient permissions.`, AwsErrorCodes.E_INVALID_PROFILE) } } diff --git a/server/aws-lsp-identity/src/sso/cache/refreshingSsoCache.ts b/server/aws-lsp-identity/src/sso/cache/refreshingSsoCache.ts index d642f0a36b..7f7570af89 100644 --- a/server/aws-lsp-identity/src/sso/cache/refreshingSsoCache.ts +++ b/server/aws-lsp-identity/src/sso/cache/refreshingSsoCache.ts @@ -128,14 +128,18 @@ export class RefreshingSsoCache implements SsoCache { // Current time is before start of refresh window? Just return it const refreshAfterMillis = accessTokenExpiresAtMillis - refreshWindowMillis if (nowMillis < refreshAfterMillis) { - this.observability.logging.log('SSO token before refresh window. Returning current SSO token.') + this.observability.logging.log( + 'SSO token expiration is before refresh window. Returning current SSO token.' + ) return ssoToken } // Last refresh attempt was less than the retry window? Just return it const retryAfterMillis = ssoTokenDetail.lastRefreshMillis + retryCooldownWindowMillis if (nowMillis < retryAfterMillis) { - this.observability.logging.log('SSO token in retry cooldown window. Returning current SSO token.') + this.observability.logging.log( + 'SSO token expiration is in retry cooldown window. Returning current SSO token.' + ) return ssoToken } } diff --git a/server/aws-lsp-identity/src/sso/utils.ts b/server/aws-lsp-identity/src/sso/utils.ts index 29841df6e4..38c6846b17 100644 --- a/server/aws-lsp-identity/src/sso/utils.ts +++ b/server/aws-lsp-identity/src/sso/utils.ts @@ -5,6 +5,7 @@ import { ShowMessageRequestParams, SsoSession, Lsp, + SsoTokenChangedParams, } from '@aws/language-server-runtimes/server-interface' import { CreateTokenCommandOutput, SSOOIDC, SSOOIDCClientConfig } from '@aws-sdk/client-sso-oidc' import { SsoClientRegistration } from './cache' @@ -36,7 +37,7 @@ export function getSsoOidc(ssoRegion: string): SSOOIDC & Disposable { } export function throwOnInvalidClientName(clientName?: string): asserts clientName is string { - if (!clientName?.trim().length) { + if (typeof clientName?.trim !== 'function' || !clientName?.trim().length) { throw new AwsError(`Client name [${clientName}] is invalid.`, AwsErrorCodes.E_INVALID_SSO_CLIENT) } } @@ -57,7 +58,7 @@ export function throwOnInvalidClientRegistration( } export function throwOnInvalidSsoSessionName(ssoSessionName?: string): asserts ssoSessionName is string { - if (!ssoSessionName?.trim()) { + if (typeof ssoSessionName?.trim !== 'function' || !ssoSessionName?.trim()) { throw new AwsError('SSO session name is invalid.', AwsErrorCodes.E_INVALID_SSO_SESSION) } } @@ -111,6 +112,7 @@ export function UpdateSsoTokenFromCreateToken( export type ShowUrl = (url: URL) => void export type ShowMessageRequest = (params: ShowMessageRequestParams) => Promise export type ShowProgress = Lsp['sendProgress'] +export type SendSsoTokenChanged = (params: SsoTokenChangedParams) => void export type SsoHandlers = { showUrl: ShowUrl showMessageRequest: ShowMessageRequest diff --git a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts index 20c60539a5..87a2ad5e42 100644 --- a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts +++ b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts @@ -40,7 +40,7 @@ export class FileSystemStsCache implements StsCache { if ( !(credential.accessKeyId && credential.secretAccessKey && credential.sessionToken && credential.expiration) ) { - this.observability.logging.log('File read from STS cache is not an STS credential.') + this.observability.logging.log('Cannot set credential: missing fields.') return } diff --git a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts index 23daba88c1..3f191b7e89 100644 --- a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts +++ b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts @@ -42,7 +42,9 @@ export class RefreshingStsCache implements StsCache { // Check if credential is still valid (not in refresh window) if (nowMillis < expirationMillis) { - this.observability.logging.log('STS credential before refresh window. Returning current STS credential.') + this.observability.logging.log( + 'STS credential expiration is before refresh window. Returning current STS credential.' + ) return credential } else { // Credential is in refresh window or expired From 023dac1f6cb1ad375d21bf48ed1b10885fbea8b7 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Tue, 22 Jul 2025 17:10:39 -0400 Subject: [PATCH 32/37] refactor: move common auto refresh logic into AutoRefresher class --- .../src/language-server/autoRefresher.ts | 45 +++++++ .../src/language-server/identityServer.ts | 2 +- .../language-server/identityService.test.ts | 2 +- .../src/language-server/identityService.ts | 8 +- .../src/sso/cache/refreshingSsoCache.ts | 8 +- .../ssoTokenAutoRefresher.test.ts | 0 .../ssoTokenAutoRefresher.ts | 57 +++------ .../src/sts/cache/refreshingStsCache.ts | 27 ++-- .../src/sts/stsAutoRefresher.ts | 119 +++++++----------- 9 files changed, 119 insertions(+), 149 deletions(-) create mode 100644 server/aws-lsp-identity/src/language-server/autoRefresher.ts rename server/aws-lsp-identity/src/{language-server => sso}/ssoTokenAutoRefresher.test.ts (100%) rename server/aws-lsp-identity/src/{language-server => sso}/ssoTokenAutoRefresher.ts (54%) diff --git a/server/aws-lsp-identity/src/language-server/autoRefresher.ts b/server/aws-lsp-identity/src/language-server/autoRefresher.ts new file mode 100644 index 0000000000..0ec337079d --- /dev/null +++ b/server/aws-lsp-identity/src/language-server/autoRefresher.ts @@ -0,0 +1,45 @@ +import { Observability } from '@aws/lsp-core' + +export const refreshWindowMillis: number = 5 * 60 * 1000 +export const retryCooldownWindowMillis: number = 30000 +const bufferedRefreshWindowMillis = refreshWindowMillis * 0.95 +const bufferedRetryCooldownWindowMillis = retryCooldownWindowMillis * 1.05 +const maxRefreshJitterMillis = 10000 +const maxRetryCooldownJitterMillis = 3000 + +export abstract class AutoRefresher implements Disposable { + protected readonly timeouts: Record = {} + + constructor(protected readonly observability: Observability) {} + + [Symbol.dispose](): void { + for (const key of Object.keys(this.timeouts)) { + this.unwatch(key) + } + } + + protected abstract unwatch(key: string): void + + getDelay(expiration: string): number { + const nowMillis = Date.now() + const expiresAtMillis = Date.parse(expiration) + let delayMillis: number + + if (nowMillis < expiresAtMillis - refreshWindowMillis) { + // Before refresh window, schedule to run in refresh window with jitter + delayMillis = expiresAtMillis - bufferedRefreshWindowMillis - nowMillis + delayMillis += Math.random() * maxRefreshJitterMillis // Jitter to mitigate race conditions + } else if (expiresAtMillis - refreshWindowMillis < nowMillis && nowMillis < expiresAtMillis) { + // In refresh window with time for a retry + delayMillis = bufferedRetryCooldownWindowMillis + delayMillis += Math.random() * maxRetryCooldownJitterMillis // Jitter to mitigate race conditions + } else { + // Otherwise, expired + this.observability.logging.log('SSO token has expired and will not be auto-refreshed.') + return -1 + } + + this.observability.logging.log(`Auto-refreshing SSO token in ${delayMillis} milliseconds.`) + return delayMillis + } +} diff --git a/server/aws-lsp-identity/src/language-server/identityServer.ts b/server/aws-lsp-identity/src/language-server/identityServer.ts index db5f2a5d78..82236d6e64 100644 --- a/server/aws-lsp-identity/src/language-server/identityServer.ts +++ b/server/aws-lsp-identity/src/language-server/identityServer.ts @@ -20,7 +20,7 @@ import { SharedConfigProfileStore } from './profiles/sharedConfigProfileStore' import { IdentityService } from './identityService' import { FileSystemSsoCache, RefreshingSsoCache } from '../sso/cache' import { RefreshingStsCache } from '../sts/cache/refreshingStsCache' -import { SsoTokenAutoRefresher } from './ssoTokenAutoRefresher' +import { SsoTokenAutoRefresher } from '../sso/ssoTokenAutoRefresher' import { FileSystemStsCache } from '../sts/cache/fileSystemStsCache' import { StsAutoRefresher } from '../sts/stsAutoRefresher' import { AwsError, ServerBase } from '@aws/lsp-core' diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index 9ae2692025..5f3d8cc5e1 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -3,7 +3,7 @@ import { StubbedInstance, stubInterface } from 'ts-sinon' import { awsBuilderIdReservedName, SsoCache, SsoClientRegistration } from '../sso' import { IdentityService } from './identityService' import { ProfileData, ProfileStore } from './profiles/profileService' -import { SsoTokenAutoRefresher } from './ssoTokenAutoRefresher' +import { SsoTokenAutoRefresher } from '../sso/ssoTokenAutoRefresher' import { createStubInstance, restore, spy, SinonSpy, stub, SinonStub } from 'sinon' import { AuthorizationFlowKind, diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index d442a66e0f..0f8e9fa7fb 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -21,7 +21,7 @@ import { import { normalizeSettingList, ProfileStore } from './profiles/profileService' import { authorizationCodePkceFlow, awsBuilderIdReservedName, awsBuilderIdSsoRegion } from '../sso' import { SsoCache, SsoClientRegistration } from '../sso/cache' -import { SsoTokenAutoRefresher } from './ssoTokenAutoRefresher' +import { SsoTokenAutoRefresher } from '../sso/ssoTokenAutoRefresher' import { StsCache } from '../sts/cache/stsCache' import { StsAutoRefresher } from '../sts/stsAutoRefresher' import { @@ -51,7 +51,7 @@ export class IdentityService { constructor( private readonly profileStore: ProfileStore, private readonly ssoCache: SsoCache, - private readonly autoRefresher: SsoTokenAutoRefresher, + private readonly ssoAutoRefresher: SsoTokenAutoRefresher, private readonly stsCache: StsCache, private readonly stsAutoRefresher: StsAutoRefresher, private readonly iamProvider: IamProvider, @@ -134,7 +134,7 @@ export class IdentityService { } // Auto refresh is best effort - await this.autoRefresher.watch(this.clientName, ssoSession).catch(reason => { + await this.ssoAutoRefresher.watch(this.clientName, ssoSession).catch(reason => { this.observability.logging.log(`Unable to auto-refresh token. ${reason}`) }) @@ -227,7 +227,7 @@ export class IdentityService { try { throwOnInvalidSsoSessionName(params?.ssoTokenId) - this.autoRefresher.unwatch(params.ssoTokenId) + this.ssoAutoRefresher.unwatch(params.ssoTokenId) await this.ssoCache.removeSsoToken(params.ssoTokenId) diff --git a/server/aws-lsp-identity/src/sso/cache/refreshingSsoCache.ts b/server/aws-lsp-identity/src/sso/cache/refreshingSsoCache.ts index 7f7570af89..197fd1680b 100644 --- a/server/aws-lsp-identity/src/sso/cache/refreshingSsoCache.ts +++ b/server/aws-lsp-identity/src/sso/cache/refreshingSsoCache.ts @@ -7,13 +7,11 @@ import { throwOnInvalidClientName, UpdateSsoTokenFromCreateToken, throwOnInvalidSsoSessionName, + SendSsoTokenChanged, } from '../utils' -import { RaiseSsoTokenChanged } from '../../language-server/ssoTokenAutoRefresher' import { CreateTokenCommandOutput, InvalidGrantException } from '@aws-sdk/client-sso-oidc' import { AwsError, Observability } from '@aws/lsp-core' - -export const refreshWindowMillis: number = 5 * 60 * 1000 -export const retryCooldownWindowMillis: number = 30000 +import { refreshWindowMillis, retryCooldownWindowMillis } from '../../language-server/autoRefresher' interface SsoTokenDetail { lastRefreshMillis: number @@ -24,7 +22,7 @@ export class RefreshingSsoCache implements SsoCache { constructor( private readonly next: SsoCache, - private readonly raiseSsoTokenChanged: RaiseSsoTokenChanged, + private readonly raiseSsoTokenChanged: SendSsoTokenChanged, private readonly observability: Observability ) {} diff --git a/server/aws-lsp-identity/src/language-server/ssoTokenAutoRefresher.test.ts b/server/aws-lsp-identity/src/sso/ssoTokenAutoRefresher.test.ts similarity index 100% rename from server/aws-lsp-identity/src/language-server/ssoTokenAutoRefresher.test.ts rename to server/aws-lsp-identity/src/sso/ssoTokenAutoRefresher.test.ts diff --git a/server/aws-lsp-identity/src/language-server/ssoTokenAutoRefresher.ts b/server/aws-lsp-identity/src/sso/ssoTokenAutoRefresher.ts similarity index 54% rename from server/aws-lsp-identity/src/language-server/ssoTokenAutoRefresher.ts rename to server/aws-lsp-identity/src/sso/ssoTokenAutoRefresher.ts index 477d5c901f..5b448308b1 100644 --- a/server/aws-lsp-identity/src/language-server/ssoTokenAutoRefresher.ts +++ b/server/aws-lsp-identity/src/sso/ssoTokenAutoRefresher.ts @@ -1,30 +1,18 @@ -import { SsoSession, SsoTokenChangedParams } from '@aws/language-server-runtimes/protocol' -import { RefreshingSsoCache, refreshWindowMillis, retryCooldownWindowMillis } from '../sso/cache/refreshingSsoCache' -import { throwOnInvalidClientName, throwOnInvalidSsoSession, throwOnInvalidSsoSessionName } from '../sso/utils' +import { SsoSession } from '@aws/language-server-runtimes/protocol' +import { RefreshingSsoCache } from './cache/refreshingSsoCache' +import { throwOnInvalidClientName, throwOnInvalidSsoSession, throwOnInvalidSsoSessionName } from './utils' import { MetricEvent } from '@aws/language-server-runtimes/server-interface' -import { normalizeSettingList } from './profiles/profileService' +import { normalizeSettingList } from '../language-server/profiles/profileService' import { __ServiceException } from '@aws-sdk/client-sso-oidc/dist-types/models/SSOOIDCServiceException' import { AwsError, Observability } from '@aws/lsp-core' +import { AutoRefresher } from '../language-server/autoRefresher' -const bufferedRefreshWindowMillis = refreshWindowMillis * 0.95 -const bufferedRetryCooldownWindowMillis = retryCooldownWindowMillis * 1.05 -const maxRefreshJitterMillis = 10000 -const maxRetryCooldownJitterMillis = 3000 - -export type RaiseSsoTokenChanged = (params: SsoTokenChangedParams) => void - -export class SsoTokenAutoRefresher implements Disposable { - private readonly timeouts: Record = {} - +export class SsoTokenAutoRefresher extends AutoRefresher { constructor( private readonly ssoCache: RefreshingSsoCache, - private readonly observability: Observability - ) {} - - [Symbol.dispose](): void { - for (const ssoSessionName of Object.keys(this.timeouts)) { - this.unwatch(ssoSessionName) - } + observability: Observability + ) { + super(observability) } async watch(clientName: string, ssoSession: SsoSession): Promise { @@ -46,29 +34,12 @@ export class SsoTokenAutoRefresher implements Disposable { return } - const nowMillis = Date.now() - const accessTokenExpiresAtMillis = Date.parse(ssoToken.expiresAt) - let delayMillis: number - - if (nowMillis < accessTokenExpiresAtMillis - refreshWindowMillis) { - // Before refresh window, schedule to run in refresh window with jitter - delayMillis = accessTokenExpiresAtMillis - bufferedRefreshWindowMillis - nowMillis - delayMillis += Math.random() * maxRefreshJitterMillis // Jitter to mitigate race conditions - } else if ( - accessTokenExpiresAtMillis - refreshWindowMillis < nowMillis && - nowMillis < accessTokenExpiresAtMillis - ) { - // In refresh window with time for a retry - delayMillis = bufferedRetryCooldownWindowMillis - delayMillis += Math.random() * maxRetryCooldownJitterMillis // Jitter to mitigate race conditions - } else { - // Otherwise, expired - this.observability.logging.log('SSO token has expired and will not be auto-refreshed.') - return + // Refresh timeout if delay is valid + const delayMillis = this.getDelay(ssoToken.expiresAt) + if (delayMillis >= 0) { + this.observability.logging.log(`Auto-refreshing SSO token in ${delayMillis} milliseconds.`) + this.timeouts[ssoSession.name] = setTimeout(this.watch.bind(this, clientName, ssoSession), delayMillis) } - - this.observability.logging.log(`Auto-refreshing SSO token in ${delayMillis} milliseconds.`) - this.timeouts[ssoSession.name] = setTimeout(this.watch.bind(this, clientName, ssoSession), delayMillis) } catch (e) { emitMetric(e, ssoSession) diff --git a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts index 3f191b7e89..f8ea98334b 100644 --- a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts +++ b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts @@ -1,36 +1,27 @@ import { StsCache } from './stsCache' import { AwsErrorCodes, IamCredentials } from '@aws/language-server-runtimes/server-interface' import { AwsError, Observability } from '@aws/lsp-core' - -interface StsCredentialDetail { - lastRefreshMillis: number -} +import { throwOnInvalidCredentialId } from '../../iam/utils' export class RefreshingStsCache implements StsCache { - private readonly stsCredentialDetails: Record = {} - constructor( private readonly next: StsCache, private readonly observability: Observability ) {} - async removeStsCredential(name: string): Promise { + async removeStsCredential(iamCredentialId: string): Promise { this.observability.logging.log('Removing STS Credential.') - if (!name.trim()) { - throw new AwsError('Profile name is invalid.', AwsErrorCodes.E_INVALID_PROFILE) - } + throwOnInvalidCredentialId(iamCredentialId) - await this.next.removeStsCredential(name) + await this.next.removeStsCredential(iamCredentialId) } - async getStsCredential(name: string): Promise { + async getStsCredential(iamCredentialId: string): Promise { this.observability.logging.log('Retrieving STS Credential.') - if (!name.trim()) { - throw new AwsError('Profile name is invalid.', AwsErrorCodes.E_INVALID_PROFILE) - } + throwOnInvalidCredentialId(iamCredentialId) - const credential = await this.next.getStsCredential(name) + const credential = await this.next.getStsCredential(iamCredentialId) if (!credential?.expiration) { this.observability.logging.log('STS Credential not found.') @@ -53,8 +44,8 @@ export class RefreshingStsCache implements StsCache { } } - async setStsCredential(name: string, credentials: IamCredentials): Promise { + async setStsCredential(iamCredentialId: string, credentials: IamCredentials): Promise { this.observability.logging.log('Storing STS Credential.') - await this.next.setStsCredential(name, credentials) + await this.next.setStsCredential(iamCredentialId, credentials) } } diff --git a/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts b/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts index 5336ce55e2..321411dcfa 100644 --- a/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts +++ b/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts @@ -1,46 +1,29 @@ import { StsCache } from './cache/stsCache' import { Observability } from '@aws/lsp-core' -import { - IamCredentials, - StsCredentialChangedKind, - StsCredentialChangedParams, -} from '@aws/language-server-runtimes/protocol' - -// Modified to match SSO token refresh behavior -const refreshWindowMillis = 5 * 60 * 1000 // 5 minutes (matching SSO) -const retryCooldownWindowMillis = 30000 // 30 seconds (matching SSO) -const bufferedRefreshWindowMillis = refreshWindowMillis * 0.95 // 4.75 minutes -const bufferedRetryCooldownWindowMillis = retryCooldownWindowMillis * 1.05 // 31.5 seconds -const maxRefreshJitterMillis = 10000 // 10 seconds (matching SSO) -const maxRetryCooldownJitterMillis = 3000 // 3 seconds (matching SSO) - -export type RaiseStsChanged = (params: StsCredentialChangedParams) => void +import { AutoRefresher } from '../language-server/autoRefresher' +import { IamCredentials, StsCredentialChangedKind } from '@aws/language-server-runtimes/protocol' +import { SendStsCredentialChanged } from '../iam/utils' interface StsCredentialDetail { lastRefreshMillis: number } -export class StsAutoRefresher implements Disposable { - private readonly timeouts: Record = {} +export class StsAutoRefresher extends AutoRefresher { private readonly stsCredentialDetails: Record = {} constructor( private readonly stsCache: StsCache, - private readonly raiseStsCredentialChanged: RaiseStsChanged, - private readonly observability: Observability - ) {} - - [Symbol.dispose](): void { - for (const stsSessionName of Object.keys(this.timeouts)) { - this.unwatch(stsSessionName) - } + private readonly raiseStsCredentialChanged: SendStsCredentialChanged, + observability: Observability + ) { + super(observability) } - async watch(name: string, refreshCallback: () => Promise): Promise { + async watch(iamCredentialId: string, refreshCallback: () => Promise): Promise { try { - this.unwatch(name) + this.unwatch(iamCredentialId) - const credential = await this.stsCache.getStsCredential(name).catch(_ => undefined) + const credential = await this.stsCache.getStsCredential(iamCredentialId).catch(_ => undefined) if (!credential?.expiration) { this.observability.logging.log( @@ -49,57 +32,39 @@ export class StsAutoRefresher implements Disposable { return } - const nowMillis = Date.now() - const expirationMillis = new Date(credential.expiration).getTime() - - // Get or create StsCredentialDetail (matching SSO pattern) + // Get or create StsCredentialDetail const stsCredentialDetail = - this.stsCredentialDetails[name] ?? (this.stsCredentialDetails[name] = { lastRefreshMillis: 0 }) - - let delayMs: number - - if (nowMillis < expirationMillis - refreshWindowMillis) { - // Before refresh window, schedule to run in refresh window with jitter - delayMs = expirationMillis - bufferedRefreshWindowMillis - nowMillis - delayMs += Math.random() * maxRefreshJitterMillis - } else if (expirationMillis - refreshWindowMillis < nowMillis && nowMillis < expirationMillis) { - // In refresh window - check if we're still in retry cooldown - const retryAfterMillis = stsCredentialDetail.lastRefreshMillis + retryCooldownWindowMillis - if (nowMillis < retryAfterMillis) { - this.observability.logging.log('STS credentials in retry cooldown window. Scheduling next retry.') - delayMs = retryAfterMillis - nowMillis - } else { - // Ready to refresh - use buffered retry cooldown with jitter - delayMs = bufferedRetryCooldownWindowMillis - delayMs += Math.random() * maxRetryCooldownJitterMillis - } - } else { - // Expired - this.observability.logging.log('STS credentials have expired and will not be auto-refreshed.') - return + this.stsCredentialDetails[iamCredentialId] ?? + (this.stsCredentialDetails[iamCredentialId] = { lastRefreshMillis: 0 }) + + const delayMillis = this.getDelay(credential.expiration.toISOString()) + if (delayMillis >= 0) { + this.observability.logging.info(`Auto-refreshing STS credentials in ${delayMillis} milliseconds.`) + this.timeouts[iamCredentialId] = setTimeout(async () => { + try { + // Update last refresh attempt time (matching SSO pattern) + stsCredentialDetail.lastRefreshMillis = Date.now() + + // Passing refresh function into here is easier than refreshing from STS cache + const newCredentials = await refreshCallback() + this.observability.logging.log(`Generated new STS credentials`) + await this.stsCache.setStsCredential(iamCredentialId, newCredentials) + + // Continue watching with the new credentials (allows multiple refreshes) + this.watch(iamCredentialId, refreshCallback) + + this.raiseStsCredentialChanged({ + kind: StsCredentialChangedKind.Refreshed, + stsCredentialId: iamCredentialId, + }) + } catch (error) { + this.observability.logging.log(`Failed to refresh STS credentials: ${error}`) + + // On error, continue watching to retry later (matching SSO pattern) + this.watch(iamCredentialId, refreshCallback) + } + }, delayMillis) } - - this.observability.logging.info(`Auto-refreshing STS credentials in ${delayMs} milliseconds.`) - this.timeouts[name] = setTimeout(async () => { - try { - // Update last refresh attempt time (matching SSO pattern) - stsCredentialDetail.lastRefreshMillis = Date.now() - - const newCredentials = await refreshCallback() - this.observability.logging.log(`Generated new STS credentials`) - await this.stsCache.setStsCredential(name, newCredentials) - - // Continue watching with the new credentials (allows multiple refreshes) - this.watch(name, refreshCallback) - - this.raiseStsCredentialChanged({ kind: StsCredentialChangedKind.Refreshed, stsCredentialId: name }) - } catch (error) { - this.observability.logging.log(`Failed to refresh STS credentials: ${error}`) - - // On error, continue watching to retry later (matching SSO pattern) - this.watch(name, refreshCallback) - } - }, delayMs) } catch (e) { this.observability.logging.log(`Error setting up STS auto-refresh: ${e}`) throw e From 2920ee9e713eca6d838e3492ecebaa0a07d95c2b Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Wed, 23 Jul 2025 14:55:52 -0400 Subject: [PATCH 33/37] refactor: move recursion count from IAM provider to IAM flow params --- .../src/iam/iamProvider.test.ts | 1 + .../aws-lsp-identity/src/iam/iamProvider.ts | 66 +++++++++---------- server/aws-lsp-identity/src/iam/utils.ts | 1 + .../src/language-server/identityService.ts | 1 + 4 files changed, 34 insertions(+), 35 deletions(-) diff --git a/server/aws-lsp-identity/src/iam/iamProvider.test.ts b/server/aws-lsp-identity/src/iam/iamProvider.test.ts index b6237905c7..c2958123b6 100644 --- a/server/aws-lsp-identity/src/iam/iamProvider.test.ts +++ b/server/aws-lsp-identity/src/iam/iamProvider.test.ts @@ -116,6 +116,7 @@ describe('IamProvider', () => { defaultParams = { profile: defaultProfile, callStsOnInvalidIamCredential: true, + recursionCount: 0, profileStore: profileStore, stsCache: stsCache, stsAutoRefresher: stsAutoRefresher, diff --git a/server/aws-lsp-identity/src/iam/iamProvider.ts b/server/aws-lsp-identity/src/iam/iamProvider.ts index f90357b88e..10bffc3aec 100644 --- a/server/aws-lsp-identity/src/iam/iamProvider.ts +++ b/server/aws-lsp-identity/src/iam/iamProvider.ts @@ -15,42 +15,36 @@ const mfaTimeout = 2 * 60 * 1000 // 2 minutes export class IamProvider { readonly defaultRegion = 'us-east-1' - private sourceProfileRecursionCount = 0 async getCredential(params: IamFlowParams): Promise { - try { - let id: IamCredentialId = '' - let credentials: IamCredentials + let id: IamCredentialId = '' + let credentials: IamCredentials - // Get the credentials directly from the profile - if (params.profile.kinds.includes(ProfileKind.IamCredentialsProfile)) { - credentials = { - accessKeyId: params.profile.settings!.aws_access_key_id!, - secretAccessKey: params.profile.settings!.aws_secret_access_key!, - sessionToken: params.profile.settings!.aws_session_token!, - } + // Get the credentials directly from the profile + if (params.profile.kinds.includes(ProfileKind.IamCredentialsProfile)) { + credentials = { + accessKeyId: params.profile.settings!.aws_access_key_id!, + secretAccessKey: params.profile.settings!.aws_secret_access_key!, + sessionToken: params.profile.settings?.aws_session_token, } - // Assume the role matching the found ARN - else if (params.profile.kinds.includes(ProfileKind.IamSourceProfileProfile)) { - const key = JSON.stringify({ - RoleArn: params.profile.settings?.role_arn, - RoleSessionName: params.profile.settings?.role_session_name, - SerialNumber: params.profile.settings?.mfa_serial, - }) - id = createHash('sha1').update(key).digest('hex') - credentials = await this.getAssumedRoleCredential(id, params) - } else { - throw new AwsError( - 'Credentials could not be found for provided profile kind', - AwsErrorCodes.E_INVALID_PROFILE - ) - } - - return { id: id, kinds: params.profile.kinds, credentials: credentials } - } catch (e) { - this.sourceProfileRecursionCount = 0 - throw e } + // Assume the role matching the found ARN + else if (params.profile.kinds.includes(ProfileKind.IamSourceProfileProfile)) { + const key = JSON.stringify({ + RoleArn: params.profile.settings?.role_arn, + RoleSessionName: params.profile.settings?.role_session_name, + SerialNumber: params.profile.settings?.mfa_serial, + }) + id = createHash('sha1').update(key).digest('hex') + credentials = await this.getAssumedRoleCredential(id, params) + } else { + throw new AwsError( + 'Credentials could not be found for provided profile kind', + AwsErrorCodes.E_INVALID_PROFILE + ) + } + + return { id: id, kinds: params.profile.kinds, credentials: credentials } } private async getAssumedRoleCredential(id: IamCredentialId, params: IamFlowParams): Promise { @@ -111,11 +105,13 @@ export class IamProvider { throw new AwsError('Source profile not found.', AwsErrorCodes.E_PROFILE_NOT_FOUND) } // Obtain parent profile credentials if IamRoleSourceProfile chain isn't too long - if (this.sourceProfileRecursionCount <= sourceProfileRecursionMax) { - this.sourceProfileRecursionCount += 1 - const response = await this.getCredential({ ...params, profile: sourceProfile }) + if (params.recursionCount <= sourceProfileRecursionMax) { + const response = await this.getCredential({ + ...params, + profile: sourceProfile, + recursionCount: params.recursionCount + 1, + }) parentCredentials = response.credentials - this.sourceProfileRecursionCount = 0 } else { throw new AwsError('Source profile chain exceeded max length.', AwsErrorCodes.E_INVALID_PROFILE) } diff --git a/server/aws-lsp-identity/src/iam/utils.ts b/server/aws-lsp-identity/src/iam/utils.ts index 4ae0af07ba..5e5ddc02e1 100644 --- a/server/aws-lsp-identity/src/iam/utils.ts +++ b/server/aws-lsp-identity/src/iam/utils.ts @@ -92,6 +92,7 @@ export type IamHandlers = { export type IamFlowParams = { profile: Profile callStsOnInvalidIamCredential: boolean + recursionCount: number profileStore: ProfileStore stsCache: StsCache stsAutoRefresher: StsAutoRefresher diff --git a/server/aws-lsp-identity/src/language-server/identityService.ts b/server/aws-lsp-identity/src/language-server/identityService.ts index 0f8e9fa7fb..7f75df1645 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.ts @@ -176,6 +176,7 @@ export class IdentityService { const flowOpts: IamFlowParams = { profile: profile, callStsOnInvalidIamCredential: options.callStsOnInvalidIamCredential, + recursionCount: 0, profileStore: this.profileStore, stsCache: this.stsCache, stsAutoRefresher: this.stsAutoRefresher, From a8c23cdfe51199d633dacfc0eafef565de0be9ad Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Fri, 25 Jul 2025 09:49:11 -0400 Subject: [PATCH 34/37] fix: revert unnecessary changes --- .../src/credentials/credentialsProvider.ts | 1 - package-lock.json | 1613 +++-------------- .../profiles/profileService.test.ts | 48 +- .../profiles/profileService.ts | 45 +- .../profiles/sharedConfigProfileStore.test.ts | 24 +- 5 files changed, 335 insertions(+), 1396 deletions(-) diff --git a/core/aws-lsp-core/src/credentials/credentialsProvider.ts b/core/aws-lsp-core/src/credentials/credentialsProvider.ts index bea8a5cfcd..a56be8e0ad 100644 --- a/core/aws-lsp-core/src/credentials/credentialsProvider.ts +++ b/core/aws-lsp-core/src/credentials/credentialsProvider.ts @@ -4,7 +4,6 @@ export interface IamCredentials { accessKeyId: string secretAccessKey: string sessionToken?: string - expiration?: Date } export interface BearerToken { diff --git a/package-lock.json b/package-lock.json index 5b2b09f113..36b3816e8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2351,31 +2351,44 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-iam/-/client-iam-3.840.0.tgz", - "integrity": "sha512-+HWqpTwXQYhFzgwfjGFHfo+a0mRQwYq29BEYlgfcydo8UOApc1oxsVmEmnYh2nbukaefUkOaMDb1xORybsE6Lw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.839.0.tgz", + "integrity": "sha512-7zDInY+qltKxeG+9d/97nbs+FWINcAi5bChBrleUQkuQ/dA9pSP1URo/6JlVzD2Ejvksm+hVK6z3VUWZaIAVOw==", "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/credential-provider-node": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/credential-provider-node": "3.839.0", + "@aws-sdk/middleware-bucket-endpoint": "3.830.0", + "@aws-sdk/middleware-expect-continue": "3.821.0", + "@aws-sdk/middleware-flexible-checksums": "3.839.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-location-constraint": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-sdk-s3": "3.839.0", + "@aws-sdk/middleware-ssec": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/signature-v4-multi-region": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.839.0", + "@aws-sdk/xml-builder": "3.821.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", + "@smithy/eventstream-serde-browser": "^4.0.4", + "@smithy/eventstream-serde-config-resolver": "^4.1.2", + "@smithy/eventstream-serde-node": "^4.0.4", "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-blob-browser": "^4.0.4", "@smithy/hash-node": "^4.0.4", + "@smithy/hash-stream-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", + "@smithy/md5-js": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.13", "@smithy/middleware-retry": "^4.1.14", @@ -2395,33 +2408,34 @@ "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", + "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.6", - "tslib": "^2.6.2" + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/client-sso": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.840.0.tgz", - "integrity": "sha512-3Zp+FWN2hhmKdpS0Ragi5V2ZPsZNScE3jlbgoJjzjI/roHZqO+e3/+XFN4TlM0DsPKYJNp+1TAjmhxN6rOnfYA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.839.0.tgz", + "integrity": "sha512-AZABysUhbfcwXVlMo97/vwHgsfJNF81wypCAowpqAJkSjP2KrqsqHpb71/RoR2w8JGmEnBBXRD4wIxDhnmifWg==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.839.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", "@smithy/fetch-http-handler": "^5.0.4", @@ -2453,14 +2467,12 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/core": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.840.0.tgz", - "integrity": "sha512-x3Zgb39tF1h2XpU+yA4OAAQlW6LVEfXNlSedSYJ7HGKXqA/E9h3rWQVpYfhXXVVsLdYXdNw5KBUkoAoruoZSZA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.839.0.tgz", + "integrity": "sha512-KdwL5RaK7eUIlOpdOoZ5u+2t4X1rdX/MTZgz3IV/aBzjVUoGsp+uUnbyqXomLQSUitPHp72EE/NHDsvWW/IHvQ==", "dependencies": { - "@aws-sdk/types": "3.840.0", + "@aws-sdk/types": "3.821.0", "@aws-sdk/xml-builder": "3.821.0", "@smithy/core": "^3.6.0", "@smithy/node-config-provider": "^4.1.3", @@ -2480,15 +2492,13 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.840.0.tgz", - "integrity": "sha512-EzF6VcJK7XvQ/G15AVEfJzN2mNXU8fcVpXo4bRyr1S6t2q5zx6UPH/XjDbn18xyUmOq01t+r8gG+TmHEVo18fA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.839.0.tgz", + "integrity": "sha512-cWTadewPPz1OvObZJB+olrgh8VwcgIVcT293ZUT9V0CMF0UU7QaPwJP7uNXcNxltTh+sk1yhjH4UlcnJigZZbA==", "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2497,15 +2507,13 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.840.0.tgz", - "integrity": "sha512-wbnUiPGLVea6mXbUh04fu+VJmGkQvmToPeTYdHE8eRZq3NRDi3t3WltT+jArLBKD/4NppRpMjf2ju4coMCz91g==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.839.0.tgz", + "integrity": "sha512-fv0BZwrDhWDju4D1MCLT4I2aPjr0dVQ6P+MpqvcGNOA41Oa9UdRhYTV5iuy5NLXzIzoCmnS+XfSq5Kbsf6//xw==", "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/node-http-handler": "^4.0.6", "@smithy/property-provider": "^4.0.4", @@ -2519,21 +2527,19 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.840.0.tgz", - "integrity": "sha512-7F290BsWydShHb+7InXd+IjJc3mlEIm9I0R57F/Pjl1xZB69MdkhVGCnuETWoBt4g53ktJd6NEjzm/iAhFXFmw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.839.0.tgz", + "integrity": "sha512-GHm0hF4CiDxIDR7TauMaA6iI55uuSqRxMBcqTAHaTPm6+h1A+MS+ysQMxZ+Jvwtoy8WmfTIGrJVxSCw0sK2hvA==", "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/credential-provider-env": "3.840.0", - "@aws-sdk/credential-provider-http": "3.840.0", - "@aws-sdk/credential-provider-process": "3.840.0", - "@aws-sdk/credential-provider-sso": "3.840.0", - "@aws-sdk/credential-provider-web-identity": "3.840.0", - "@aws-sdk/nested-clients": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/credential-provider-env": "3.839.0", + "@aws-sdk/credential-provider-http": "3.839.0", + "@aws-sdk/credential-provider-process": "3.839.0", + "@aws-sdk/credential-provider-sso": "3.839.0", + "@aws-sdk/credential-provider-web-identity": "3.839.0", + "@aws-sdk/nested-clients": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -2544,20 +2550,18 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.840.0.tgz", - "integrity": "sha512-KufP8JnxA31wxklLm63evUPSFApGcH8X86z3mv9SRbpCm5ycgWIGVCTXpTOdgq6rPZrwT9pftzv2/b4mV/9clg==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.839.0.tgz", + "integrity": "sha512-7bR+U2h+ft0V8chyeu9Bh/pvau4ZkQMeRt5f0dAULoepZQ77QQVRP4H04yJPTg9DCtqbVULQ3uf5YOp1/08vQw==", "dependencies": { - "@aws-sdk/credential-provider-env": "3.840.0", - "@aws-sdk/credential-provider-http": "3.840.0", - "@aws-sdk/credential-provider-ini": "3.840.0", - "@aws-sdk/credential-provider-process": "3.840.0", - "@aws-sdk/credential-provider-sso": "3.840.0", - "@aws-sdk/credential-provider-web-identity": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/credential-provider-env": "3.839.0", + "@aws-sdk/credential-provider-http": "3.839.0", + "@aws-sdk/credential-provider-ini": "3.839.0", + "@aws-sdk/credential-provider-process": "3.839.0", + "@aws-sdk/credential-provider-sso": "3.839.0", + "@aws-sdk/credential-provider-web-identity": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -2568,15 +2572,13 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.840.0.tgz", - "integrity": "sha512-HkDQWHy8tCI4A0Ps2NVtuVYMv9cB4y/IuD/TdOsqeRIAT12h8jDb98BwQPNLAImAOwOWzZJ8Cu0xtSpX7CQhMw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.839.0.tgz", + "integrity": "sha512-qShpekjociUZ+isyQNa0P7jo+0q3N2+0eJDg8SGyP6K6hHTcGfiqxTDps+IKl6NreCPhZCBzyI9mWkP0xSDR6g==", "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", @@ -2586,17 +2588,15 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.840.0.tgz", - "integrity": "sha512-2qgdtdd6R0Z1y0KL8gzzwFUGmhBHSUx4zy85L2XV1CXhpRNwV71SVWJqLDVV5RVWVf9mg50Pm3AWrUC0xb0pcA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.839.0.tgz", + "integrity": "sha512-w10zBLHhU8SBQcdrSPMI02haLoRGZg+gP7mH/Er8VhIXfHefbr7o4NirmB0hwdw/YAH8MLlC9jj7c2SJlsNhYA==", "dependencies": { - "@aws-sdk/client-sso": "3.840.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/token-providers": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/client-sso": "3.839.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/token-providers": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", @@ -2606,16 +2606,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.840.0.tgz", - "integrity": "sha512-dpEeVXG8uNZSmVXReE4WP0lwoioX2gstk4RnUgrdUE3YaPq8A+hJiVAyc3h+cjDeIqfbsQbZm9qFetKC2LF9dQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.839.0.tgz", + "integrity": "sha512-EvqTc7J1kgmiuxknpCp1S60hyMQvmKxsI5uXzQtcogl/N55rxiXEqnCLI5q6p33q91PJegrcMCM5Q17Afhm5qA==", "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/nested-clients": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/nested-clients": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2624,14 +2622,12 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", - "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", + "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", "dependencies": { - "@aws-sdk/types": "3.840.0", + "@aws-sdk/types": "3.821.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2640,14 +2636,12 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-logger": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", - "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", + "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", "dependencies": { - "@aws-sdk/types": "3.840.0", + "@aws-sdk/types": "3.821.0", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -2655,14 +2649,12 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", - "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", + "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", "dependencies": { - "@aws-sdk/types": "3.840.0", + "@aws-sdk/types": "3.821.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2671,16 +2663,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.840.0.tgz", - "integrity": "sha512-hiiMf7BP5ZkAFAvWRcK67Mw/g55ar7OCrvrynC92hunx/xhMkrgSLM0EXIZ1oTn3uql9kH/qqGF0nqsK6K555A==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.839.0.tgz", + "integrity": "sha512-2u74uRM1JWq6Sf7+3YpjejPM9YkomGt4kWhrmooIBEq1k5r2GTbkH7pNCxBQwBueXM21jAGVDxxeClpTx+5hig==", "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", "@smithy/core": "^3.6.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", @@ -2690,25 +2680,23 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/nested-clients": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.840.0.tgz", - "integrity": "sha512-LXYYo9+n4hRqnRSIMXLBb+BLz+cEmjMtTudwK1BF6Bn2RfdDv29KuyeDRrPCS3TwKl7ZKmXUmE9n5UuHAPfBpA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/nested-clients": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.839.0.tgz", + "integrity": "sha512-Glic0pg2THYP3aRhJORwJJBe1JLtJoEdWV/MFZNyzCklfMwEzpWtZAyxy+tQyFmMeW50uBAnh2R0jhMMcf257w==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.839.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", "@smithy/fetch-http-handler": "^5.0.4", @@ -2740,14 +2728,12 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", - "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", + "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", "dependencies": { - "@aws-sdk/types": "3.840.0", + "@aws-sdk/types": "3.821.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", @@ -2758,77 +2744,24 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/token-providers": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.840.0.tgz", - "integrity": "sha512-6BuTOLTXvmgwjK7ve7aTg9JaWFdM5UoMolLVPMyh3wTv9Ufalh8oklxYHUBIxsKkBGO2WiHXytveuxH6tAgTYg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/nested-clients": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/types": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", - "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-endpoints": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.840.0.tgz", - "integrity": "sha512-eqE9ROdg/Kk0rj3poutyRCFauPDXIf/WSvCqFiRDDVi6QOnCv/M0g2XW8/jSvkJlOyaXkNCptapIp6BeeFFGYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "@smithy/util-endpoints": "^3.0.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", - "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", + "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", "dependencies": { - "@aws-sdk/types": "3.840.0", + "@aws-sdk/types": "3.821.0", "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.840.0.tgz", - "integrity": "sha512-Fy5JUEDQU1tPm2Yw/YqRYYc27W5+QD/J4mYvQvdWjUGZLB5q3eLFMGD35Uc28ZFoGMufPr4OCxK/bRfWROBRHQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.839.0.tgz", + "integrity": "sha512-MuunkIG1bJVMtTH7MbjXOrhHleU5wjHz5eCAUc6vj7M9rwol71nqjj9b8RLnkO5gsJcKc29Qk8iV6xQuzKWNMw==", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2845,12 +2778,10 @@ } } }, - "node_modules/@aws-sdk/client-iam/node_modules/@smithy/abort-controller": { + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/abort-controller": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2859,12 +2790,10 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@smithy/node-http-handler": { + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/node-http-handler": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", - "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^4.0.4", "@smithy/protocol-http": "^5.1.2", @@ -2876,12 +2805,10 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/types": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "dev": true, - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -2889,44 +2816,89 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3": { + "node_modules/@aws-sdk/client-s3/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.731.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.731.0.tgz", + "integrity": "sha512-O4C/UYGgqMsBg21MMApFdgyh8BX568hQhbdoNFmRVTBoSnCZ3w+H4a1wBPX4Gyl0NX+ab6Xxo9rId8HiyPXJ0A==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.731.0", + "@aws-sdk/middleware-host-header": "3.731.0", + "@aws-sdk/middleware-logger": "3.731.0", + "@aws-sdk/middleware-recursion-detection": "3.731.0", + "@aws-sdk/middleware-user-agent": "3.731.0", + "@aws-sdk/region-config-resolver": "3.731.0", + "@aws-sdk/types": "3.731.0", + "@aws-sdk/util-endpoints": "3.731.0", + "@aws-sdk/util-user-agent-browser": "3.731.0", + "@aws-sdk/util-user-agent-node": "3.731.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.839.0.tgz", - "integrity": "sha512-7zDInY+qltKxeG+9d/97nbs+FWINcAi5bChBrleUQkuQ/dA9pSP1URo/6JlVzD2Ejvksm+hVK6z3VUWZaIAVOw==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.839.0.tgz", + "integrity": "sha512-7QnMApYfQBT441YkxObxt1hZ8TdqZH7h0NdYsvbLdEqGROXBDDT+Wq7ZVfsnKjuVUGQ/t75bIqFn7M8cdyESfA==", "dependencies": { - "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.839.0", "@aws-sdk/credential-provider-node": "3.839.0", - "@aws-sdk/middleware-bucket-endpoint": "3.830.0", - "@aws-sdk/middleware-expect-continue": "3.821.0", - "@aws-sdk/middleware-flexible-checksums": "3.839.0", "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-location-constraint": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-sdk-s3": "3.839.0", - "@aws-sdk/middleware-ssec": "3.821.0", "@aws-sdk/middleware-user-agent": "3.839.0", "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/signature-v4-multi-region": "3.839.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", "@aws-sdk/util-user-agent-node": "3.839.0", - "@aws-sdk/xml-builder": "3.821.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", - "@smithy/eventstream-serde-browser": "^4.0.4", - "@smithy/eventstream-serde-config-resolver": "^4.1.2", - "@smithy/eventstream-serde-node": "^4.0.4", "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-blob-browser": "^4.0.4", "@smithy/hash-node": "^4.0.4", - "@smithy/hash-stream-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", - "@smithy/md5-js": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.13", "@smithy/middleware-retry": "^4.1.14", @@ -2946,18 +2918,14 @@ "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", - "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", - "@smithy/util-waiter": "^4.0.6", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sso": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.839.0.tgz", "integrity": "sha512-AZABysUhbfcwXVlMo97/vwHgsfJNF81wypCAowpqAJkSjP2KrqsqHpb71/RoR2w8JGmEnBBXRD4wIxDhnmifWg==", @@ -3005,7 +2973,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/core": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.839.0.tgz", "integrity": "sha512-KdwL5RaK7eUIlOpdOoZ5u+2t4X1rdX/MTZgz3IV/aBzjVUoGsp+uUnbyqXomLQSUitPHp72EE/NHDsvWW/IHvQ==", @@ -3030,7 +2998,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-env": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.839.0.tgz", "integrity": "sha512-cWTadewPPz1OvObZJB+olrgh8VwcgIVcT293ZUT9V0CMF0UU7QaPwJP7uNXcNxltTh+sk1yhjH4UlcnJigZZbA==", @@ -3045,7 +3013,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-http": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.839.0.tgz", "integrity": "sha512-fv0BZwrDhWDju4D1MCLT4I2aPjr0dVQ6P+MpqvcGNOA41Oa9UdRhYTV5iuy5NLXzIzoCmnS+XfSq5Kbsf6//xw==", @@ -3065,7 +3033,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.839.0.tgz", "integrity": "sha512-GHm0hF4CiDxIDR7TauMaA6iI55uuSqRxMBcqTAHaTPm6+h1A+MS+ysQMxZ+Jvwtoy8WmfTIGrJVxSCw0sK2hvA==", @@ -3088,7 +3056,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-node": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.839.0.tgz", "integrity": "sha512-7bR+U2h+ft0V8chyeu9Bh/pvau4ZkQMeRt5f0dAULoepZQ77QQVRP4H04yJPTg9DCtqbVULQ3uf5YOp1/08vQw==", @@ -3110,7 +3078,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-process": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.839.0.tgz", "integrity": "sha512-qShpekjociUZ+isyQNa0P7jo+0q3N2+0eJDg8SGyP6K6hHTcGfiqxTDps+IKl6NreCPhZCBzyI9mWkP0xSDR6g==", @@ -3126,7 +3094,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-sso": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.839.0.tgz", "integrity": "sha512-w10zBLHhU8SBQcdrSPMI02haLoRGZg+gP7mH/Er8VhIXfHefbr7o4NirmB0hwdw/YAH8MLlC9jj7c2SJlsNhYA==", @@ -3144,7 +3112,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.839.0.tgz", "integrity": "sha512-EvqTc7J1kgmiuxknpCp1S60hyMQvmKxsI5uXzQtcogl/N55rxiXEqnCLI5q6p33q91PJegrcMCM5Q17Afhm5qA==", @@ -3160,7 +3128,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-host-header": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", @@ -3174,7 +3142,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-logger": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", @@ -3187,7 +3155,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", @@ -3201,7 +3169,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-user-agent": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.839.0.tgz", "integrity": "sha512-2u74uRM1JWq6Sf7+3YpjejPM9YkomGt4kWhrmooIBEq1k5r2GTbkH7pNCxBQwBueXM21jAGVDxxeClpTx+5hig==", @@ -3218,7 +3186,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/nested-clients": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/nested-clients": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.839.0.tgz", "integrity": "sha512-Glic0pg2THYP3aRhJORwJJBe1JLtJoEdWV/MFZNyzCklfMwEzpWtZAyxy+tQyFmMeW50uBAnh2R0jhMMcf257w==", @@ -3266,7 +3234,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/region-config-resolver": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", @@ -3282,7 +3250,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", @@ -3293,7 +3261,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.839.0.tgz", "integrity": "sha512-MuunkIG1bJVMtTH7MbjXOrhHleU5wjHz5eCAUc6vj7M9rwol71nqjj9b8RLnkO5gsJcKc29Qk8iV6xQuzKWNMw==", @@ -3316,7 +3284,7 @@ } } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/abort-controller": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/abort-controller": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", @@ -3328,7 +3296,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/node-http-handler": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-http-handler": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", @@ -3343,7 +3311,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/types": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", @@ -3354,196 +3322,37 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.731.0.tgz", - "integrity": "sha512-O4C/UYGgqMsBg21MMApFdgyh8BX568hQhbdoNFmRVTBoSnCZ3w+H4a1wBPX4Gyl0NX+ab6Xxo9rId8HiyPXJ0A==", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.731.0.tgz", + "integrity": "sha512-NrdkJg6oOUbXR2r9WvHP408CLyvST8cJfp1/jP9pemtjvjPoh6NukbCtiSFdOOb1eryP02CnqQWItfJC1p2Y/Q==", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.731.0", - "@aws-sdk/middleware-host-header": "3.731.0", - "@aws-sdk/middleware-logger": "3.731.0", - "@aws-sdk/middleware-recursion-detection": "3.731.0", - "@aws-sdk/middleware-user-agent": "3.731.0", - "@aws-sdk/region-config-resolver": "3.731.0", - "@aws-sdk/types": "3.731.0", - "@aws-sdk/util-endpoints": "3.731.0", - "@aws-sdk/util-user-agent-browser": "3.731.0", - "@aws-sdk/util-user-agent-node": "3.731.0", - "@smithy/config-resolver": "^4.0.0", - "@smithy/core": "^3.0.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/hash-node": "^4.0.0", - "@smithy/invalid-dependency": "^4.0.0", - "@smithy/middleware-content-length": "^4.0.0", - "@smithy/middleware-endpoint": "^4.0.0", - "@smithy/middleware-retry": "^4.0.0", - "@smithy/middleware-serde": "^4.0.0", - "@smithy/middleware-stack": "^4.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", "@smithy/types": "^4.0.0", - "@smithy/url-parser": "^4.0.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.0", - "@smithy/util-defaults-mode-node": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "@smithy/util-middleware": "^4.0.0", - "@smithy/util-retry": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.839.0.tgz", - "integrity": "sha512-7QnMApYfQBT441YkxObxt1hZ8TdqZH7h0NdYsvbLdEqGROXBDDT+Wq7ZVfsnKjuVUGQ/t75bIqFn7M8cdyESfA==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.839.0", - "@aws-sdk/credential-provider-node": "3.839.0", - "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-logger": "3.821.0", - "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.839.0", - "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.839.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.6.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.13", - "@smithy/middleware-retry": "^4.1.14", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.21", - "@smithy/util-defaults-mode-node": "^4.0.21", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sso": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.839.0.tgz", - "integrity": "sha512-AZABysUhbfcwXVlMo97/vwHgsfJNF81wypCAowpqAJkSjP2KrqsqHpb71/RoR2w8JGmEnBBXRD4wIxDhnmifWg==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.839.0", - "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-logger": "3.821.0", - "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.839.0", - "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.839.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.6.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.13", - "@smithy/middleware-retry": "^4.1.14", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.21", - "@smithy/util-defaults-mode-node": "^4.0.21", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/core": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.839.0.tgz", - "integrity": "sha512-KdwL5RaK7eUIlOpdOoZ5u+2t4X1rdX/MTZgz3IV/aBzjVUoGsp+uUnbyqXomLQSUitPHp72EE/NHDsvWW/IHvQ==", + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { + "version": "3.731.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.731.0.tgz", + "integrity": "sha512-riztxTAfncFS9yQWcBJffGgOgLoKSa63ph+rxWJxKl6BHAmWEvHICj1qDcVmnWfIcvJ5cClclY75l9qKaUH7rQ==", "dependencies": { - "@aws-sdk/types": "3.821.0", - "@aws-sdk/xml-builder": "3.821.0", - "@smithy/core": "^3.6.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/signature-v4": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-utf8": "^4.0.0", - "fast-xml-parser": "4.4.1", + "@aws-sdk/types": "3.731.0", + "@smithy/types": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.839.0.tgz", - "integrity": "sha512-cWTadewPPz1OvObZJB+olrgh8VwcgIVcT293ZUT9V0CMF0UU7QaPwJP7uNXcNxltTh+sk1yhjH4UlcnJigZZbA==", + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -3551,42 +3360,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.839.0.tgz", - "integrity": "sha512-fv0BZwrDhWDju4D1MCLT4I2aPjr0dVQ6P+MpqvcGNOA41Oa9UdRhYTV5iuy5NLXzIzoCmnS+XfSq5Kbsf6//xw==", + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/property-provider": "^4.0.4", + "@smithy/abort-controller": "^4.0.4", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/util-stream": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.839.0.tgz", - "integrity": "sha512-GHm0hF4CiDxIDR7TauMaA6iI55uuSqRxMBcqTAHaTPm6+h1A+MS+ysQMxZ+Jvwtoy8WmfTIGrJVxSCw0sK2hvA==", - "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/credential-provider-env": "3.839.0", - "@aws-sdk/credential-provider-http": "3.839.0", - "@aws-sdk/credential-provider-process": "3.839.0", - "@aws-sdk/credential-provider-sso": "3.839.0", - "@aws-sdk/credential-provider-web-identity": "3.839.0", - "@aws-sdk/nested-clients": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/querystring-builder": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -3594,866 +3375,10 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.839.0.tgz", - "integrity": "sha512-7bR+U2h+ft0V8chyeu9Bh/pvau4ZkQMeRt5f0dAULoepZQ77QQVRP4H04yJPTg9DCtqbVULQ3uf5YOp1/08vQw==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.839.0", - "@aws-sdk/credential-provider-http": "3.839.0", - "@aws-sdk/credential-provider-ini": "3.839.0", - "@aws-sdk/credential-provider-process": "3.839.0", - "@aws-sdk/credential-provider-sso": "3.839.0", - "@aws-sdk/credential-provider-web-identity": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.839.0.tgz", - "integrity": "sha512-qShpekjociUZ+isyQNa0P7jo+0q3N2+0eJDg8SGyP6K6hHTcGfiqxTDps+IKl6NreCPhZCBzyI9mWkP0xSDR6g==", - "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.839.0.tgz", - "integrity": "sha512-w10zBLHhU8SBQcdrSPMI02haLoRGZg+gP7mH/Er8VhIXfHefbr7o4NirmB0hwdw/YAH8MLlC9jj7c2SJlsNhYA==", - "dependencies": { - "@aws-sdk/client-sso": "3.839.0", - "@aws-sdk/core": "3.839.0", - "@aws-sdk/token-providers": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.839.0.tgz", - "integrity": "sha512-EvqTc7J1kgmiuxknpCp1S60hyMQvmKxsI5uXzQtcogl/N55rxiXEqnCLI5q6p33q91PJegrcMCM5Q17Afhm5qA==", - "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/nested-clients": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", - "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-logger": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", - "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", - "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.839.0.tgz", - "integrity": "sha512-2u74uRM1JWq6Sf7+3YpjejPM9YkomGt4kWhrmooIBEq1k5r2GTbkH7pNCxBQwBueXM21jAGVDxxeClpTx+5hig==", - "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@smithy/core": "^3.6.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/nested-clients": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.839.0.tgz", - "integrity": "sha512-Glic0pg2THYP3aRhJORwJJBe1JLtJoEdWV/MFZNyzCklfMwEzpWtZAyxy+tQyFmMeW50uBAnh2R0jhMMcf257w==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.839.0", - "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-logger": "3.821.0", - "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.839.0", - "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.839.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.6.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.13", - "@smithy/middleware-retry": "^4.1.14", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.21", - "@smithy/util-defaults-mode-node": "^4.0.21", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", - "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", - "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.839.0.tgz", - "integrity": "sha512-MuunkIG1bJVMtTH7MbjXOrhHleU5wjHz5eCAUc6vj7M9rwol71nqjj9b8RLnkO5gsJcKc29Qk8iV6xQuzKWNMw==", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-http-handler": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", - "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", - "dependencies": { - "@smithy/abort-controller": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { - "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.731.0.tgz", - "integrity": "sha512-NrdkJg6oOUbXR2r9WvHP408CLyvST8cJfp1/jP9pemtjvjPoh6NukbCtiSFdOOb1eryP02CnqQWItfJC1p2Y/Q==", - "dependencies": { - "@smithy/types": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { - "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.731.0.tgz", - "integrity": "sha512-riztxTAfncFS9yQWcBJffGgOgLoKSa63ph+rxWJxKl6BHAmWEvHICj1qDcVmnWfIcvJ5cClclY75l9qKaUH7rQ==", - "dependencies": { - "@aws-sdk/types": "3.731.0", - "@smithy/types": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-http-handler": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", - "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", - "dependencies": { - "@smithy/abort-controller": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.840.0.tgz", - "integrity": "sha512-h+mu89Wk81Ne+B624GT/pBM5VjuAZueSeQNixhgtQ1QHi6bZzrpz8+lvMSibKO+kXFyQsTLzkyibbxnhLpWQZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/credential-provider-node": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.840.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.6.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.13", - "@smithy/middleware-retry": "^4.1.14", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.21", - "@smithy/util-defaults-mode-node": "^4.0.21", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/client-sso": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.840.0.tgz", - "integrity": "sha512-3Zp+FWN2hhmKdpS0Ragi5V2ZPsZNScE3jlbgoJjzjI/roHZqO+e3/+XFN4TlM0DsPKYJNp+1TAjmhxN6rOnfYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.840.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.6.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.13", - "@smithy/middleware-retry": "^4.1.14", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.21", - "@smithy/util-defaults-mode-node": "^4.0.21", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/core": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.840.0.tgz", - "integrity": "sha512-x3Zgb39tF1h2XpU+yA4OAAQlW6LVEfXNlSedSYJ7HGKXqA/E9h3rWQVpYfhXXVVsLdYXdNw5KBUkoAoruoZSZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@aws-sdk/xml-builder": "3.821.0", - "@smithy/core": "^3.6.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/signature-v4": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-utf8": "^4.0.0", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.840.0.tgz", - "integrity": "sha512-EzF6VcJK7XvQ/G15AVEfJzN2mNXU8fcVpXo4bRyr1S6t2q5zx6UPH/XjDbn18xyUmOq01t+r8gG+TmHEVo18fA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.840.0.tgz", - "integrity": "sha512-wbnUiPGLVea6mXbUh04fu+VJmGkQvmToPeTYdHE8eRZq3NRDi3t3WltT+jArLBKD/4NppRpMjf2ju4coMCz91g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/util-stream": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.840.0.tgz", - "integrity": "sha512-7F290BsWydShHb+7InXd+IjJc3mlEIm9I0R57F/Pjl1xZB69MdkhVGCnuETWoBt4g53ktJd6NEjzm/iAhFXFmw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/credential-provider-env": "3.840.0", - "@aws-sdk/credential-provider-http": "3.840.0", - "@aws-sdk/credential-provider-process": "3.840.0", - "@aws-sdk/credential-provider-sso": "3.840.0", - "@aws-sdk/credential-provider-web-identity": "3.840.0", - "@aws-sdk/nested-clients": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.840.0.tgz", - "integrity": "sha512-KufP8JnxA31wxklLm63evUPSFApGcH8X86z3mv9SRbpCm5ycgWIGVCTXpTOdgq6rPZrwT9pftzv2/b4mV/9clg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.840.0", - "@aws-sdk/credential-provider-http": "3.840.0", - "@aws-sdk/credential-provider-ini": "3.840.0", - "@aws-sdk/credential-provider-process": "3.840.0", - "@aws-sdk/credential-provider-sso": "3.840.0", - "@aws-sdk/credential-provider-web-identity": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.840.0.tgz", - "integrity": "sha512-HkDQWHy8tCI4A0Ps2NVtuVYMv9cB4y/IuD/TdOsqeRIAT12h8jDb98BwQPNLAImAOwOWzZJ8Cu0xtSpX7CQhMw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.840.0.tgz", - "integrity": "sha512-2qgdtdd6R0Z1y0KL8gzzwFUGmhBHSUx4zy85L2XV1CXhpRNwV71SVWJqLDVV5RVWVf9mg50Pm3AWrUC0xb0pcA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.840.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/token-providers": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.840.0.tgz", - "integrity": "sha512-dpEeVXG8uNZSmVXReE4WP0lwoioX2gstk4RnUgrdUE3YaPq8A+hJiVAyc3h+cjDeIqfbsQbZm9qFetKC2LF9dQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/nested-clients": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", - "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-logger": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", - "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", - "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.840.0.tgz", - "integrity": "sha512-hiiMf7BP5ZkAFAvWRcK67Mw/g55ar7OCrvrynC92hunx/xhMkrgSLM0EXIZ1oTn3uql9kH/qqGF0nqsK6K555A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@smithy/core": "^3.6.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/nested-clients": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.840.0.tgz", - "integrity": "sha512-LXYYo9+n4hRqnRSIMXLBb+BLz+cEmjMtTudwK1BF6Bn2RfdDv29KuyeDRrPCS3TwKl7ZKmXUmE9n5UuHAPfBpA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.840.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.6.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.13", - "@smithy/middleware-retry": "^4.1.14", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.21", - "@smithy/util-defaults-mode-node": "^4.0.21", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", - "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/token-providers": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.840.0.tgz", - "integrity": "sha512-6BuTOLTXvmgwjK7ve7aTg9JaWFdM5UoMolLVPMyh3wTv9Ufalh8oklxYHUBIxsKkBGO2WiHXytveuxH6tAgTYg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/nested-clients": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/types": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", - "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-endpoints": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.840.0.tgz", - "integrity": "sha512-eqE9ROdg/Kk0rj3poutyRCFauPDXIf/WSvCqFiRDDVi6QOnCv/M0g2XW8/jSvkJlOyaXkNCptapIp6BeeFFGYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "@smithy/util-endpoints": "^3.0.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", - "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.840.0.tgz", - "integrity": "sha512-Fy5JUEDQU1tPm2Yw/YqRYYc27W5+QD/J4mYvQvdWjUGZLB5q3eLFMGD35Uc28ZFoGMufPr4OCxK/bRfWROBRHQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/node-http-handler": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", - "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/types": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "dev": true, - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts index adc77dc1be..216f3010af 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.test.ts @@ -54,7 +54,7 @@ describe('ProfileService', async () => { } profile4 = { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'profile4', settings: { aws_access_key_id: 'access-key', @@ -259,9 +259,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Sso-session name required on profile.') }) - it('updateProfile throws on missing access key for IAM user profile', async () => { + it('updateProfile throws on missing access key for IamCredentialsProfile', async () => { const profile = { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'profile-name', settings: { aws_secret_access_key: 'secret-key', @@ -271,9 +271,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Access key required on profile.') }) - it('updateProfile throws on missing secret key for IAM user profile', async () => { + it('updateProfile throws on missing secret key for IamCredentialsProfile', async () => { const profile = { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'profile-name', settings: { aws_access_key_id: 'access-key', @@ -283,9 +283,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Secret key required on profile.') }) - it('updateProfile throws on missing role ARN for role source profile', async () => { + it('updateProfile throws on missing role ARN for IamSourceProfileProfile', async () => { const profile = { - kinds: [ProfileKind.RoleSourceProfile], + kinds: [ProfileKind.IamSourceProfileProfile], name: 'profile-name', settings: { source_profile: 'source', @@ -295,9 +295,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Role ARN required on profile.') }) - it('updateProfile throws on missing source profile for role source profile', async () => { + it('updateProfile throws on missing source profile for IamSourceProfileProfile', async () => { const profile = { - kinds: [ProfileKind.RoleSourceProfile], + kinds: [ProfileKind.IamSourceProfileProfile], name: 'profile-name', settings: { role_arn: 'role-arn', @@ -307,9 +307,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Source profile required on profile.') }) - it('updateProfile throws on missing role ARN for role instance profile', async () => { + it('updateProfile throws on missing role ARN for IamCredentialSourceProfile', async () => { const profile = { - kinds: [ProfileKind.RoleInstanceProfile], + kinds: [ProfileKind.IamCredentialSourceProfile], name: 'profile-name', settings: { credential_source: 'Ec2InstanceMetadata', @@ -320,9 +320,9 @@ describe('ProfileService', async () => { await expectAwsError(sut, { profile }, AwsErrorCodes.E_INVALID_PROFILE, 'Role ARN required on profile.') }) - it('updateProfile throws on missing credential source for role instance profile', async () => { + it('updateProfile throws on missing credential source for IamCredentialSourceProfile', async () => { const profile = { - kinds: [ProfileKind.RoleInstanceProfile], + kinds: [ProfileKind.IamCredentialSourceProfile], name: 'profile-name', settings: { role_arn: 'role-arn', @@ -340,7 +340,7 @@ describe('ProfileService', async () => { it('updateProfile throws on missing credential process for process profile', async () => { const profile = { - kinds: [ProfileKind.ProcessProfile], + kinds: [ProfileKind.IamCredentialProcessProfile], name: 'profile-name', settings: {}, } @@ -573,7 +573,7 @@ describe('profileService.DuckTypers', () => { } }) - it('profileDuckTypers.IamUserProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.IamCredentialsProfile.eval returns true on valid profiles', () => { const profiles = [ { aws_access_key_id: 'access-key', @@ -587,12 +587,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamUserProfile.eval(profile) + const actual = profileDuckTypers.IamCredentialsProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.IamUserProfile.eval returns false on invalid profiles', () => { + it('profileDuckTypers.IamCredentialsProfile.eval returns false on invalid profiles', () => { const profiles = [ { sso_session: 'my-sso-session', @@ -604,12 +604,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.IamUserProfile.eval(profile as object) + const actual = profileDuckTypers.IamCredentialsProfile.eval(profile as object) expect(actual).to.be.false } }) - it('profileDuckTypers.RoleSourceProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.IamSourceProfileProfile.eval returns true on valid profiles', () => { const profiles = [ { role_arn: 'role-arn', @@ -624,12 +624,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.RoleSourceProfile.eval(profile) + const actual = profileDuckTypers.IamSourceProfileProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.RoleInstanceProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.IamCredentialSourceProfile.eval returns true on valid profiles', () => { const profiles = [ { role_arn: 'role-arn', @@ -645,12 +645,12 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.RoleInstanceProfile.eval(profile) + const actual = profileDuckTypers.IamCredentialSourceProfile.eval(profile) expect(actual).to.be.true } }) - it('profileDuckTypers.ProcessProfile.eval returns true on valid profiles', () => { + it('profileDuckTypers.IamCredentialProcessProfile.eval returns true on valid profiles', () => { const profiles = [ { credential_process: 'credential-process', @@ -664,7 +664,7 @@ describe('profileService.DuckTypers', () => { ] for (const profile of profiles) { - const actual = profileDuckTypers.ProcessProfile.eval(profile) + const actual = profileDuckTypers.IamCredentialProcessProfile.eval(profile) expect(actual).to.be.true } }) diff --git a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts index afa855de8a..a37e27e30e 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/profileService.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/profileService.ts @@ -40,6 +40,8 @@ export const ProfileFields = { source_profile: 'source_profile', mfa_serial: 'mfa_serial', external_id: 'external_id', + credential_cache: 'credential_cache', + credential_cache_location: 'credential_cache_location', } as const export const SsoSessionFields = { @@ -55,26 +57,39 @@ export const profileTypes = { optional: [ProfileFields.region], disallowed: [ProfileFields.sso_account_id, ProfileFields.sso_role_name], }, - IamUserProfile: { - kind: ProfileKind.IamUserProfile, + IamCredentialsProfile: { + kind: ProfileKind.IamCredentialsProfile, required: [ProfileFields.aws_access_key_id, ProfileFields.aws_secret_access_key], optional: [ProfileFields.aws_session_token], disallowed: [], }, - IamRoleSourceProfile: { - kind: ProfileKind.IamRoleSourceProfile, + IamSourceProfileProfile: { + kind: ProfileKind.IamSourceProfileProfile, required: [ProfileFields.role_arn, ProfileFields.source_profile], - optional: [ProfileFields.external_id, ProfileFields.role_session_name, ProfileFields.mfa_serial], + optional: [ + ProfileFields.external_id, + ProfileFields.role_session_name, + ProfileFields.region, + ProfileFields.mfa_serial, + ProfileFields.credential_cache, + ProfileFields.credential_cache_location, + ], disallowed: [ProfileFields.credential_source], }, - IamRoleInstanceProfile: { - kind: ProfileKind.IamRoleInstanceProfile, + IamCredentialSourceProfile: { + kind: ProfileKind.IamCredentialSourceProfile, required: [ProfileFields.role_arn, ProfileFields.credential_source], - optional: [ProfileFields.external_id, ProfileFields.role_session_name, ProfileFields.region], + optional: [ + ProfileFields.external_id, + ProfileFields.role_session_name, + ProfileFields.region, + ProfileFields.credential_cache, + ProfileFields.credential_cache_location, + ], disallowed: [ProfileFields.source_profile], }, - IamProcessProfile: { - kind: ProfileKind.IamProcessProfile, + IamCredentialProcessProfile: { + kind: ProfileKind.IamCredentialProcessProfile, required: [ProfileFields.credential_process], optional: [], disallowed: [], @@ -148,7 +163,6 @@ export class ProfileService { this.throwOnInvalidProfile(!params.profile, 'Profile required.') const profile = params.profile! - // Removing this check for profile deletion this.throwOnInvalidProfile( !profile.kinds.some(kind => Object.values(ProfileKind).includes(kind)), 'Profile must be non-legacy sso-session or iam-credentials type.' @@ -168,6 +182,7 @@ export class ProfileService { throw new AwsError('Cannot create profile.', AwsErrorCodes.E_CANNOT_CREATE_PROFILE) } + // TODO: can this be refactored and simplified using the existing DuckTypers? // Validate SSO profile if (profile.kinds.includes(ProfileKind.SsoTokenProfile)) { this.throwOnInvalidProfile(!profileSettings.sso_session, 'Sso-session name required on profile.') @@ -204,22 +219,22 @@ export class ProfileService { } // Validate IAM profiles - if (profile.kinds.includes(ProfileKind.IamUserProfile)) { + if (profile.kinds.includes(ProfileKind.IamCredentialsProfile)) { this.throwOnInvalidProfile(!profileSettings.aws_access_key_id, 'Access key required on profile.') this.throwOnInvalidProfile(!profileSettings.aws_secret_access_key, 'Secret key required on profile.') } - if (profile.kinds.includes(ProfileKind.RoleInstanceProfile)) { + if (profile.kinds.includes(ProfileKind.IamCredentialSourceProfile)) { this.throwOnInvalidProfile(!profileSettings.role_arn, 'Role ARN required on profile.') this.throwOnInvalidProfile(!profileSettings.credential_source, 'Credential source required on profile.') } - if (profile.kinds.includes(ProfileKind.RoleSourceProfile)) { + if (profile.kinds.includes(ProfileKind.IamSourceProfileProfile)) { this.throwOnInvalidProfile(!profileSettings.role_arn, 'Role ARN required on profile.') this.throwOnInvalidProfile(!profileSettings.source_profile, 'Source profile required on profile.') } - if (profile.kinds.includes(ProfileKind.ProcessProfile)) { + if (profile.kinds.includes(ProfileKind.IamCredentialProcessProfile)) { this.throwOnInvalidProfile(!profileSettings.credential_process, 'Credential process required on profile.') } diff --git a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts index 227003dbd8..fa0e85bb72 100644 --- a/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts +++ b/server/aws-lsp-identity/src/language-server/profiles/sharedConfigProfileStore.test.ts @@ -89,7 +89,7 @@ describe('SharedConfigProfileStore', async () => { expect(actual).to.deep.equal({ profiles: [ { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', @@ -180,7 +180,7 @@ describe('SharedConfigProfileStore', async () => { expect(after).to.deep.equal({ profiles: [ { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', @@ -276,7 +276,7 @@ describe('SharedConfigProfileStore', async () => { expect(after).to.deep.equal({ profiles: [ { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', @@ -316,7 +316,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'iam-user.profile', settings: { aws_access_key_id: 'new-access-key', @@ -325,7 +325,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.RoleSourceProfile], + kinds: [ProfileKind.IamSourceProfileProfile], name: 'role-source.profile', settings: { role_arn: 'new-role-arn', @@ -333,7 +333,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.RoleInstanceProfile], + kinds: [ProfileKind.IamCredentialSourceProfile], name: 'role-instance.profile', settings: { role_arn: 'new-role-arn', @@ -341,7 +341,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.ProcessProfile], + kinds: [ProfileKind.IamCredentialProcessProfile], name: 'process.profile', settings: { credential_process: 'new-credential-process', @@ -392,7 +392,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'default', settings: { aws_access_key_id: 'AAAAAAAA', @@ -400,7 +400,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.IamUserProfile], + kinds: [ProfileKind.IamCredentialsProfile], name: 'iam-user.profile', settings: { aws_access_key_id: 'new-access-key', @@ -409,14 +409,14 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.ProcessProfile], + kinds: [ProfileKind.IamCredentialProcessProfile], name: 'process.profile', settings: { credential_process: 'new-credential-process', }, }, { - kinds: [ProfileKind.RoleInstanceProfile], + kinds: [ProfileKind.IamCredentialSourceProfile], name: 'role-instance.profile', settings: { role_arn: 'new-role-arn', @@ -424,7 +424,7 @@ describe('SharedConfigProfileStore', async () => { }, }, { - kinds: [ProfileKind.RoleSourceProfile], + kinds: [ProfileKind.IamSourceProfileProfile], name: 'role-source.profile', settings: { role_arn: 'new-role-arn', From 2a8afee035612d99f2010337d30e2f100e52f634 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Fri, 25 Jul 2025 10:06:32 -0400 Subject: [PATCH 35/37] fix: request MFA serial number from client and revert minor change --- package-lock.json | 1613 +++-------------- .../src/iam/iamProvider.test.ts | 2 +- .../aws-lsp-identity/src/iam/iamProvider.ts | 19 +- server/aws-lsp-identity/src/iam/utils.ts | 8 +- .../language-server/identityService.test.ts | 2 +- 5 files changed, 286 insertions(+), 1358 deletions(-) diff --git a/package-lock.json b/package-lock.json index 36b3816e8f..003f4f5902 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1813,31 +1813,44 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-iam/-/client-iam-3.840.0.tgz", - "integrity": "sha512-+HWqpTwXQYhFzgwfjGFHfo+a0mRQwYq29BEYlgfcydo8UOApc1oxsVmEmnYh2nbukaefUkOaMDb1xORybsE6Lw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.839.0.tgz", + "integrity": "sha512-7zDInY+qltKxeG+9d/97nbs+FWINcAi5bChBrleUQkuQ/dA9pSP1URo/6JlVzD2Ejvksm+hVK6z3VUWZaIAVOw==", "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/credential-provider-node": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/credential-provider-node": "3.839.0", + "@aws-sdk/middleware-bucket-endpoint": "3.830.0", + "@aws-sdk/middleware-expect-continue": "3.821.0", + "@aws-sdk/middleware-flexible-checksums": "3.839.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-location-constraint": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-sdk-s3": "3.839.0", + "@aws-sdk/middleware-ssec": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/signature-v4-multi-region": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.839.0", + "@aws-sdk/xml-builder": "3.821.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", + "@smithy/eventstream-serde-browser": "^4.0.4", + "@smithy/eventstream-serde-config-resolver": "^4.1.2", + "@smithy/eventstream-serde-node": "^4.0.4", "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-blob-browser": "^4.0.4", "@smithy/hash-node": "^4.0.4", + "@smithy/hash-stream-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", + "@smithy/md5-js": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.13", "@smithy/middleware-retry": "^4.1.14", @@ -1857,33 +1870,34 @@ "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", + "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.6", - "tslib": "^2.6.2" + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/client-sso": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.840.0.tgz", - "integrity": "sha512-3Zp+FWN2hhmKdpS0Ragi5V2ZPsZNScE3jlbgoJjzjI/roHZqO+e3/+XFN4TlM0DsPKYJNp+1TAjmhxN6rOnfYA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.839.0.tgz", + "integrity": "sha512-AZABysUhbfcwXVlMo97/vwHgsfJNF81wypCAowpqAJkSjP2KrqsqHpb71/RoR2w8JGmEnBBXRD4wIxDhnmifWg==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.839.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", "@smithy/fetch-http-handler": "^5.0.4", @@ -1915,14 +1929,12 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/core": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.840.0.tgz", - "integrity": "sha512-x3Zgb39tF1h2XpU+yA4OAAQlW6LVEfXNlSedSYJ7HGKXqA/E9h3rWQVpYfhXXVVsLdYXdNw5KBUkoAoruoZSZA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.839.0.tgz", + "integrity": "sha512-KdwL5RaK7eUIlOpdOoZ5u+2t4X1rdX/MTZgz3IV/aBzjVUoGsp+uUnbyqXomLQSUitPHp72EE/NHDsvWW/IHvQ==", "dependencies": { - "@aws-sdk/types": "3.840.0", + "@aws-sdk/types": "3.821.0", "@aws-sdk/xml-builder": "3.821.0", "@smithy/core": "^3.6.0", "@smithy/node-config-provider": "^4.1.3", @@ -1942,15 +1954,13 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.840.0.tgz", - "integrity": "sha512-EzF6VcJK7XvQ/G15AVEfJzN2mNXU8fcVpXo4bRyr1S6t2q5zx6UPH/XjDbn18xyUmOq01t+r8gG+TmHEVo18fA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.839.0.tgz", + "integrity": "sha512-cWTadewPPz1OvObZJB+olrgh8VwcgIVcT293ZUT9V0CMF0UU7QaPwJP7uNXcNxltTh+sk1yhjH4UlcnJigZZbA==", "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -1959,15 +1969,13 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.840.0.tgz", - "integrity": "sha512-wbnUiPGLVea6mXbUh04fu+VJmGkQvmToPeTYdHE8eRZq3NRDi3t3WltT+jArLBKD/4NppRpMjf2ju4coMCz91g==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.839.0.tgz", + "integrity": "sha512-fv0BZwrDhWDju4D1MCLT4I2aPjr0dVQ6P+MpqvcGNOA41Oa9UdRhYTV5iuy5NLXzIzoCmnS+XfSq5Kbsf6//xw==", "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/node-http-handler": "^4.0.6", "@smithy/property-provider": "^4.0.4", @@ -1981,21 +1989,19 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.840.0.tgz", - "integrity": "sha512-7F290BsWydShHb+7InXd+IjJc3mlEIm9I0R57F/Pjl1xZB69MdkhVGCnuETWoBt4g53ktJd6NEjzm/iAhFXFmw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.839.0.tgz", + "integrity": "sha512-GHm0hF4CiDxIDR7TauMaA6iI55uuSqRxMBcqTAHaTPm6+h1A+MS+ysQMxZ+Jvwtoy8WmfTIGrJVxSCw0sK2hvA==", "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/credential-provider-env": "3.840.0", - "@aws-sdk/credential-provider-http": "3.840.0", - "@aws-sdk/credential-provider-process": "3.840.0", - "@aws-sdk/credential-provider-sso": "3.840.0", - "@aws-sdk/credential-provider-web-identity": "3.840.0", - "@aws-sdk/nested-clients": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/credential-provider-env": "3.839.0", + "@aws-sdk/credential-provider-http": "3.839.0", + "@aws-sdk/credential-provider-process": "3.839.0", + "@aws-sdk/credential-provider-sso": "3.839.0", + "@aws-sdk/credential-provider-web-identity": "3.839.0", + "@aws-sdk/nested-clients": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -2006,20 +2012,18 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.840.0.tgz", - "integrity": "sha512-KufP8JnxA31wxklLm63evUPSFApGcH8X86z3mv9SRbpCm5ycgWIGVCTXpTOdgq6rPZrwT9pftzv2/b4mV/9clg==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.839.0.tgz", + "integrity": "sha512-7bR+U2h+ft0V8chyeu9Bh/pvau4ZkQMeRt5f0dAULoepZQ77QQVRP4H04yJPTg9DCtqbVULQ3uf5YOp1/08vQw==", "dependencies": { - "@aws-sdk/credential-provider-env": "3.840.0", - "@aws-sdk/credential-provider-http": "3.840.0", - "@aws-sdk/credential-provider-ini": "3.840.0", - "@aws-sdk/credential-provider-process": "3.840.0", - "@aws-sdk/credential-provider-sso": "3.840.0", - "@aws-sdk/credential-provider-web-identity": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/credential-provider-env": "3.839.0", + "@aws-sdk/credential-provider-http": "3.839.0", + "@aws-sdk/credential-provider-ini": "3.839.0", + "@aws-sdk/credential-provider-process": "3.839.0", + "@aws-sdk/credential-provider-sso": "3.839.0", + "@aws-sdk/credential-provider-web-identity": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -2030,15 +2034,13 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.840.0.tgz", - "integrity": "sha512-HkDQWHy8tCI4A0Ps2NVtuVYMv9cB4y/IuD/TdOsqeRIAT12h8jDb98BwQPNLAImAOwOWzZJ8Cu0xtSpX7CQhMw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.839.0.tgz", + "integrity": "sha512-qShpekjociUZ+isyQNa0P7jo+0q3N2+0eJDg8SGyP6K6hHTcGfiqxTDps+IKl6NreCPhZCBzyI9mWkP0xSDR6g==", "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", @@ -2048,17 +2050,15 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.840.0.tgz", - "integrity": "sha512-2qgdtdd6R0Z1y0KL8gzzwFUGmhBHSUx4zy85L2XV1CXhpRNwV71SVWJqLDVV5RVWVf9mg50Pm3AWrUC0xb0pcA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.839.0.tgz", + "integrity": "sha512-w10zBLHhU8SBQcdrSPMI02haLoRGZg+gP7mH/Er8VhIXfHefbr7o4NirmB0hwdw/YAH8MLlC9jj7c2SJlsNhYA==", "dependencies": { - "@aws-sdk/client-sso": "3.840.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/token-providers": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/client-sso": "3.839.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/token-providers": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", @@ -2068,16 +2068,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.840.0.tgz", - "integrity": "sha512-dpEeVXG8uNZSmVXReE4WP0lwoioX2gstk4RnUgrdUE3YaPq8A+hJiVAyc3h+cjDeIqfbsQbZm9qFetKC2LF9dQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.839.0.tgz", + "integrity": "sha512-EvqTc7J1kgmiuxknpCp1S60hyMQvmKxsI5uXzQtcogl/N55rxiXEqnCLI5q6p33q91PJegrcMCM5Q17Afhm5qA==", "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/nested-clients": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/nested-clients": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2086,14 +2084,12 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", - "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", + "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", "dependencies": { - "@aws-sdk/types": "3.840.0", + "@aws-sdk/types": "3.821.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2102,14 +2098,12 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-logger": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", - "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", + "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", "dependencies": { - "@aws-sdk/types": "3.840.0", + "@aws-sdk/types": "3.821.0", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -2117,14 +2111,12 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", - "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", + "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", "dependencies": { - "@aws-sdk/types": "3.840.0", + "@aws-sdk/types": "3.821.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2133,16 +2125,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.840.0.tgz", - "integrity": "sha512-hiiMf7BP5ZkAFAvWRcK67Mw/g55ar7OCrvrynC92hunx/xhMkrgSLM0EXIZ1oTn3uql9kH/qqGF0nqsK6K555A==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.839.0.tgz", + "integrity": "sha512-2u74uRM1JWq6Sf7+3YpjejPM9YkomGt4kWhrmooIBEq1k5r2GTbkH7pNCxBQwBueXM21jAGVDxxeClpTx+5hig==", "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", "@smithy/core": "^3.6.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", @@ -2152,25 +2142,23 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/nested-clients": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.840.0.tgz", - "integrity": "sha512-LXYYo9+n4hRqnRSIMXLBb+BLz+cEmjMtTudwK1BF6Bn2RfdDv29KuyeDRrPCS3TwKl7ZKmXUmE9n5UuHAPfBpA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/nested-clients": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.839.0.tgz", + "integrity": "sha512-Glic0pg2THYP3aRhJORwJJBe1JLtJoEdWV/MFZNyzCklfMwEzpWtZAyxy+tQyFmMeW50uBAnh2R0jhMMcf257w==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.840.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.839.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", "@smithy/fetch-http-handler": "^5.0.4", @@ -2202,14 +2190,12 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", - "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", + "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", "dependencies": { - "@aws-sdk/types": "3.840.0", + "@aws-sdk/types": "3.821.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", @@ -2220,77 +2206,24 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/token-providers": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.840.0.tgz", - "integrity": "sha512-6BuTOLTXvmgwjK7ve7aTg9JaWFdM5UoMolLVPMyh3wTv9Ufalh8oklxYHUBIxsKkBGO2WiHXytveuxH6tAgTYg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/nested-clients": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/types": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", - "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-endpoints": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.840.0.tgz", - "integrity": "sha512-eqE9ROdg/Kk0rj3poutyRCFauPDXIf/WSvCqFiRDDVi6QOnCv/M0g2XW8/jSvkJlOyaXkNCptapIp6BeeFFGYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "@smithy/util-endpoints": "^3.0.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", - "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", + "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", "dependencies": { - "@aws-sdk/types": "3.840.0", + "@aws-sdk/types": "3.821.0", "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.840.0.tgz", - "integrity": "sha512-Fy5JUEDQU1tPm2Yw/YqRYYc27W5+QD/J4mYvQvdWjUGZLB5q3eLFMGD35Uc28ZFoGMufPr4OCxK/bRfWROBRHQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.839.0.tgz", + "integrity": "sha512-MuunkIG1bJVMtTH7MbjXOrhHleU5wjHz5eCAUc6vj7M9rwol71nqjj9b8RLnkO5gsJcKc29Qk8iV6xQuzKWNMw==", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/types": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/types": "3.821.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2307,12 +2240,10 @@ } } }, - "node_modules/@aws-sdk/client-iam/node_modules/@smithy/abort-controller": { + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/abort-controller": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" @@ -2321,12 +2252,10 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@smithy/node-http-handler": { + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/node-http-handler": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", - "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^4.0.4", "@smithy/protocol-http": "^5.1.2", @@ -2338,12 +2267,10 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/types": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "dev": true, - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -2351,44 +2278,89 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3": { + "node_modules/@aws-sdk/client-s3/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.731.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.731.0.tgz", + "integrity": "sha512-O4C/UYGgqMsBg21MMApFdgyh8BX568hQhbdoNFmRVTBoSnCZ3w+H4a1wBPX4Gyl0NX+ab6Xxo9rId8HiyPXJ0A==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.731.0", + "@aws-sdk/middleware-host-header": "3.731.0", + "@aws-sdk/middleware-logger": "3.731.0", + "@aws-sdk/middleware-recursion-detection": "3.731.0", + "@aws-sdk/middleware-user-agent": "3.731.0", + "@aws-sdk/region-config-resolver": "3.731.0", + "@aws-sdk/types": "3.731.0", + "@aws-sdk/util-endpoints": "3.731.0", + "@aws-sdk/util-user-agent-browser": "3.731.0", + "@aws-sdk/util-user-agent-node": "3.731.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.839.0.tgz", - "integrity": "sha512-7zDInY+qltKxeG+9d/97nbs+FWINcAi5bChBrleUQkuQ/dA9pSP1URo/6JlVzD2Ejvksm+hVK6z3VUWZaIAVOw==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.839.0.tgz", + "integrity": "sha512-7QnMApYfQBT441YkxObxt1hZ8TdqZH7h0NdYsvbLdEqGROXBDDT+Wq7ZVfsnKjuVUGQ/t75bIqFn7M8cdyESfA==", "dependencies": { - "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.839.0", "@aws-sdk/credential-provider-node": "3.839.0", - "@aws-sdk/middleware-bucket-endpoint": "3.830.0", - "@aws-sdk/middleware-expect-continue": "3.821.0", - "@aws-sdk/middleware-flexible-checksums": "3.839.0", "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-location-constraint": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-sdk-s3": "3.839.0", - "@aws-sdk/middleware-ssec": "3.821.0", "@aws-sdk/middleware-user-agent": "3.839.0", "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/signature-v4-multi-region": "3.839.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", "@aws-sdk/util-user-agent-node": "3.839.0", - "@aws-sdk/xml-builder": "3.821.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", - "@smithy/eventstream-serde-browser": "^4.0.4", - "@smithy/eventstream-serde-config-resolver": "^4.1.2", - "@smithy/eventstream-serde-node": "^4.0.4", "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-blob-browser": "^4.0.4", "@smithy/hash-node": "^4.0.4", - "@smithy/hash-stream-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", - "@smithy/md5-js": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.13", "@smithy/middleware-retry": "^4.1.14", @@ -2408,18 +2380,14 @@ "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", - "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", - "@smithy/util-waiter": "^4.0.6", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sso": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.839.0.tgz", "integrity": "sha512-AZABysUhbfcwXVlMo97/vwHgsfJNF81wypCAowpqAJkSjP2KrqsqHpb71/RoR2w8JGmEnBBXRD4wIxDhnmifWg==", @@ -2467,7 +2435,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/core": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.839.0.tgz", "integrity": "sha512-KdwL5RaK7eUIlOpdOoZ5u+2t4X1rdX/MTZgz3IV/aBzjVUoGsp+uUnbyqXomLQSUitPHp72EE/NHDsvWW/IHvQ==", @@ -2492,7 +2460,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-env": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.839.0.tgz", "integrity": "sha512-cWTadewPPz1OvObZJB+olrgh8VwcgIVcT293ZUT9V0CMF0UU7QaPwJP7uNXcNxltTh+sk1yhjH4UlcnJigZZbA==", @@ -2507,7 +2475,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-http": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.839.0.tgz", "integrity": "sha512-fv0BZwrDhWDju4D1MCLT4I2aPjr0dVQ6P+MpqvcGNOA41Oa9UdRhYTV5iuy5NLXzIzoCmnS+XfSq5Kbsf6//xw==", @@ -2527,7 +2495,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.839.0.tgz", "integrity": "sha512-GHm0hF4CiDxIDR7TauMaA6iI55uuSqRxMBcqTAHaTPm6+h1A+MS+ysQMxZ+Jvwtoy8WmfTIGrJVxSCw0sK2hvA==", @@ -2550,7 +2518,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-node": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.839.0.tgz", "integrity": "sha512-7bR+U2h+ft0V8chyeu9Bh/pvau4ZkQMeRt5f0dAULoepZQ77QQVRP4H04yJPTg9DCtqbVULQ3uf5YOp1/08vQw==", @@ -2572,7 +2540,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-process": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.839.0.tgz", "integrity": "sha512-qShpekjociUZ+isyQNa0P7jo+0q3N2+0eJDg8SGyP6K6hHTcGfiqxTDps+IKl6NreCPhZCBzyI9mWkP0xSDR6g==", @@ -2588,7 +2556,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-sso": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.839.0.tgz", "integrity": "sha512-w10zBLHhU8SBQcdrSPMI02haLoRGZg+gP7mH/Er8VhIXfHefbr7o4NirmB0hwdw/YAH8MLlC9jj7c2SJlsNhYA==", @@ -2606,7 +2574,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.839.0.tgz", "integrity": "sha512-EvqTc7J1kgmiuxknpCp1S60hyMQvmKxsI5uXzQtcogl/N55rxiXEqnCLI5q6p33q91PJegrcMCM5Q17Afhm5qA==", @@ -2622,7 +2590,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-host-header": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", @@ -2636,7 +2604,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-logger": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", @@ -2649,7 +2617,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", @@ -2663,7 +2631,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-user-agent": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.839.0.tgz", "integrity": "sha512-2u74uRM1JWq6Sf7+3YpjejPM9YkomGt4kWhrmooIBEq1k5r2GTbkH7pNCxBQwBueXM21jAGVDxxeClpTx+5hig==", @@ -2680,7 +2648,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/nested-clients": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/nested-clients": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.839.0.tgz", "integrity": "sha512-Glic0pg2THYP3aRhJORwJJBe1JLtJoEdWV/MFZNyzCklfMwEzpWtZAyxy+tQyFmMeW50uBAnh2R0jhMMcf257w==", @@ -2728,7 +2696,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/region-config-resolver": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", @@ -2744,7 +2712,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", @@ -2755,7 +2723,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.839.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.839.0.tgz", "integrity": "sha512-MuunkIG1bJVMtTH7MbjXOrhHleU5wjHz5eCAUc6vj7M9rwol71nqjj9b8RLnkO5gsJcKc29Qk8iV6xQuzKWNMw==", @@ -2778,7 +2746,7 @@ } } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/abort-controller": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/abort-controller": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", @@ -2790,7 +2758,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/node-http-handler": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-http-handler": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", @@ -2805,7 +2773,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/types": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", @@ -2816,196 +2784,37 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.731.0.tgz", - "integrity": "sha512-O4C/UYGgqMsBg21MMApFdgyh8BX568hQhbdoNFmRVTBoSnCZ3w+H4a1wBPX4Gyl0NX+ab6Xxo9rId8HiyPXJ0A==", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.731.0.tgz", + "integrity": "sha512-NrdkJg6oOUbXR2r9WvHP408CLyvST8cJfp1/jP9pemtjvjPoh6NukbCtiSFdOOb1eryP02CnqQWItfJC1p2Y/Q==", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.731.0", - "@aws-sdk/middleware-host-header": "3.731.0", - "@aws-sdk/middleware-logger": "3.731.0", - "@aws-sdk/middleware-recursion-detection": "3.731.0", - "@aws-sdk/middleware-user-agent": "3.731.0", - "@aws-sdk/region-config-resolver": "3.731.0", - "@aws-sdk/types": "3.731.0", - "@aws-sdk/util-endpoints": "3.731.0", - "@aws-sdk/util-user-agent-browser": "3.731.0", - "@aws-sdk/util-user-agent-node": "3.731.0", - "@smithy/config-resolver": "^4.0.0", - "@smithy/core": "^3.0.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/hash-node": "^4.0.0", - "@smithy/invalid-dependency": "^4.0.0", - "@smithy/middleware-content-length": "^4.0.0", - "@smithy/middleware-endpoint": "^4.0.0", - "@smithy/middleware-retry": "^4.0.0", - "@smithy/middleware-serde": "^4.0.0", - "@smithy/middleware-stack": "^4.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", "@smithy/types": "^4.0.0", - "@smithy/url-parser": "^4.0.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.0", - "@smithy/util-defaults-mode-node": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "@smithy/util-middleware": "^4.0.0", - "@smithy/util-retry": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.839.0.tgz", - "integrity": "sha512-7QnMApYfQBT441YkxObxt1hZ8TdqZH7h0NdYsvbLdEqGROXBDDT+Wq7ZVfsnKjuVUGQ/t75bIqFn7M8cdyESfA==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.839.0", - "@aws-sdk/credential-provider-node": "3.839.0", - "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-logger": "3.821.0", - "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.839.0", - "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.839.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.6.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.13", - "@smithy/middleware-retry": "^4.1.14", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.21", - "@smithy/util-defaults-mode-node": "^4.0.21", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sso": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.839.0.tgz", - "integrity": "sha512-AZABysUhbfcwXVlMo97/vwHgsfJNF81wypCAowpqAJkSjP2KrqsqHpb71/RoR2w8JGmEnBBXRD4wIxDhnmifWg==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.839.0", - "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-logger": "3.821.0", - "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.839.0", - "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.839.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.6.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.13", - "@smithy/middleware-retry": "^4.1.14", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.21", - "@smithy/util-defaults-mode-node": "^4.0.21", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/core": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.839.0.tgz", - "integrity": "sha512-KdwL5RaK7eUIlOpdOoZ5u+2t4X1rdX/MTZgz3IV/aBzjVUoGsp+uUnbyqXomLQSUitPHp72EE/NHDsvWW/IHvQ==", + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { + "version": "3.731.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.731.0.tgz", + "integrity": "sha512-riztxTAfncFS9yQWcBJffGgOgLoKSa63ph+rxWJxKl6BHAmWEvHICj1qDcVmnWfIcvJ5cClclY75l9qKaUH7rQ==", "dependencies": { - "@aws-sdk/types": "3.821.0", - "@aws-sdk/xml-builder": "3.821.0", - "@smithy/core": "^3.6.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/signature-v4": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-utf8": "^4.0.0", - "fast-xml-parser": "4.4.1", + "@aws-sdk/types": "3.731.0", + "@smithy/types": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.839.0.tgz", - "integrity": "sha512-cWTadewPPz1OvObZJB+olrgh8VwcgIVcT293ZUT9V0CMF0UU7QaPwJP7uNXcNxltTh+sk1yhjH4UlcnJigZZbA==", + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -3013,42 +2822,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.839.0.tgz", - "integrity": "sha512-fv0BZwrDhWDju4D1MCLT4I2aPjr0dVQ6P+MpqvcGNOA41Oa9UdRhYTV5iuy5NLXzIzoCmnS+XfSq5Kbsf6//xw==", + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/property-provider": "^4.0.4", + "@smithy/abort-controller": "^4.0.4", "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/util-stream": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.839.0.tgz", - "integrity": "sha512-GHm0hF4CiDxIDR7TauMaA6iI55uuSqRxMBcqTAHaTPm6+h1A+MS+ysQMxZ+Jvwtoy8WmfTIGrJVxSCw0sK2hvA==", - "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/credential-provider-env": "3.839.0", - "@aws-sdk/credential-provider-http": "3.839.0", - "@aws-sdk/credential-provider-process": "3.839.0", - "@aws-sdk/credential-provider-sso": "3.839.0", - "@aws-sdk/credential-provider-web-identity": "3.839.0", - "@aws-sdk/nested-clients": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/querystring-builder": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -3056,866 +2837,10 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.839.0.tgz", - "integrity": "sha512-7bR+U2h+ft0V8chyeu9Bh/pvau4ZkQMeRt5f0dAULoepZQ77QQVRP4H04yJPTg9DCtqbVULQ3uf5YOp1/08vQw==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.839.0", - "@aws-sdk/credential-provider-http": "3.839.0", - "@aws-sdk/credential-provider-ini": "3.839.0", - "@aws-sdk/credential-provider-process": "3.839.0", - "@aws-sdk/credential-provider-sso": "3.839.0", - "@aws-sdk/credential-provider-web-identity": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.839.0.tgz", - "integrity": "sha512-qShpekjociUZ+isyQNa0P7jo+0q3N2+0eJDg8SGyP6K6hHTcGfiqxTDps+IKl6NreCPhZCBzyI9mWkP0xSDR6g==", - "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.839.0.tgz", - "integrity": "sha512-w10zBLHhU8SBQcdrSPMI02haLoRGZg+gP7mH/Er8VhIXfHefbr7o4NirmB0hwdw/YAH8MLlC9jj7c2SJlsNhYA==", - "dependencies": { - "@aws-sdk/client-sso": "3.839.0", - "@aws-sdk/core": "3.839.0", - "@aws-sdk/token-providers": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.839.0.tgz", - "integrity": "sha512-EvqTc7J1kgmiuxknpCp1S60hyMQvmKxsI5uXzQtcogl/N55rxiXEqnCLI5q6p33q91PJegrcMCM5Q17Afhm5qA==", - "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/nested-clients": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", - "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-logger": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", - "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", - "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.839.0.tgz", - "integrity": "sha512-2u74uRM1JWq6Sf7+3YpjejPM9YkomGt4kWhrmooIBEq1k5r2GTbkH7pNCxBQwBueXM21jAGVDxxeClpTx+5hig==", - "dependencies": { - "@aws-sdk/core": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@smithy/core": "^3.6.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/nested-clients": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.839.0.tgz", - "integrity": "sha512-Glic0pg2THYP3aRhJORwJJBe1JLtJoEdWV/MFZNyzCklfMwEzpWtZAyxy+tQyFmMeW50uBAnh2R0jhMMcf257w==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.839.0", - "@aws-sdk/middleware-host-header": "3.821.0", - "@aws-sdk/middleware-logger": "3.821.0", - "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.839.0", - "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.828.0", - "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.839.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.6.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.13", - "@smithy/middleware-retry": "^4.1.14", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.21", - "@smithy/util-defaults-mode-node": "^4.0.21", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", - "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", - "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", - "dependencies": { - "@aws-sdk/types": "3.821.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.839.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.839.0.tgz", - "integrity": "sha512-MuunkIG1bJVMtTH7MbjXOrhHleU5wjHz5eCAUc6vj7M9rwol71nqjj9b8RLnkO5gsJcKc29Qk8iV6xQuzKWNMw==", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.839.0", - "@aws-sdk/types": "3.821.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-http-handler": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", - "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", - "dependencies": { - "@smithy/abort-controller": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { - "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.731.0.tgz", - "integrity": "sha512-NrdkJg6oOUbXR2r9WvHP408CLyvST8cJfp1/jP9pemtjvjPoh6NukbCtiSFdOOb1eryP02CnqQWItfJC1p2Y/Q==", - "dependencies": { - "@smithy/types": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { - "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.731.0.tgz", - "integrity": "sha512-riztxTAfncFS9yQWcBJffGgOgLoKSa63ph+rxWJxKl6BHAmWEvHICj1qDcVmnWfIcvJ5cClclY75l9qKaUH7rQ==", - "dependencies": { - "@aws-sdk/types": "3.731.0", - "@smithy/types": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-http-handler": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", - "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", - "dependencies": { - "@smithy/abort-controller": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.840.0.tgz", - "integrity": "sha512-h+mu89Wk81Ne+B624GT/pBM5VjuAZueSeQNixhgtQ1QHi6bZzrpz8+lvMSibKO+kXFyQsTLzkyibbxnhLpWQZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/credential-provider-node": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.840.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.6.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.13", - "@smithy/middleware-retry": "^4.1.14", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.21", - "@smithy/util-defaults-mode-node": "^4.0.21", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/client-sso": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.840.0.tgz", - "integrity": "sha512-3Zp+FWN2hhmKdpS0Ragi5V2ZPsZNScE3jlbgoJjzjI/roHZqO+e3/+XFN4TlM0DsPKYJNp+1TAjmhxN6rOnfYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.840.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.6.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.13", - "@smithy/middleware-retry": "^4.1.14", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.21", - "@smithy/util-defaults-mode-node": "^4.0.21", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/core": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.840.0.tgz", - "integrity": "sha512-x3Zgb39tF1h2XpU+yA4OAAQlW6LVEfXNlSedSYJ7HGKXqA/E9h3rWQVpYfhXXVVsLdYXdNw5KBUkoAoruoZSZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@aws-sdk/xml-builder": "3.821.0", - "@smithy/core": "^3.6.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/signature-v4": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-utf8": "^4.0.0", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.840.0.tgz", - "integrity": "sha512-EzF6VcJK7XvQ/G15AVEfJzN2mNXU8fcVpXo4bRyr1S6t2q5zx6UPH/XjDbn18xyUmOq01t+r8gG+TmHEVo18fA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.840.0.tgz", - "integrity": "sha512-wbnUiPGLVea6mXbUh04fu+VJmGkQvmToPeTYdHE8eRZq3NRDi3t3WltT+jArLBKD/4NppRpMjf2ju4coMCz91g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/util-stream": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.840.0.tgz", - "integrity": "sha512-7F290BsWydShHb+7InXd+IjJc3mlEIm9I0R57F/Pjl1xZB69MdkhVGCnuETWoBt4g53ktJd6NEjzm/iAhFXFmw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/credential-provider-env": "3.840.0", - "@aws-sdk/credential-provider-http": "3.840.0", - "@aws-sdk/credential-provider-process": "3.840.0", - "@aws-sdk/credential-provider-sso": "3.840.0", - "@aws-sdk/credential-provider-web-identity": "3.840.0", - "@aws-sdk/nested-clients": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.840.0.tgz", - "integrity": "sha512-KufP8JnxA31wxklLm63evUPSFApGcH8X86z3mv9SRbpCm5ycgWIGVCTXpTOdgq6rPZrwT9pftzv2/b4mV/9clg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.840.0", - "@aws-sdk/credential-provider-http": "3.840.0", - "@aws-sdk/credential-provider-ini": "3.840.0", - "@aws-sdk/credential-provider-process": "3.840.0", - "@aws-sdk/credential-provider-sso": "3.840.0", - "@aws-sdk/credential-provider-web-identity": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.840.0.tgz", - "integrity": "sha512-HkDQWHy8tCI4A0Ps2NVtuVYMv9cB4y/IuD/TdOsqeRIAT12h8jDb98BwQPNLAImAOwOWzZJ8Cu0xtSpX7CQhMw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.840.0.tgz", - "integrity": "sha512-2qgdtdd6R0Z1y0KL8gzzwFUGmhBHSUx4zy85L2XV1CXhpRNwV71SVWJqLDVV5RVWVf9mg50Pm3AWrUC0xb0pcA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.840.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/token-providers": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.840.0.tgz", - "integrity": "sha512-dpEeVXG8uNZSmVXReE4WP0lwoioX2gstk4RnUgrdUE3YaPq8A+hJiVAyc3h+cjDeIqfbsQbZm9qFetKC2LF9dQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/nested-clients": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", - "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-logger": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", - "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", - "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.840.0.tgz", - "integrity": "sha512-hiiMf7BP5ZkAFAvWRcK67Mw/g55ar7OCrvrynC92hunx/xhMkrgSLM0EXIZ1oTn3uql9kH/qqGF0nqsK6K555A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@smithy/core": "^3.6.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/nested-clients": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.840.0.tgz", - "integrity": "sha512-LXYYo9+n4hRqnRSIMXLBb+BLz+cEmjMtTudwK1BF6Bn2RfdDv29KuyeDRrPCS3TwKl7ZKmXUmE9n5UuHAPfBpA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.840.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.840.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.6.0", - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.13", - "@smithy/middleware-retry": "^4.1.14", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.5", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.21", - "@smithy/util-defaults-mode-node": "^4.0.21", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", - "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/token-providers": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.840.0.tgz", - "integrity": "sha512-6BuTOLTXvmgwjK7ve7aTg9JaWFdM5UoMolLVPMyh3wTv9Ufalh8oklxYHUBIxsKkBGO2WiHXytveuxH6tAgTYg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.840.0", - "@aws-sdk/nested-clients": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/types": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", - "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-endpoints": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.840.0.tgz", - "integrity": "sha512-eqE9ROdg/Kk0rj3poutyRCFauPDXIf/WSvCqFiRDDVi6QOnCv/M0g2XW8/jSvkJlOyaXkNCptapIp6BeeFFGYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "@smithy/util-endpoints": "^3.0.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", - "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.840.0.tgz", - "integrity": "sha512-Fy5JUEDQU1tPm2Yw/YqRYYc27W5+QD/J4mYvQvdWjUGZLB5q3eLFMGD35Uc28ZFoGMufPr4OCxK/bRfWROBRHQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/node-http-handler": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", - "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/types": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "dev": true, - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, diff --git a/server/aws-lsp-identity/src/iam/iamProvider.test.ts b/server/aws-lsp-identity/src/iam/iamProvider.test.ts index c2958123b6..66415caf4e 100644 --- a/server/aws-lsp-identity/src/iam/iamProvider.test.ts +++ b/server/aws-lsp-identity/src/iam/iamProvider.test.ts @@ -108,7 +108,7 @@ describe('IamProvider', () => { observability.telemetry = stubInterface() handlers = stubInterface({ - sendGetMfaCode: Promise.resolve({ code: 'mfa-code' }), + sendGetMfaCode: Promise.resolve({ code: 'mfa-code', mfaSerial: 'mfa-serial' }), }) token = stubInterface() diff --git a/server/aws-lsp-identity/src/iam/iamProvider.ts b/server/aws-lsp-identity/src/iam/iamProvider.ts index 10bffc3aec..5d207d68cc 100644 --- a/server/aws-lsp-identity/src/iam/iamProvider.ts +++ b/server/aws-lsp-identity/src/iam/iamProvider.ts @@ -141,14 +141,6 @@ export class IamProvider { params.profile.settings?.region ) if (mfaRequired) { - // Get the MFA device serial number from the profile - if (!params.profile.settings?.mfa_serial) { - throw new AwsError( - 'MFA serial required when assuming role with MultiFactorAuthPresent permission condition', - AwsErrorCodes.E_MFA_REQUIRED - ) - } - assumeRoleInput.SerialNumber = params.profile.settings?.mfa_serial // Request an MFA code from the language client let timeoutId: NodeJS.Timeout | undefined const timeout = new Promise( @@ -160,18 +152,27 @@ export class IamProvider { ) const response = await Promise.race([ params.handlers.sendGetMfaCode({ - mfaSerial: params.profile.settings?.mfa_serial, profileName: params.profile.name, + mfaSerial: params.profile.settings?.mfa_serial, }), timeout, ]) clearTimeout(timeoutId) + if (!response.code) { throw new AwsError( 'MFA code required when assuming role with MultiFactorAuthPresent permission condition', AwsErrorCodes.E_MFA_REQUIRED ) } + if (!response.mfaSerial) { + throw new AwsError( + 'MFA serial required when assuming role with MultiFactorAuthPresent permission condition', + AwsErrorCodes.E_MFA_REQUIRED + ) + } + + assumeRoleInput.SerialNumber = response.mfaSerial assumeRoleInput.TokenCode = response.code } diff --git a/server/aws-lsp-identity/src/iam/utils.ts b/server/aws-lsp-identity/src/iam/utils.ts index 5e5ddc02e1..a5ef2bf1af 100644 --- a/server/aws-lsp-identity/src/iam/utils.ts +++ b/server/aws-lsp-identity/src/iam/utils.ts @@ -14,6 +14,8 @@ import { StsCache } from '../sts/cache/stsCache' import { StsAutoRefresher } from '../sts/stsAutoRefresher' import { ProfileStore } from '../language-server/profiles/profileService' +const defaultRegion = 'us-east-1' + export async function validatePermissions( credentials: IamCredentials, permissions: string[], @@ -52,17 +54,17 @@ export function throwOnInvalidCredentialId(iamCredentialId?: string): asserts ia async function simulatePermissions( credentials: IamCredentials, permissions: string[], - region: string = 'us-east-1' + region?: string ): Promise { // Convert the credentials into an identity - const stsClient = new STSClient({ region: region, credentials: credentials }) + const stsClient = new STSClient({ region: region || defaultRegion, credentials: credentials }) const identity = await stsClient.send(new GetCallerIdentityCommand({})) if (!identity.Arn) { throw new AwsError('Caller identity ARN not found.', AwsErrorCodes.E_CALLER_IDENTITY_NOT_FOUND) } // Simulate permissions on the identity - const iamClient = new IAMClient({ region: region, credentials: credentials }) + const iamClient = new IAMClient({ region: region || defaultRegion, credentials: credentials }) return await iamClient.send( new SimulatePrincipalPolicyCommand({ PolicySourceArn: convertToIamArn(identity.Arn), diff --git a/server/aws-lsp-identity/src/language-server/identityService.test.ts b/server/aws-lsp-identity/src/language-server/identityService.test.ts index 5f3d8cc5e1..51966100ee 100644 --- a/server/aws-lsp-identity/src/language-server/identityService.test.ts +++ b/server/aws-lsp-identity/src/language-server/identityService.test.ts @@ -135,7 +135,7 @@ describe('IdentityService', () => { showUrl: _ => {}, showMessageRequest: _ => Promise.resolve({ title: 'client-response' }), showProgress: _ => Promise.resolve(), - sendGetMfaCode: () => Promise.resolve({ code: 'mfa-code' }), + sendGetMfaCode: () => Promise.resolve({ code: 'mfa-code', mfaSerial: 'mfa-serial' }), }, 'My Client', observability, From a91c5f3260fb95a1bfd5f6eec7880acb4306d1d6 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Tue, 29 Jul 2025 09:38:15 -0400 Subject: [PATCH 36/37] fix: update profile after prompting MFA serial --- .../aws-lsp-identity/src/iam/iamProvider.ts | 122 +++++++++--------- .../src/sts/cache/fileSystemStsCache.ts | 12 +- 2 files changed, 72 insertions(+), 62 deletions(-) diff --git a/server/aws-lsp-identity/src/iam/iamProvider.ts b/server/aws-lsp-identity/src/iam/iamProvider.ts index 5d207d68cc..95e2807ba6 100644 --- a/server/aws-lsp-identity/src/iam/iamProvider.ts +++ b/server/aws-lsp-identity/src/iam/iamProvider.ts @@ -1,14 +1,14 @@ import { AwsErrorCodes, + GetMfaCodeResult, IamCredential, - IamCredentialId, IamCredentials, ProfileKind, } from '@aws/language-server-runtimes/server-interface' import { AwsError } from '@aws/lsp-core' import { AssumeRoleCommand, AssumeRoleCommandInput, STSClient } from '@aws-sdk/client-sts' import { checkMfaRequired, IamFlowParams } from './utils' -import { createHash } from 'crypto' +import { convertProfileToId } from '../sts/cache/fileSystemStsCache' const sourceProfileRecursionMax = 5 const mfaTimeout = 2 * 60 * 1000 // 2 minutes @@ -17,7 +17,6 @@ export class IamProvider { readonly defaultRegion = 'us-east-1' async getCredential(params: IamFlowParams): Promise { - let id: IamCredentialId = '' let credentials: IamCredentials // Get the credentials directly from the profile @@ -30,13 +29,7 @@ export class IamProvider { } // Assume the role matching the found ARN else if (params.profile.kinds.includes(ProfileKind.IamSourceProfileProfile)) { - const key = JSON.stringify({ - RoleArn: params.profile.settings?.role_arn, - RoleSessionName: params.profile.settings?.role_session_name, - SerialNumber: params.profile.settings?.mfa_serial, - }) - id = createHash('sha1').update(key).digest('hex') - credentials = await this.getAssumedRoleCredential(id, params) + credentials = await this.getAssumedRoleCredential(params) } else { throw new AwsError( 'Credentials could not be found for provided profile kind', @@ -44,36 +37,27 @@ export class IamProvider { ) } - return { id: id, kinds: params.profile.kinds, credentials: credentials } + return { id: convertProfileToId(params.profile), kinds: params.profile.kinds, credentials: credentials } } - private async getAssumedRoleCredential(id: IamCredentialId, params: IamFlowParams): Promise { + private async getAssumedRoleCredential(params: IamFlowParams): Promise { if (!params.profile.settings) { throw new AwsError('Profile settings not found when assuming role.', AwsErrorCodes.E_INVALID_PROFILE) } // Try to get the STS credentials from cache let result: IamCredentials - const credential = await params.stsCache.getStsCredential(id).catch(_ => undefined) + const credential = await params.stsCache + .getStsCredential(convertProfileToId(params.profile)) + .catch(_ => undefined) if (credential) { - result = { - accessKeyId: credential.accessKeyId, - secretAccessKey: credential.secretAccessKey, - sessionToken: credential.sessionToken, - expiration: credential.expiration, - } + result = credential } else if (params.callStsOnInvalidIamCredential) { // Generate STS credentials - const response = await this.generateStsCredential(params) + result = await this.generateStsCredential(params) // Cache STS credentials - await params.stsCache.setStsCredential(id, response) - result = { - accessKeyId: response.accessKeyId, - secretAccessKey: response.secretAccessKey, - sessionToken: response.sessionToken, - expiration: response.expiration, - } + await params.stsCache.setStsCredential(convertProfileToId(params.profile), result) } else { // If we could not get the cached STS credential and cannot generate a new credential, give up params.observability.logging.log( @@ -85,7 +69,7 @@ export class IamProvider { // Set up auto-refresh if MFA is disabled if (!params.profile.settings.mfa_serial) { await params.stsAutoRefresher - .watch(id, () => this.generateStsCredential(params)) + .watch(convertProfileToId(params.profile), () => this.generateStsCredential(params)) .catch(reason => { params.observability.logging.log(`Unable to auto-refresh STS credentials. ${reason}`) }) @@ -123,59 +107,40 @@ export class IamProvider { private async generateStsCredential(params: IamFlowParams): Promise { try { + // Set up AssumeRole input const parentCredentials = await this.getParentCredential(params) const stsClient = new STSClient({ region: params.profile.settings?.region || this.defaultRegion, credentials: parentCredentials, }) - - // Add MFA fields to assume role request if MultiFactorAuthPresent is required const assumeRoleInput: AssumeRoleCommandInput = { RoleArn: params.profile.settings?.role_arn, RoleSessionName: params.profile.settings?.role_session_name || `session-${Date.now()}`, DurationSeconds: 3600, } + + // Add MFA fields to assume role request if MultiFactorAuthPresent is required const mfaRequired = await checkMfaRequired( parentCredentials, ['sts:AssumeRole'], params.profile.settings?.region ) if (mfaRequired) { - // Request an MFA code from the language client - let timeoutId: NodeJS.Timeout | undefined - const timeout = new Promise( - (_, reject) => - (timeoutId = setTimeout( - () => reject(new AwsError('MFA code request timed out', AwsErrorCodes.E_MFA_REQUIRED)), - mfaTimeout - )) - ) - const response = await Promise.race([ - params.handlers.sendGetMfaCode({ - profileName: params.profile.name, - mfaSerial: params.profile.settings?.mfa_serial, - }), - timeout, - ]) - clearTimeout(timeoutId) - - if (!response.code) { - throw new AwsError( - 'MFA code required when assuming role with MultiFactorAuthPresent permission condition', - AwsErrorCodes.E_MFA_REQUIRED - ) - } - if (!response.mfaSerial) { - throw new AwsError( - 'MFA serial required when assuming role with MultiFactorAuthPresent permission condition', - AwsErrorCodes.E_MFA_REQUIRED - ) - } - + const response = await this.requestMfa(params) assumeRoleInput.SerialNumber = response.mfaSerial assumeRoleInput.TokenCode = response.code + + // Add the MFA serial number to the profile + const updatedProfile = { + ...params.profile, + settings: { ...params.profile.settings, mfa_serial: response.mfaSerial }, + } + params.profileStore.save({ profiles: [updatedProfile], ssoSessions: [] }) + // Update params.profile to ensure STS cache key is generated with updated MFA serial number + params.profile = updatedProfile } + // Call AssumeRole API const command = new AssumeRoleCommand(assumeRoleInput) const { Credentials } = await stsClient.send(command) if (!Credentials?.AccessKeyId || !Credentials.SecretAccessKey) { @@ -195,4 +160,39 @@ export class IamProvider { throw e } } + + // Request an MFA code from the language client + private async requestMfa(params: IamFlowParams): Promise { + let timeoutId: NodeJS.Timeout | undefined + const timeout = new Promise( + (_, reject) => + (timeoutId = setTimeout( + () => reject(new AwsError('MFA code request timed out', AwsErrorCodes.E_MFA_REQUIRED)), + mfaTimeout + )) + ) + const response = await Promise.race([ + params.handlers.sendGetMfaCode({ + profileName: params.profile.name, + mfaSerial: params.profile.settings?.mfa_serial, + }), + timeout, + ]) + clearTimeout(timeoutId) + + if (!response.code) { + throw new AwsError( + 'MFA code required when assuming role with MultiFactorAuthPresent permission condition', + AwsErrorCodes.E_MFA_REQUIRED + ) + } + if (!response.mfaSerial) { + throw new AwsError( + 'MFA serial required when assuming role with MultiFactorAuthPresent permission condition', + AwsErrorCodes.E_MFA_REQUIRED + ) + } + + return response + } } diff --git a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts index 87a2ad5e42..fd40ffda47 100644 --- a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts +++ b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts @@ -1,10 +1,11 @@ import { StsCache } from './stsCache' import { AwsError, Observability } from '@aws/lsp-core' -import { AwsErrorCodes, IamCredentials } from '@aws/language-server-runtimes/protocol' +import { AwsErrorCodes, IamCredentials, Profile } from '@aws/language-server-runtimes/protocol' import path, { join } from 'path' import { mkdir, readFile, unlink, writeFile } from 'fs/promises' import { getHomeDir } from '@smithy/shared-ini-file-loader' import { throwOnInvalidCredentialId } from '../../iam/utils' +import { createHash } from 'crypto' export class FileSystemStsCache implements StsCache { constructor(private readonly observability: Observability) {} @@ -61,6 +62,15 @@ export class FileSystemStsCache implements StsCache { } } +export function convertProfileToId(profile: Profile) { + const key = JSON.stringify({ + RoleArn: profile.settings?.role_arn, + RoleSessionName: profile.settings?.role_session_name, + SerialNumber: profile.settings?.mfa_serial, + }) + return createHash('sha1').update(key).digest('hex') +} + // Based on: // https://github.com/smithy-lang/smithy-typescript/blob/main/packages/shared-ini-file-loader/src/getSSOTokenFilepath.ts export function getStsCredentialFilepath(id: string): string { From 9345d1d6957cfcf64a3c07cfe75979732a427c94 Mon Sep 17 00:00:00 2001 From: Ramon Li Date: Wed, 30 Jul 2025 12:29:56 -0400 Subject: [PATCH 37/37] fix: incorporate STS feedback --- .../aws-lsp-identity/src/iam/iamProvider.ts | 27 +++----- .../src/language-server/autoRefresher.ts | 3 +- .../src/language-server/identityServer.ts | 3 +- .../src/sso/ssoTokenAutoRefresher.ts | 4 +- .../src/sts/cache/fileSystemStsCache.test.ts | 49 ++++++++++---- .../src/sts/cache/fileSystemStsCache.ts | 60 +++++++++++------ .../src/sts/cache/refreshingStsCache.test.ts | 66 ------------------- .../src/sts/cache/refreshingStsCache.ts | 51 -------------- .../src/sts/stsAutoRefresher.test.ts | 6 +- .../src/sts/stsAutoRefresher.ts | 4 +- 10 files changed, 95 insertions(+), 178 deletions(-) delete mode 100644 server/aws-lsp-identity/src/sts/cache/refreshingStsCache.test.ts delete mode 100644 server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts diff --git a/server/aws-lsp-identity/src/iam/iamProvider.ts b/server/aws-lsp-identity/src/iam/iamProvider.ts index 95e2807ba6..dd93fc118b 100644 --- a/server/aws-lsp-identity/src/iam/iamProvider.ts +++ b/server/aws-lsp-identity/src/iam/iamProvider.ts @@ -83,10 +83,11 @@ export class IamProvider { if (params.profile.kinds.includes(ProfileKind.IamSourceProfileProfile)) { // Get the source profile const profileData = await params.profileStore.load() - const sourceProfile = profileData.profiles.find(p => p.name === params.profile.settings!.source_profile!) + const sourceName = params.profile.settings!.source_profile! + const sourceProfile = profileData.profiles.find(p => p.name === sourceName) if (!sourceProfile) { - params.observability.logging.log('Source profile not found.') - throw new AwsError('Source profile not found.', AwsErrorCodes.E_PROFILE_NOT_FOUND) + params.observability.logging.log(`Source profile ${sourceName} not found.`) + throw new AwsError(`Source profile ${sourceName} not found.`, AwsErrorCodes.E_PROFILE_NOT_FOUND) } // Obtain parent profile credentials if IamRoleSourceProfile chain isn't too long if (params.recursionCount <= sourceProfileRecursionMax) { @@ -163,22 +164,10 @@ export class IamProvider { // Request an MFA code from the language client private async requestMfa(params: IamFlowParams): Promise { - let timeoutId: NodeJS.Timeout | undefined - const timeout = new Promise( - (_, reject) => - (timeoutId = setTimeout( - () => reject(new AwsError('MFA code request timed out', AwsErrorCodes.E_MFA_REQUIRED)), - mfaTimeout - )) - ) - const response = await Promise.race([ - params.handlers.sendGetMfaCode({ - profileName: params.profile.name, - mfaSerial: params.profile.settings?.mfa_serial, - }), - timeout, - ]) - clearTimeout(timeoutId) + const response = await params.handlers.sendGetMfaCode({ + profileName: params.profile.name, + mfaSerial: params.profile.settings?.mfa_serial, + }) if (!response.code) { throw new AwsError( diff --git a/server/aws-lsp-identity/src/language-server/autoRefresher.ts b/server/aws-lsp-identity/src/language-server/autoRefresher.ts index 0ec337079d..7f7f72f3f1 100644 --- a/server/aws-lsp-identity/src/language-server/autoRefresher.ts +++ b/server/aws-lsp-identity/src/language-server/autoRefresher.ts @@ -1,5 +1,6 @@ import { Observability } from '@aws/lsp-core' +export const invalidDelay: number = -1 export const refreshWindowMillis: number = 5 * 60 * 1000 export const retryCooldownWindowMillis: number = 30000 const bufferedRefreshWindowMillis = refreshWindowMillis * 0.95 @@ -36,7 +37,7 @@ export abstract class AutoRefresher implements Disposable { } else { // Otherwise, expired this.observability.logging.log('SSO token has expired and will not be auto-refreshed.') - return -1 + return invalidDelay } this.observability.logging.log(`Auto-refreshing SSO token in ${delayMillis} milliseconds.`) diff --git a/server/aws-lsp-identity/src/language-server/identityServer.ts b/server/aws-lsp-identity/src/language-server/identityServer.ts index 82236d6e64..1ca20798ef 100644 --- a/server/aws-lsp-identity/src/language-server/identityServer.ts +++ b/server/aws-lsp-identity/src/language-server/identityServer.ts @@ -19,7 +19,6 @@ import { import { SharedConfigProfileStore } from './profiles/sharedConfigProfileStore' import { IdentityService } from './identityService' import { FileSystemSsoCache, RefreshingSsoCache } from '../sso/cache' -import { RefreshingStsCache } from '../sts/cache/refreshingStsCache' import { SsoTokenAutoRefresher } from '../sso/ssoTokenAutoRefresher' import { FileSystemStsCache } from '../sts/cache/fileSystemStsCache' import { StsAutoRefresher } from '../sts/stsAutoRefresher' @@ -67,7 +66,7 @@ export class IdentityServer extends ServerBase { ) const autoRefresher = new SsoTokenAutoRefresher(ssoCache, this.observability) - const stsCache = new RefreshingStsCache(new FileSystemStsCache(this.observability), this.observability) + const stsCache = new FileSystemStsCache(this.observability) const stsAutoRefresher = new StsAutoRefresher(stsCache, sendStsCredentialChanged, this.observability) const iamProvider = new IamProvider() diff --git a/server/aws-lsp-identity/src/sso/ssoTokenAutoRefresher.ts b/server/aws-lsp-identity/src/sso/ssoTokenAutoRefresher.ts index 5b448308b1..4a3863129e 100644 --- a/server/aws-lsp-identity/src/sso/ssoTokenAutoRefresher.ts +++ b/server/aws-lsp-identity/src/sso/ssoTokenAutoRefresher.ts @@ -5,7 +5,7 @@ import { MetricEvent } from '@aws/language-server-runtimes/server-interface' import { normalizeSettingList } from '../language-server/profiles/profileService' import { __ServiceException } from '@aws-sdk/client-sso-oidc/dist-types/models/SSOOIDCServiceException' import { AwsError, Observability } from '@aws/lsp-core' -import { AutoRefresher } from '../language-server/autoRefresher' +import { AutoRefresher, invalidDelay } from '../language-server/autoRefresher' export class SsoTokenAutoRefresher extends AutoRefresher { constructor( @@ -36,7 +36,7 @@ export class SsoTokenAutoRefresher extends AutoRefresher { // Refresh timeout if delay is valid const delayMillis = this.getDelay(ssoToken.expiresAt) - if (delayMillis >= 0) { + if (delayMillis !== invalidDelay) { this.observability.logging.log(`Auto-refreshing SSO token in ${delayMillis} milliseconds.`) this.timeouts[ssoSession.name] = setTimeout(this.watch.bind(this, clientName, ssoSession), delayMillis) } diff --git a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts index df3c4d135a..c686ad8cd1 100644 --- a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts +++ b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.test.ts @@ -22,7 +22,7 @@ const credential: IamCredentials = { accessKeyId: 'someaccesskeyid', secretAccessKey: 'somesecretaccesskey', sessionToken: 'somesessiontoken', - expiration: new Date('2024-09-25T18:09:20.455Z'), + expiration: new Date(Date.now() + 60 * 60 * 1000), } function setupTest(args?: { id?: string; credential?: IamCredentials }): void { @@ -83,7 +83,7 @@ describe('FileSystemStsCache', () => { await expectFileExists(filename).to.not.be.rejectedWith() }) - it('removeStsCredential throws on invalid profile name', async () => { + it('removeStsCredential throws on invalid id', async () => { await expect(sut.removeStsCredential(' ')).to.be.rejectedWith() }) @@ -108,7 +108,23 @@ describe('FileSystemStsCache', () => { }) it('getStsCredential returns undefined on invalid credential', async () => { - setupTest({ id: 'invalid-profile', credential: {} as IamCredentials }) + setupTest({ id: 'invalid-id', credential: {} as IamCredentials }) + + const actual = await sut.getStsCredential(id) + + expect(actual).to.be.undefined + }) + + it('getStsCredential returns undefined on expired credential', async () => { + setupTest({ + id: 'invalid-id', + credential: { + accessKeyId: 'newaccesskeyid', + secretAccessKey: 'newsecretaccesskey', + sessionToken: 'newsessiontoken', + expiration: new Date(Date.now() - 60 * 60 * 1000), + } as IamCredentials, + }) const actual = await sut.getStsCredential(id) @@ -146,20 +162,20 @@ describe('FileSystemStsCache', () => { it('setStsCredential writes updated existing credential', async () => { setupTest() - await sut.setStsCredential(id, { + const newCredential = { accessKeyId: 'newaccesskeyid', secretAccessKey: 'newsecretaccesskey', sessionToken: 'newsessiontoken', - expiration: new Date('2024-10-14T12:00:00.000Z'), - }) - + expiration: new Date(Date.now() + 60 * 60 * 1000), + } + await sut.setStsCredential(id, newCredential) const actual = await sut.getStsCredential(id) expect(actual).to.not.be.null.and.not.undefined - expect(actual?.accessKeyId).to.equal('newaccesskeyid') - expect(actual?.secretAccessKey).to.equal('newsecretaccesskey') - expect(actual?.sessionToken).to.equal('newsessiontoken') - expect(actual?.expiration?.toISOString()).to.equal('2024-10-14T12:00:00.000Z') + expect(actual?.accessKeyId).to.equal(newCredential.accessKeyId) + expect(actual?.secretAccessKey).to.equal(newCredential.secretAccessKey) + expect(actual?.sessionToken).to.equal(newCredential.sessionToken) + expect(actual?.expiration?.toISOString()).to.equal(newCredential.expiration.toISOString()) }) it('setStsCredential returns without error on invalid credential', async () => { @@ -167,4 +183,15 @@ describe('FileSystemStsCache', () => { await sut.setStsCredential(id, {} as IamCredentials) // no throw }) + + it('setStsCredential returns without error on expired credential', async () => { + setupTest() + + await sut.setStsCredential(id, { + accessKeyId: 'newaccesskeyid', + secretAccessKey: 'newsecretaccesskey', + sessionToken: 'newsessiontoken', + expiration: new Date(Date.now() - 60 * 60 * 1000), + } as IamCredentials) // no throw + }) }) diff --git a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts index fd40ffda47..a11120f232 100644 --- a/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts +++ b/server/aws-lsp-identity/src/sts/cache/fileSystemStsCache.ts @@ -17,33 +17,35 @@ export class FileSystemStsCache implements StsCache { } async getStsCredential(name: string): Promise { - return await getStsCredentialFromFile(name) - .then(credential => { - if ( - credential.accessKeyId && - credential.secretAccessKey && - credential.sessionToken && - credential.expiration - ) { - // Ensure Expiration is a Date object - if (typeof credential.expiration === 'string') { - credential = { ...credential, expiration: new Date(credential.expiration) } - } - return credential - } else { - return undefined - } - }) - .catch(reason => this.ignoreDoesNotExistOrThrow(reason)) + try { + let credential = await getStsCredentialFromFile(name) + if (!this.isValid(credential)) { + this.observability.logging.log(`Cannot get credential from ${name}: missing fields.`) + return undefined + } + // Ensure expiration is a Date object + if (typeof credential.expiration === 'string') { + credential = { ...credential, expiration: new Date(credential.expiration) } + } + if (this.isExpired(credential)) { + this.observability.logging.log(`Credential from ${name} is expired`) + return undefined + } + return credential + } catch (e) { + this.ignoreDoesNotExistOrThrow(e) + } } async setStsCredential(name: string, credential: IamCredentials): Promise { - if ( - !(credential.accessKeyId && credential.secretAccessKey && credential.sessionToken && credential.expiration) - ) { + if (!this.isValid(credential)) { this.observability.logging.log('Cannot set credential: missing fields.') return } + if (this.isExpired(credential)) { + this.observability.logging.log(`Cannot set credential: expired`) + return undefined + } await writeStsObjectToFile(name, credential).catch(reason => { throw AwsError.wrap(reason, AwsErrorCodes.E_CANNOT_WRITE_SSO_CACHE) @@ -60,6 +62,22 @@ export class FileSystemStsCache implements StsCache { this.observability.logging.log('Cannot read STS cache.') throw AwsError.wrap(error as Error, AwsErrorCodes.E_CANNOT_READ_SSO_CACHE) } + + private isValid(credential: IamCredentials): boolean { + return ( + credential.accessKeyId !== undefined && + credential.secretAccessKey !== undefined && + credential.sessionToken !== undefined && + credential.expiration !== undefined + ) + } + + private isExpired(credential: IamCredentials): boolean { + if (credential.expiration === undefined) { + return false + } + return Date.now() >= credential.expiration.getTime() + } } export function convertProfileToId(profile: Profile) { diff --git a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.test.ts b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.test.ts deleted file mode 100644 index d18dc67179..0000000000 --- a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { expect, use } from 'chai' -import { restore } from 'sinon' -import { stubInterface } from 'ts-sinon' -import { RefreshingStsCache } from './refreshingStsCache' -import { Logging, IamCredentials, Telemetry } from '@aws/language-server-runtimes/server-interface' -import { Observability } from '@aws/lsp-core' -import { StsCache } from './stsCache' - -// eslint-disable-next-line -use(require('chai-as-promised')) - -let observability: Observability - -const profileName: string = 'someprofile' - -function createStsCredential(expiresAsOffsetMillis: number): IamCredentials { - return { - accessKeyId: 'someaccesskeyid', - secretAccessKey: 'somesecretaccesskey', - sessionToken: 'somesessiontoken', - expiration: new Date(Date.now() + expiresAsOffsetMillis), - } satisfies IamCredentials -} - -function stubStsCache(credential?: IamCredentials): StsCache { - return stubInterface({ - getStsCredential: Promise.resolve(credential), - }) -} - -describe('RefreshingStsCache', () => { - beforeEach(() => { - observability = stubInterface() - observability.logging = stubInterface() - observability.telemetry = stubInterface() - }) - - afterEach(() => { - restore() - }) - - describe('getStsCredential', () => { - it('Returns nothing on no cached STS credential.', async () => { - const stsCache = stubStsCache() - const sut = new RefreshingStsCache(stsCache, observability) - - const actual = await sut.getStsCredential(profileName) - - expect(actual).to.be.undefined - }) - - it('Returns existing STS credential before refresh window (5 minutes before expiration).', async () => { - const credential = createStsCredential(6 * 60 * 1000 /* 6 minutes before */) - const stsCache = stubStsCache(credential) - const sut = new RefreshingStsCache(stsCache, observability) - - const actual = await sut.getStsCredential(profileName) - - expect(actual).to.not.be.null.and.not.empty - expect(actual?.accessKeyId).to.equal(credential.accessKeyId) - expect(actual?.secretAccessKey).to.equal(credential.secretAccessKey) - expect(actual?.sessionToken).to.equal(credential.sessionToken) - expect(actual?.expiration).to.equal(credential.expiration) - }) - }) -}) diff --git a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts b/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts deleted file mode 100644 index f8ea98334b..0000000000 --- a/server/aws-lsp-identity/src/sts/cache/refreshingStsCache.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { StsCache } from './stsCache' -import { AwsErrorCodes, IamCredentials } from '@aws/language-server-runtimes/server-interface' -import { AwsError, Observability } from '@aws/lsp-core' -import { throwOnInvalidCredentialId } from '../../iam/utils' - -export class RefreshingStsCache implements StsCache { - constructor( - private readonly next: StsCache, - private readonly observability: Observability - ) {} - - async removeStsCredential(iamCredentialId: string): Promise { - this.observability.logging.log('Removing STS Credential.') - throwOnInvalidCredentialId(iamCredentialId) - - await this.next.removeStsCredential(iamCredentialId) - } - - async getStsCredential(iamCredentialId: string): Promise { - this.observability.logging.log('Retrieving STS Credential.') - - throwOnInvalidCredentialId(iamCredentialId) - - const credential = await this.next.getStsCredential(iamCredentialId) - - if (!credential?.expiration) { - this.observability.logging.log('STS Credential not found.') - return undefined - } - - const nowMillis = Date.now() - const expirationMillis = new Date(credential.expiration).getTime() - - // Check if credential is still valid (not in refresh window) - if (nowMillis < expirationMillis) { - this.observability.logging.log( - 'STS credential expiration is before refresh window. Returning current STS credential.' - ) - return credential - } else { - // Credential is in refresh window or expired - this.observability.logging.log('STS credential has expired.') - throw new AwsError('STS credential has expired.', AwsErrorCodes.E_STS_CREDENTIAL_EXPIRED) - } - } - - async setStsCredential(iamCredentialId: string, credentials: IamCredentials): Promise { - this.observability.logging.log('Storing STS Credential.') - await this.next.setStsCredential(iamCredentialId, credentials) - } -} diff --git a/server/aws-lsp-identity/src/sts/stsAutoRefresher.test.ts b/server/aws-lsp-identity/src/sts/stsAutoRefresher.test.ts index 7250a405a1..ec080b6ba1 100644 --- a/server/aws-lsp-identity/src/sts/stsAutoRefresher.test.ts +++ b/server/aws-lsp-identity/src/sts/stsAutoRefresher.test.ts @@ -4,7 +4,7 @@ import { StubbedInstance, stubInterface } from 'ts-sinon' import { restore, spy } from 'sinon' import { AwsErrorCodes, IamCredentials, Logging, Telemetry } from '@aws/language-server-runtimes/server-interface' import { AwsError, Observability } from '@aws/lsp-core' -import { RefreshingStsCache } from './cache/refreshingStsCache' +import { FileSystemStsCache } from './cache/fileSystemStsCache' // eslint-disable-next-line use(require('chai-as-promised')) @@ -32,8 +32,8 @@ function refreshStsCredential(): Promise { } satisfies IamCredentials) } -function stubStsCache(credential?: IamCredentials): RefreshingStsCache { - return stubInterface({ +function stubStsCache(credential?: IamCredentials): FileSystemStsCache { + return stubInterface({ getStsCredential: credential ? Promise.resolve(credential) : Promise.reject(new AwsError('Test: No STS credential', AwsErrorCodes.E_INVALID_STS_CREDENTIAL)), diff --git a/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts b/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts index 321411dcfa..e5610a8c7e 100644 --- a/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts +++ b/server/aws-lsp-identity/src/sts/stsAutoRefresher.ts @@ -1,6 +1,6 @@ import { StsCache } from './cache/stsCache' import { Observability } from '@aws/lsp-core' -import { AutoRefresher } from '../language-server/autoRefresher' +import { AutoRefresher, invalidDelay } from '../language-server/autoRefresher' import { IamCredentials, StsCredentialChangedKind } from '@aws/language-server-runtimes/protocol' import { SendStsCredentialChanged } from '../iam/utils' @@ -38,7 +38,7 @@ export class StsAutoRefresher extends AutoRefresher { (this.stsCredentialDetails[iamCredentialId] = { lastRefreshMillis: 0 }) const delayMillis = this.getDelay(credential.expiration.toISOString()) - if (delayMillis >= 0) { + if (delayMillis !== invalidDelay) { this.observability.logging.info(`Auto-refreshing STS credentials in ${delayMillis} milliseconds.`) this.timeouts[iamCredentialId] = setTimeout(async () => { try {