From 8f3abdb908fe14102fa347bf9061c8b572da8767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADdia=20Tarcza?= <100163235+diatrcz@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:01:29 +0200 Subject: [PATCH 1/9] feat(auth): add support for new service version in VPC Instant Auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lídia Tarcza <100163235+diatrcz@users.noreply.github.com> --- Authentication.md | 9 ++ .../vpc-instance-authenticator.ts | 26 ++++++ .../vpc-instance-token-manager.ts | 43 ++++++++- etc/ibm-cloud-sdk-core.api.md | 12 +++ test/unit/vpc-instance-authenticator.test.js | 93 +++++++++++++++++++ test/unit/vpc-instance-token-manager.test.js | 75 +++++++++++++++ 6 files changed, 253 insertions(+), 5 deletions(-) diff --git a/Authentication.md b/Authentication.md index e4a36b7ca..bc238ad93 100644 --- a/Authentication.md +++ b/Authentication.md @@ -492,6 +492,13 @@ The IAM access token is added to each outbound request in the `Authorization` he The default value of this property is `http://169.254.169.254`. However, if the VPC Instance Metadata Service is configured with the HTTP Secure Protocol setting (`https`), then you should configure this property to be `https://api.metadata.cloud.ibm.com`. +- serviceVersion: (optional) The VPC Instance Metadata Service version to use. +The default value is `2022-03-01`. When set to `2025-08-26`, the authenticator will use the new API paths +(`/identity/v1/token` and `/identity/v1/iam_tokens`) instead of the legacy paths. + +- tokenLifetime: (optional) The lifetime (in seconds) of the instance identity token. +The default value is `300` seconds. This property can only be configured programmatically (not via environment variables). + Usage Notes: 1. At most one of `iamProfileCrn` or `iamProfileId` may be specified. The specified value must map to a trusted IAM profile that has been linked to the compute resource (virtual server instance). @@ -533,6 +540,8 @@ const ExampleServiceV1 = require('/example-service/v1'); const options = { serviceName: 'example_service', + serviceVersion: '2025-08-26', + tokenLifetime: 600, }; const service = ExampleServiceV1.newInstance(options); diff --git a/auth/authenticators/vpc-instance-authenticator.ts b/auth/authenticators/vpc-instance-authenticator.ts index 28a07bd33..99ceb746e 100644 --- a/auth/authenticators/vpc-instance-authenticator.ts +++ b/auth/authenticators/vpc-instance-authenticator.ts @@ -24,6 +24,10 @@ export interface Options extends BaseOptions { iamProfileCrn?: string; /** The ID of the linked trusted IAM profile to be used when obtaining the IAM access token */ iamProfileId?: string; + + serviceVersion: string; + + tokenLifetime: number; } /** @@ -43,6 +47,10 @@ export class VpcInstanceAuthenticator extends TokenRequestBasedAuthenticator { private iamProfileId: string; + private serviceVersion: string; + + private tokenLifetime: number; + /** * Create a new VpcInstanceAuthenticator instance. * @@ -68,6 +76,12 @@ export class VpcInstanceAuthenticator extends TokenRequestBasedAuthenticator { if (options.iamProfileId) { this.iamProfileId = options.iamProfileId; } + if (options.serviceVersion) { + this.serviceVersion = options.serviceVersion; + } + if (options.tokenLifetime) { + this.tokenLifetime = options.tokenLifetime + } // the param names are shared between the authenticator and the token // manager so we can just pass along the options object. @@ -97,6 +111,18 @@ export class VpcInstanceAuthenticator extends TokenRequestBasedAuthenticator { this.tokenManager.setIamProfileId(iamProfileId); } + public setServiceVersion(serviceVersion: string): void { + this.serviceVersion = serviceVersion; + + this.tokenManager.setServiceVersion(serviceVersion); + } + + public setTokenLifetime(tokenLifetime: number): void { + this.tokenLifetime = tokenLifetime; + + this.tokenManager.setTokenLifetime(tokenLifetime); + } + /** * Returns the authenticator's type ('vpc'). * diff --git a/auth/token-managers/vpc-instance-token-manager.ts b/auth/token-managers/vpc-instance-token-manager.ts index f435eac80..bdaaaa81c 100644 --- a/auth/token-managers/vpc-instance-token-manager.ts +++ b/auth/token-managers/vpc-instance-token-manager.ts @@ -22,6 +22,7 @@ import { JwtTokenManager, JwtTokenManagerOptions } from './jwt-token-manager'; const DEFAULT_IMS_ENDPOINT = 'http://169.254.169.254'; const METADATA_SERVICE_VERSION = '2022-03-01'; const IAM_EXPIRATION_WINDOW = 10; +const METADATA_TOKEN_LIFETIME = 300; /** Configuration options for VPC token retrieval. */ interface Options extends JwtTokenManagerOptions { @@ -29,6 +30,10 @@ interface Options extends JwtTokenManagerOptions { iamProfileCrn?: string; /** The ID of the linked trusted IAM profile to be used when obtaining the IAM access token */ iamProfileId?: string; + + serviceVersion: string; + + tokenLifetime: number; } // this interface is a representation of the response received from @@ -57,6 +62,10 @@ export class VpcInstanceTokenManager extends JwtTokenManager { private iamProfileId: string; + private serviceVersion: string; + + private tokenLifetime: number; + /** * Create a new VpcInstanceTokenManager instance. * @@ -81,6 +90,8 @@ export class VpcInstanceTokenManager extends JwtTokenManager { } this.url = options.url || DEFAULT_IMS_ENDPOINT; + this.serviceVersion = options.serviceVersion || METADATA_SERVICE_VERSION + this.tokenLifetime = options.tokenLifetime || METADATA_TOKEN_LIFETIME if (options.iamProfileCrn) { this.iamProfileCrn = options.iamProfileCrn; @@ -108,6 +119,28 @@ export class VpcInstanceTokenManager extends JwtTokenManager { this.iamProfileId = iamProfileId; } + public setServiceVersion(serviceVersion: string): void { + this.serviceVersion = serviceVersion; + } + + public setTokenLifetime(tokenLifetime: number): void { + this.tokenLifetime = tokenLifetime; + } + + protected getAccessTokenPath(): string { + if ((this.serviceVersion) === '2025-08-26') { + return '/identity/v1/token'; + } + return '/instance_identity/v1/token'; + } + + protected getIamTokenPath(): string { + if ((this.serviceVersion) === '2025-08-26') { + return '/identity/v1/iam_tokens'; + } + return '/instance_identity/v1/iam_token'; + } + protected async requestToken(): Promise { const instanceIdentityToken: string = await this.getInstanceIdentityToken(); @@ -125,9 +158,9 @@ export class VpcInstanceTokenManager extends JwtTokenManager { const parameters = { options: { - url: `${this.url}/instance_identity/v1/iam_token`, + url: `${this.url}${this.getIamTokenPath()}`, qs: { - version: METADATA_SERVICE_VERSION, + version: this.serviceVersion, }, body, method: 'POST', @@ -150,12 +183,12 @@ export class VpcInstanceTokenManager extends JwtTokenManager { private async getInstanceIdentityToken(): Promise { const parameters = { options: { - url: `${this.url}/instance_identity/v1/token`, + url: `${this.url}${this.getAccessTokenPath()}`, qs: { - version: METADATA_SERVICE_VERSION, + version: this.serviceVersion, }, body: { - expires_in: 300, + expires_in: this.tokenLifetime, }, method: 'PUT', headers: { diff --git a/etc/ibm-cloud-sdk-core.api.md b/etc/ibm-cloud-sdk-core.api.md index 62d1c9536..995bcf7ae 100644 --- a/etc/ibm-cloud-sdk-core.api.md +++ b/etc/ibm-cloud-sdk-core.api.md @@ -510,6 +510,10 @@ export class VpcInstanceAuthenticator extends TokenRequestBasedAuthenticator { setIamProfileCrn(iamProfileCrn: string): void; setIamProfileId(iamProfileId: string): void; // (undocumented) + setServiceVersion(serviceVersion: string): void; + // (undocumented) + setTokenLifetime(tokenLifetime: number): void; + // (undocumented) protected tokenManager: VpcInstanceTokenManager; } @@ -517,11 +521,19 @@ export class VpcInstanceAuthenticator extends TokenRequestBasedAuthenticator { export class VpcInstanceTokenManager extends JwtTokenManager { // Warning: (ae-forgotten-export) The symbol "Options_9" needs to be exported by the entry point index.d.ts constructor(options: Options_9); + // (undocumented) + protected getAccessTokenPath(): string; + // (undocumented) + protected getIamTokenPath(): string; protected isTokenExpired(): boolean; // (undocumented) protected requestToken(): Promise; setIamProfileCrn(iamProfileCrn: string): void; setIamProfileId(iamProfileId: string): void; + // (undocumented) + setServiceVersion(serviceVersion: string): void; + // (undocumented) + setTokenLifetime(tokenLifetime: number): void; } // (No @packageDocumentation comment for this package) diff --git a/test/unit/vpc-instance-authenticator.test.js b/test/unit/vpc-instance-authenticator.test.js index cfce4400f..74f57edb3 100644 --- a/test/unit/vpc-instance-authenticator.test.js +++ b/test/unit/vpc-instance-authenticator.test.js @@ -76,6 +76,99 @@ describe('VPC Instance Authenticator', () => { expect(authenticator.tokenManager.iamProfileId).toEqual(config.iamProfileId); }); + it('should store serviceVersion and tokenLifetime when provided in config', () => { + const authenticator = new VpcInstanceAuthenticator({ + serviceVersion: '2025-05-26', + tokenLifetime: 600, + }); + + expect(authenticator.serviceVersion).toBe('2025-05-26'); + expect(authenticator.tokenManager.serviceVersion).toBe('2025-05-26'); + expect(authenticator.tokenLifetime).toBe(600); + expect(authenticator.tokenManager.tokenLifetime).toBe(600); + }); + + it('should use default serviceVersion and tokenLifetime when not provided', () => { + const authenticator = new VpcInstanceAuthenticator(); + + expect(authenticator.tokenManager.serviceVersion).toBe('2022-03-01'); + expect(authenticator.tokenManager.tokenLifetime).toBe(300); + }); + + it('should set serviceVersion using the setter even when not declared in constructor', () => { + const authenticator = new VpcInstanceAuthenticator(); + + // Initially should be undefined on authenticator (but token manager has default) + expect(authenticator.serviceVersion).toBeUndefined(); + expect(authenticator.tokenManager.serviceVersion).toBe('2022-03-01'); + + authenticator.setServiceVersion('2025-05-26'); + expect(authenticator.serviceVersion).toBe('2025-05-26'); + + // also, verify that the underlying token manager has been updated + expect(authenticator.tokenManager.serviceVersion).toBe('2025-05-26'); + }); + + it('should set tokenLifetime using the setter even when not declared in constructor', () => { + const authenticator = new VpcInstanceAuthenticator(); + + // Initially should be undefined on authenticator (but token manager has default) + expect(authenticator.tokenLifetime).toBeUndefined(); + expect(authenticator.tokenManager.tokenLifetime).toBe(300); + + authenticator.setTokenLifetime(900); + expect(authenticator.tokenLifetime).toBe(900); + + // also, verify that the underlying token manager has been updated + expect(authenticator.tokenManager.tokenLifetime).toBe(900); + }); + + it('should re-set serviceVersion using the setter when already set in constructor', () => { + const authenticator = new VpcInstanceAuthenticator({ + serviceVersion: '2022-03-01', + }); + + expect(authenticator.serviceVersion).toBe('2022-03-01'); + expect(authenticator.tokenManager.serviceVersion).toBe('2022-03-01'); + + authenticator.setServiceVersion('2025-05-26'); + expect(authenticator.serviceVersion).toBe('2025-05-26'); + + // also, verify that the underlying token manager has been updated + expect(authenticator.tokenManager.serviceVersion).toBe('2025-05-26'); + }); + + it('should re-set tokenLifetime using the setter when already set in constructor', () => { + const authenticator = new VpcInstanceAuthenticator({ + tokenLifetime: 300, + }); + + expect(authenticator.tokenLifetime).toBe(300); + expect(authenticator.tokenManager.tokenLifetime).toBe(300); + + authenticator.setTokenLifetime(900); + expect(authenticator.tokenLifetime).toBe(900); + + // also, verify that the underlying token manager has been updated + expect(authenticator.tokenManager.tokenLifetime).toBe(900); + }); + + it('should pass all config options to token manager', () => { + const fullConfig = { + iamProfileId: 'some-id', + url: 'someurl.com', + serviceVersion: '2025-05-26', + tokenLifetime: 600, + }; + + const authenticator = new VpcInstanceAuthenticator(fullConfig); + + expect(authenticator.tokenManager.iamProfileId).toBe(fullConfig.iamProfileId); + expect(authenticator.tokenManager.url).toBe(fullConfig.url); + expect(authenticator.tokenManager.serviceVersion).toBe(fullConfig.serviceVersion); + expect(authenticator.tokenManager.tokenLifetime).toBe(fullConfig.tokenLifetime); + }); + // "end to end" style test, to make sure this authenticator integrates properly with parent classes it('should update the options and resolve with `null` when `authenticate` is called', async () => { const authenticator = new VpcInstanceAuthenticator({ iamProfileCrn: config.iamProfileCrn }); diff --git a/test/unit/vpc-instance-token-manager.test.js b/test/unit/vpc-instance-token-manager.test.js index 4b3cd64d0..9d5ad5ffc 100644 --- a/test/unit/vpc-instance-token-manager.test.js +++ b/test/unit/vpc-instance-token-manager.test.js @@ -231,7 +231,82 @@ describe('VPC Instance Token Manager', () => { /^ibm-node-sdk-core\/vpc-instance-authenticator.*$/ ); }); + + it('should use default service version and token lifetime', () => { + const instance = new VpcInstanceTokenManager(); + + // Test default service version + expect(instance.serviceVersion).toBe('2022-03-01'); + + // Test default token lifetime + expect(instance.tokenLifetime).toBe(300); + + // Test default paths for old service version + expect(instance.getAccessTokenPath()).toBe('/instance_identity/v1/token'); + expect(instance.getIamTokenPath()).toBe('/instance_identity/v1/iam_token'); + }); + + it('should use custom service version and token lifetime', () => { + const instance = new VpcInstanceTokenManager({ + serviceVersion: '2025-08-26', + tokenLifetime: 600, + }); + + // Test custom service version + expect(instance.serviceVersion).toBe('2025-08-26'); + + // Test custom token lifetime + expect(instance.tokenLifetime).toBe(600); + + // Test new paths for new service version + expect(instance.getAccessTokenPath()).toBe('/identity/v1/token'); + expect(instance.getIamTokenPath()).toBe('/identity/v1/iam_tokens'); + }); + + it('should set service version and token lifetime with setters', () => { + const instance = new VpcInstanceTokenManager(); + + instance.setServiceVersion('2025-08-26'); + instance.setTokenLifetime(600); + + // Test service version from setter + expect(instance.serviceVersion).toBe('2025-08-26'); + + // Test token lifetime from setter + expect(instance.tokenLifetime).toBe(600); + + // Test new paths for new service version + expect(instance.getAccessTokenPath()).toBe('/identity/v1/token'); + expect(instance.getIamTokenPath()).toBe('/identity/v1/iam_tokens'); + }); + + it('should use old paths for old service version', () => { + const instance = new VpcInstanceTokenManager({ + serviceVersion: '2022-03-01', + }); + + // Test old service version + expect(instance.serviceVersion).toBe('2022-03-01'); + + // Test old paths for old service version + expect(instance.getAccessTokenPath()).toBe('/instance_identity/v1/token'); + expect(instance.getIamTokenPath()).toBe('/instance_identity/v1/iam_token'); + }); + + it('should use old paths for non-2025-05-26 service version', () => { + const instance = new VpcInstanceTokenManager({ + serviceVersion: '2024-01-01', + }); + + // Test custom service version (not 2025-05-26) + expect(instance.serviceVersion).toBe('2024-01-01'); + + // Test old paths for non-2025-05-26 service version + expect(instance.getAccessTokenPath()).toBe('/instance_identity/v1/token'); + expect(instance.getIamTokenPath()).toBe('/instance_identity/v1/iam_token'); + }); }); + describe('getToken', () => { it('should refresh an expired access token', async () => { const instance = new VpcInstanceTokenManager({ iamProfileId: 'some-id' }); From 2c93bfd36315a5a9b824e841f49b46f65b3f3cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADdia=20Tarcza?= <100163235+diatrcz@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:04:43 +0200 Subject: [PATCH 2/9] chore: fix lint errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lídia Tarcza <100163235+diatrcz@users.noreply.github.com> --- .../vpc-instance-authenticator.ts | 4 +-- .../vpc-instance-token-manager.ts | 8 ++--- test/unit/vpc-instance-token-manager.test.js | 30 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/auth/authenticators/vpc-instance-authenticator.ts b/auth/authenticators/vpc-instance-authenticator.ts index 99ceb746e..b1e5e77a1 100644 --- a/auth/authenticators/vpc-instance-authenticator.ts +++ b/auth/authenticators/vpc-instance-authenticator.ts @@ -24,7 +24,7 @@ export interface Options extends BaseOptions { iamProfileCrn?: string; /** The ID of the linked trusted IAM profile to be used when obtaining the IAM access token */ iamProfileId?: string; - + serviceVersion: string; tokenLifetime: number; @@ -80,7 +80,7 @@ export class VpcInstanceAuthenticator extends TokenRequestBasedAuthenticator { this.serviceVersion = options.serviceVersion; } if (options.tokenLifetime) { - this.tokenLifetime = options.tokenLifetime + this.tokenLifetime = options.tokenLifetime; } // the param names are shared between the authenticator and the token diff --git a/auth/token-managers/vpc-instance-token-manager.ts b/auth/token-managers/vpc-instance-token-manager.ts index bdaaaa81c..621382a30 100644 --- a/auth/token-managers/vpc-instance-token-manager.ts +++ b/auth/token-managers/vpc-instance-token-manager.ts @@ -90,8 +90,8 @@ export class VpcInstanceTokenManager extends JwtTokenManager { } this.url = options.url || DEFAULT_IMS_ENDPOINT; - this.serviceVersion = options.serviceVersion || METADATA_SERVICE_VERSION - this.tokenLifetime = options.tokenLifetime || METADATA_TOKEN_LIFETIME + this.serviceVersion = options.serviceVersion || METADATA_SERVICE_VERSION; + this.tokenLifetime = options.tokenLifetime || METADATA_TOKEN_LIFETIME; if (options.iamProfileCrn) { this.iamProfileCrn = options.iamProfileCrn; @@ -128,14 +128,14 @@ export class VpcInstanceTokenManager extends JwtTokenManager { } protected getAccessTokenPath(): string { - if ((this.serviceVersion) === '2025-08-26') { + if (this.serviceVersion === '2025-08-26') { return '/identity/v1/token'; } return '/instance_identity/v1/token'; } protected getIamTokenPath(): string { - if ((this.serviceVersion) === '2025-08-26') { + if (this.serviceVersion === '2025-08-26') { return '/identity/v1/iam_tokens'; } return '/instance_identity/v1/iam_token'; diff --git a/test/unit/vpc-instance-token-manager.test.js b/test/unit/vpc-instance-token-manager.test.js index 9d5ad5ffc..fc52148d8 100644 --- a/test/unit/vpc-instance-token-manager.test.js +++ b/test/unit/vpc-instance-token-manager.test.js @@ -231,16 +231,16 @@ describe('VPC Instance Token Manager', () => { /^ibm-node-sdk-core\/vpc-instance-authenticator.*$/ ); }); - + it('should use default service version and token lifetime', () => { const instance = new VpcInstanceTokenManager(); - + // Test default service version expect(instance.serviceVersion).toBe('2022-03-01'); - + // Test default token lifetime expect(instance.tokenLifetime).toBe(300); - + // Test default paths for old service version expect(instance.getAccessTokenPath()).toBe('/instance_identity/v1/token'); expect(instance.getIamTokenPath()).toBe('/instance_identity/v1/iam_token'); @@ -251,13 +251,13 @@ describe('VPC Instance Token Manager', () => { serviceVersion: '2025-08-26', tokenLifetime: 600, }); - + // Test custom service version expect(instance.serviceVersion).toBe('2025-08-26'); - + // Test custom token lifetime expect(instance.tokenLifetime).toBe(600); - + // Test new paths for new service version expect(instance.getAccessTokenPath()).toBe('/identity/v1/token'); expect(instance.getIamTokenPath()).toBe('/identity/v1/iam_tokens'); @@ -265,16 +265,16 @@ describe('VPC Instance Token Manager', () => { it('should set service version and token lifetime with setters', () => { const instance = new VpcInstanceTokenManager(); - + instance.setServiceVersion('2025-08-26'); instance.setTokenLifetime(600); - + // Test service version from setter expect(instance.serviceVersion).toBe('2025-08-26'); - + // Test token lifetime from setter expect(instance.tokenLifetime).toBe(600); - + // Test new paths for new service version expect(instance.getAccessTokenPath()).toBe('/identity/v1/token'); expect(instance.getIamTokenPath()).toBe('/identity/v1/iam_tokens'); @@ -284,10 +284,10 @@ describe('VPC Instance Token Manager', () => { const instance = new VpcInstanceTokenManager({ serviceVersion: '2022-03-01', }); - + // Test old service version expect(instance.serviceVersion).toBe('2022-03-01'); - + // Test old paths for old service version expect(instance.getAccessTokenPath()).toBe('/instance_identity/v1/token'); expect(instance.getIamTokenPath()).toBe('/instance_identity/v1/iam_token'); @@ -297,10 +297,10 @@ describe('VPC Instance Token Manager', () => { const instance = new VpcInstanceTokenManager({ serviceVersion: '2024-01-01', }); - + // Test custom service version (not 2025-05-26) expect(instance.serviceVersion).toBe('2024-01-01'); - + // Test old paths for non-2025-05-26 service version expect(instance.getAccessTokenPath()).toBe('/instance_identity/v1/token'); expect(instance.getIamTokenPath()).toBe('/instance_identity/v1/iam_token'); From b4317bb5738413605dc7c499a1f228b82b85978d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADdia=20Tarcza?= <100163235+diatrcz@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:38:15 +0200 Subject: [PATCH 3/9] fix: validate service version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lídia Tarcza <100163235+diatrcz@users.noreply.github.com> --- .../vpc-instance-token-manager.ts | 8 ++++ test/unit/vpc-instance-authenticator.test.js | 38 +++++++++++++++---- test/unit/vpc-instance-token-manager.test.js | 18 ++++----- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/auth/token-managers/vpc-instance-token-manager.ts b/auth/token-managers/vpc-instance-token-manager.ts index 621382a30..0f215c1b7 100644 --- a/auth/token-managers/vpc-instance-token-manager.ts +++ b/auth/token-managers/vpc-instance-token-manager.ts @@ -24,6 +24,8 @@ const METADATA_SERVICE_VERSION = '2022-03-01'; const IAM_EXPIRATION_WINDOW = 10; const METADATA_TOKEN_LIFETIME = 300; +const metadataServiceSupportedVersions = ['2022-03-01', '2025-08-26']; + /** Configuration options for VPC token retrieval. */ interface Options extends JwtTokenManagerOptions { /** The CRN of the linked trusted IAM profile to be used as the identity of the compute resource */ @@ -93,6 +95,12 @@ export class VpcInstanceTokenManager extends JwtTokenManager { this.serviceVersion = options.serviceVersion || METADATA_SERVICE_VERSION; this.tokenLifetime = options.tokenLifetime || METADATA_TOKEN_LIFETIME; + if (!metadataServiceSupportedVersions.includes(this.serviceVersion)) { + throw new Error( + `Invalid serviceVersion. Must be one of: ${metadataServiceSupportedVersions.join(', ')}` + ); + } + if (options.iamProfileCrn) { this.iamProfileCrn = options.iamProfileCrn; } diff --git a/test/unit/vpc-instance-authenticator.test.js b/test/unit/vpc-instance-authenticator.test.js index 74f57edb3..7ca554013 100644 --- a/test/unit/vpc-instance-authenticator.test.js +++ b/test/unit/vpc-instance-authenticator.test.js @@ -78,12 +78,12 @@ describe('VPC Instance Authenticator', () => { it('should store serviceVersion and tokenLifetime when provided in config', () => { const authenticator = new VpcInstanceAuthenticator({ - serviceVersion: '2025-05-26', + serviceVersion: '2025-08-26', tokenLifetime: 600, }); - expect(authenticator.serviceVersion).toBe('2025-05-26'); - expect(authenticator.tokenManager.serviceVersion).toBe('2025-05-26'); + expect(authenticator.serviceVersion).toBe('2025-08-26'); + expect(authenticator.tokenManager.serviceVersion).toBe('2025-08-26'); expect(authenticator.tokenLifetime).toBe(600); expect(authenticator.tokenManager.tokenLifetime).toBe(600); }); @@ -102,11 +102,11 @@ describe('VPC Instance Authenticator', () => { expect(authenticator.serviceVersion).toBeUndefined(); expect(authenticator.tokenManager.serviceVersion).toBe('2022-03-01'); - authenticator.setServiceVersion('2025-05-26'); - expect(authenticator.serviceVersion).toBe('2025-05-26'); + authenticator.setServiceVersion('2025-08-26'); + expect(authenticator.serviceVersion).toBe('2025-08-26'); // also, verify that the underlying token manager has been updated - expect(authenticator.tokenManager.serviceVersion).toBe('2025-05-26'); + expect(authenticator.tokenManager.serviceVersion).toBe('2025-08-26'); }); it('should set tokenLifetime using the setter even when not declared in constructor', () => { @@ -157,7 +157,7 @@ describe('VPC Instance Authenticator', () => { const fullConfig = { iamProfileId: 'some-id', url: 'someurl.com', - serviceVersion: '2025-05-26', + serviceVersion: '2025-08-26', tokenLifetime: 600, }; @@ -169,6 +169,30 @@ describe('VPC Instance Authenticator', () => { expect(authenticator.tokenManager.tokenLifetime).toBe(fullConfig.tokenLifetime); }); + it('should accept serviceVersion from environment variables (via constructor)', () => { + // This simulates how environment variables are passed to the authenticator + // via getAuthenticatorFromEnvironment -> readExternalSources + const envConfig = { + iamProfileId: 'some-id', + serviceVersion: '2025-08-26', // This would come from SERVICE_NAME_SERVICE_VERSION env var + }; + + const authenticator = new VpcInstanceAuthenticator(envConfig); + + expect(authenticator.serviceVersion).toBe('2025-08-26'); + expect(authenticator.tokenManager.serviceVersion).toBe('2025-08-26'); + expect(authenticator.iamProfileId).toBe('some-id'); + }); + + it('should throw an error for invalid service version', () => { + expect( + () => + new VpcInstanceTokenManager({ + serviceVersion: '2024-01-01', + }) + ).toThrow('Invalid serviceVersion. Must be one of: 2022-03-01, 2025-08-26'); + }); + // "end to end" style test, to make sure this authenticator integrates properly with parent classes it('should update the options and resolve with `null` when `authenticate` is called', async () => { const authenticator = new VpcInstanceAuthenticator({ iamProfileCrn: config.iamProfileCrn }); diff --git a/test/unit/vpc-instance-token-manager.test.js b/test/unit/vpc-instance-token-manager.test.js index fc52148d8..c077b0f28 100644 --- a/test/unit/vpc-instance-token-manager.test.js +++ b/test/unit/vpc-instance-token-manager.test.js @@ -293,17 +293,13 @@ describe('VPC Instance Token Manager', () => { expect(instance.getIamTokenPath()).toBe('/instance_identity/v1/iam_token'); }); - it('should use old paths for non-2025-05-26 service version', () => { - const instance = new VpcInstanceTokenManager({ - serviceVersion: '2024-01-01', - }); - - // Test custom service version (not 2025-05-26) - expect(instance.serviceVersion).toBe('2024-01-01'); - - // Test old paths for non-2025-05-26 service version - expect(instance.getAccessTokenPath()).toBe('/instance_identity/v1/token'); - expect(instance.getIamTokenPath()).toBe('/instance_identity/v1/iam_token'); + it('should throw an error for invalid service version', () => { + expect( + () => + new VpcInstanceTokenManager({ + serviceVersion: '2024-01-01', + }) + ).toThrow('Invalid serviceVersion. Must be one of: 2022-03-01, 2025-08-26'); }); }); From b0f23baebf8c9e76b15e0122e2f3b978d2684d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADdia=20Tarcza?= <100163235+diatrcz@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:51:39 +0200 Subject: [PATCH 4/9] refactor: use variables instead of literals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lídia Tarcza <100163235+diatrcz@users.noreply.github.com> --- .../vpc-instance-token-manager.ts | 18 +++-- test/unit/vpc-instance-authenticator.test.js | 70 ++++++++----------- test/unit/vpc-instance-token-manager.test.js | 43 +++++++----- 3 files changed, 71 insertions(+), 60 deletions(-) diff --git a/auth/token-managers/vpc-instance-token-manager.ts b/auth/token-managers/vpc-instance-token-manager.ts index 0f215c1b7..8ca69e38c 100644 --- a/auth/token-managers/vpc-instance-token-manager.ts +++ b/auth/token-managers/vpc-instance-token-manager.ts @@ -23,7 +23,10 @@ const DEFAULT_IMS_ENDPOINT = 'http://169.254.169.254'; const METADATA_SERVICE_VERSION = '2022-03-01'; const IAM_EXPIRATION_WINDOW = 10; const METADATA_TOKEN_LIFETIME = 300; - +const DEFAULT_OPERATION_PATH_CREATE_ACCESS_TOKEN = '/instance_identity/v1/token'; +const DEFAULT_OPERATION_PATH_CREATE_IAM_TOKEN = '/instance_identity/v1/iam_token'; +const DEFAULT_OPERATION_PATH_CREATE_ACCESS_TOKEN2 = '/identity/v1/token'; +const DEFAULT_OPERATION_PATH_CREATE_IAM_TOKEN2 = '/identity/v1/iam_tokens'; const metadataServiceSupportedVersions = ['2022-03-01', '2025-08-26']; /** Configuration options for VPC token retrieval. */ @@ -128,6 +131,11 @@ export class VpcInstanceTokenManager extends JwtTokenManager { } public setServiceVersion(serviceVersion: string): void { + if (!metadataServiceSupportedVersions.includes(serviceVersion)) { + throw new Error( + `Invalid serviceVersion. Must be one of: ${metadataServiceSupportedVersions.join(', ')}` + ); + } this.serviceVersion = serviceVersion; } @@ -137,16 +145,16 @@ export class VpcInstanceTokenManager extends JwtTokenManager { protected getAccessTokenPath(): string { if (this.serviceVersion === '2025-08-26') { - return '/identity/v1/token'; + return DEFAULT_OPERATION_PATH_CREATE_ACCESS_TOKEN2; } - return '/instance_identity/v1/token'; + return DEFAULT_OPERATION_PATH_CREATE_ACCESS_TOKEN; } protected getIamTokenPath(): string { if (this.serviceVersion === '2025-08-26') { - return '/identity/v1/iam_tokens'; + return DEFAULT_OPERATION_PATH_CREATE_IAM_TOKEN2; } - return '/instance_identity/v1/iam_token'; + return DEFAULT_OPERATION_PATH_CREATE_IAM_TOKEN; } protected async requestToken(): Promise { diff --git a/test/unit/vpc-instance-authenticator.test.js b/test/unit/vpc-instance-authenticator.test.js index 7ca554013..f610f8645 100644 --- a/test/unit/vpc-instance-authenticator.test.js +++ b/test/unit/vpc-instance-authenticator.test.js @@ -17,6 +17,13 @@ const { Authenticator, VpcInstanceAuthenticator } = require('../../dist/auth'); const { VpcInstanceTokenManager } = require('../../dist/auth'); +// Constants for repeated values +const SERVICE_VERSION_2022 = '2022-03-01'; +const SERVICE_VERSION_2025 = '2025-08-26'; +const DEFAULT_TOKEN_LIFETIME = 300; +const CUSTOM_TOKEN_LIFETIME = 600; +const INVALID_SERVICE_VERSION_ERROR = `Invalid serviceVersion. Must be one of: ${SERVICE_VERSION_2022}, ${SERVICE_VERSION_2025}`; + // mock the `getToken` method in the token manager - dont make any rest calls const fakeToken = 'iam-acess-token'; const mockedTokenManager = new VpcInstanceTokenManager(); @@ -78,21 +85,21 @@ describe('VPC Instance Authenticator', () => { it('should store serviceVersion and tokenLifetime when provided in config', () => { const authenticator = new VpcInstanceAuthenticator({ - serviceVersion: '2025-08-26', - tokenLifetime: 600, + serviceVersion: SERVICE_VERSION_2025, + tokenLifetime: CUSTOM_TOKEN_LIFETIME, }); - expect(authenticator.serviceVersion).toBe('2025-08-26'); - expect(authenticator.tokenManager.serviceVersion).toBe('2025-08-26'); - expect(authenticator.tokenLifetime).toBe(600); - expect(authenticator.tokenManager.tokenLifetime).toBe(600); + expect(authenticator.serviceVersion).toBe(SERVICE_VERSION_2025); + expect(authenticator.tokenManager.serviceVersion).toBe(SERVICE_VERSION_2025); + expect(authenticator.tokenLifetime).toBe(CUSTOM_TOKEN_LIFETIME); + expect(authenticator.tokenManager.tokenLifetime).toBe(CUSTOM_TOKEN_LIFETIME); }); it('should use default serviceVersion and tokenLifetime when not provided', () => { const authenticator = new VpcInstanceAuthenticator(); - expect(authenticator.tokenManager.serviceVersion).toBe('2022-03-01'); - expect(authenticator.tokenManager.tokenLifetime).toBe(300); + expect(authenticator.tokenManager.serviceVersion).toBe(SERVICE_VERSION_2022); + expect(authenticator.tokenManager.tokenLifetime).toBe(DEFAULT_TOKEN_LIFETIME); }); it('should set serviceVersion using the setter even when not declared in constructor', () => { @@ -100,13 +107,13 @@ describe('VPC Instance Authenticator', () => { // Initially should be undefined on authenticator (but token manager has default) expect(authenticator.serviceVersion).toBeUndefined(); - expect(authenticator.tokenManager.serviceVersion).toBe('2022-03-01'); + expect(authenticator.tokenManager.serviceVersion).toBe(SERVICE_VERSION_2022); - authenticator.setServiceVersion('2025-08-26'); - expect(authenticator.serviceVersion).toBe('2025-08-26'); + authenticator.setServiceVersion(SERVICE_VERSION_2025); + expect(authenticator.serviceVersion).toBe(SERVICE_VERSION_2025); // also, verify that the underlying token manager has been updated - expect(authenticator.tokenManager.serviceVersion).toBe('2025-08-26'); + expect(authenticator.tokenManager.serviceVersion).toBe(SERVICE_VERSION_2025); }); it('should set tokenLifetime using the setter even when not declared in constructor', () => { @@ -114,7 +121,7 @@ describe('VPC Instance Authenticator', () => { // Initially should be undefined on authenticator (but token manager has default) expect(authenticator.tokenLifetime).toBeUndefined(); - expect(authenticator.tokenManager.tokenLifetime).toBe(300); + expect(authenticator.tokenManager.tokenLifetime).toBe(DEFAULT_TOKEN_LIFETIME); authenticator.setTokenLifetime(900); expect(authenticator.tokenLifetime).toBe(900); @@ -123,28 +130,13 @@ describe('VPC Instance Authenticator', () => { expect(authenticator.tokenManager.tokenLifetime).toBe(900); }); - it('should re-set serviceVersion using the setter when already set in constructor', () => { - const authenticator = new VpcInstanceAuthenticator({ - serviceVersion: '2022-03-01', - }); - - expect(authenticator.serviceVersion).toBe('2022-03-01'); - expect(authenticator.tokenManager.serviceVersion).toBe('2022-03-01'); - - authenticator.setServiceVersion('2025-05-26'); - expect(authenticator.serviceVersion).toBe('2025-05-26'); - - // also, verify that the underlying token manager has been updated - expect(authenticator.tokenManager.serviceVersion).toBe('2025-05-26'); - }); - it('should re-set tokenLifetime using the setter when already set in constructor', () => { const authenticator = new VpcInstanceAuthenticator({ - tokenLifetime: 300, + tokenLifetime: DEFAULT_TOKEN_LIFETIME, }); - expect(authenticator.tokenLifetime).toBe(300); - expect(authenticator.tokenManager.tokenLifetime).toBe(300); + expect(authenticator.tokenLifetime).toBe(DEFAULT_TOKEN_LIFETIME); + expect(authenticator.tokenManager.tokenLifetime).toBe(DEFAULT_TOKEN_LIFETIME); authenticator.setTokenLifetime(900); expect(authenticator.tokenLifetime).toBe(900); @@ -157,8 +149,8 @@ describe('VPC Instance Authenticator', () => { const fullConfig = { iamProfileId: 'some-id', url: 'someurl.com', - serviceVersion: '2025-08-26', - tokenLifetime: 600, + serviceVersion: SERVICE_VERSION_2025, + tokenLifetime: CUSTOM_TOKEN_LIFETIME, }; const authenticator = new VpcInstanceAuthenticator(fullConfig); @@ -174,23 +166,23 @@ describe('VPC Instance Authenticator', () => { // via getAuthenticatorFromEnvironment -> readExternalSources const envConfig = { iamProfileId: 'some-id', - serviceVersion: '2025-08-26', // This would come from SERVICE_NAME_SERVICE_VERSION env var + serviceVersion: SERVICE_VERSION_2025, // This would come from SERVICE_NAME_SERVICE_VERSION env var }; const authenticator = new VpcInstanceAuthenticator(envConfig); - expect(authenticator.serviceVersion).toBe('2025-08-26'); - expect(authenticator.tokenManager.serviceVersion).toBe('2025-08-26'); + expect(authenticator.serviceVersion).toBe(SERVICE_VERSION_2025); + expect(authenticator.tokenManager.serviceVersion).toBe(SERVICE_VERSION_2025); expect(authenticator.iamProfileId).toBe('some-id'); }); it('should throw an error for invalid service version', () => { expect( () => - new VpcInstanceTokenManager({ - serviceVersion: '2024-01-01', + new VpcInstanceAuthenticator({ + serviceVersion: 'invalid-version', }) - ).toThrow('Invalid serviceVersion. Must be one of: 2022-03-01, 2025-08-26'); + ).toThrow(INVALID_SERVICE_VERSION_ERROR); }); // "end to end" style test, to make sure this authenticator integrates properly with parent classes diff --git a/test/unit/vpc-instance-token-manager.test.js b/test/unit/vpc-instance-token-manager.test.js index c077b0f28..1c9bf42fd 100644 --- a/test/unit/vpc-instance-token-manager.test.js +++ b/test/unit/vpc-instance-token-manager.test.js @@ -37,6 +37,11 @@ const debugLogSpy = jest.spyOn(logger, 'debug').mockImplementation(() => {}); const IAM_PROFILE_CRN = 'some-crn'; const IAM_PROFILE_ID = 'some-id'; const EXPIRATION_WINDOW = 10; +const SERVICE_VERSION_2022 = '2022-03-01'; +const SERVICE_VERSION_2025 = '2025-08-26'; +const DEFAULT_TOKEN_LIFETIME = 300; +const CUSTOM_TOKEN_LIFETIME = 600; +const INVALID_SERVICE_VERSION_ERROR = `Invalid serviceVersion. Must be one of: ${SERVICE_VERSION_2022}, ${SERVICE_VERSION_2025}`; describe('VPC Instance Token Manager', () => { const sendRequestMock = jest.fn(); @@ -120,10 +125,10 @@ describe('VPC Instance Token Manager', () => { expect(requestOptions.method).toBe('PUT'); expect(requestOptions.qs).toBeDefined(); - expect(requestOptions.qs.version).toBe('2022-03-01'); + expect(requestOptions.qs.version).toBe(SERVICE_VERSION_2022); expect(requestOptions.body).toBeDefined(); - expect(requestOptions.body.expires_in).toBe(300); + expect(requestOptions.body.expires_in).toBe(DEFAULT_TOKEN_LIFETIME); expect(requestOptions.headers).toBeDefined(); expect(requestOptions.headers['Content-Type']).toBe('application/json'); @@ -182,7 +187,7 @@ describe('VPC Instance Token Manager', () => { expect(requestOptions.method).toBe('POST'); expect(requestOptions.qs).toBeDefined(); - expect(requestOptions.qs.version).toBe('2022-03-01'); + expect(requestOptions.qs.version).toBe(SERVICE_VERSION_2022); // if neither the profile id or crn is set, then the body should be undefined expect(requestOptions.body).toBeUndefined(); @@ -236,10 +241,10 @@ describe('VPC Instance Token Manager', () => { const instance = new VpcInstanceTokenManager(); // Test default service version - expect(instance.serviceVersion).toBe('2022-03-01'); + expect(instance.serviceVersion).toBe(SERVICE_VERSION_2022); // Test default token lifetime - expect(instance.tokenLifetime).toBe(300); + expect(instance.tokenLifetime).toBe(DEFAULT_TOKEN_LIFETIME); // Test default paths for old service version expect(instance.getAccessTokenPath()).toBe('/instance_identity/v1/token'); @@ -248,15 +253,15 @@ describe('VPC Instance Token Manager', () => { it('should use custom service version and token lifetime', () => { const instance = new VpcInstanceTokenManager({ - serviceVersion: '2025-08-26', - tokenLifetime: 600, + serviceVersion: SERVICE_VERSION_2025, + tokenLifetime: CUSTOM_TOKEN_LIFETIME, }); // Test custom service version - expect(instance.serviceVersion).toBe('2025-08-26'); + expect(instance.serviceVersion).toBe(SERVICE_VERSION_2025); // Test custom token lifetime - expect(instance.tokenLifetime).toBe(600); + expect(instance.tokenLifetime).toBe(CUSTOM_TOKEN_LIFETIME); // Test new paths for new service version expect(instance.getAccessTokenPath()).toBe('/identity/v1/token'); @@ -266,27 +271,33 @@ describe('VPC Instance Token Manager', () => { it('should set service version and token lifetime with setters', () => { const instance = new VpcInstanceTokenManager(); - instance.setServiceVersion('2025-08-26'); - instance.setTokenLifetime(600); + instance.setServiceVersion(SERVICE_VERSION_2025); + instance.setTokenLifetime(CUSTOM_TOKEN_LIFETIME); // Test service version from setter - expect(instance.serviceVersion).toBe('2025-08-26'); + expect(instance.serviceVersion).toBe(SERVICE_VERSION_2025); // Test token lifetime from setter - expect(instance.tokenLifetime).toBe(600); + expect(instance.tokenLifetime).toBe(CUSTOM_TOKEN_LIFETIME); // Test new paths for new service version expect(instance.getAccessTokenPath()).toBe('/identity/v1/token'); expect(instance.getIamTokenPath()).toBe('/identity/v1/iam_tokens'); }); + it('should throw an error when setting invalid service version with setter', () => { + const instance = new VpcInstanceTokenManager(); + + expect(() => instance.setServiceVersion('2024-01-01')).toThrow(INVALID_SERVICE_VERSION_ERROR); + }); + it('should use old paths for old service version', () => { const instance = new VpcInstanceTokenManager({ - serviceVersion: '2022-03-01', + serviceVersion: SERVICE_VERSION_2022, }); // Test old service version - expect(instance.serviceVersion).toBe('2022-03-01'); + expect(instance.serviceVersion).toBe(SERVICE_VERSION_2022); // Test old paths for old service version expect(instance.getAccessTokenPath()).toBe('/instance_identity/v1/token'); @@ -299,7 +310,7 @@ describe('VPC Instance Token Manager', () => { new VpcInstanceTokenManager({ serviceVersion: '2024-01-01', }) - ).toThrow('Invalid serviceVersion. Must be one of: 2022-03-01, 2025-08-26'); + ).toThrow(INVALID_SERVICE_VERSION_ERROR); }); }); From 75c162209b2e891005ca73920f99b30e39c8c58a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADdia=20Tarcza?= <100163235+diatrcz@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:56:57 +0200 Subject: [PATCH 5/9] docs: add to documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lídia Tarcza <100163235+diatrcz@users.noreply.github.com> --- Authentication.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Authentication.md b/Authentication.md index bc238ad93..164c373d9 100644 --- a/Authentication.md +++ b/Authentication.md @@ -533,6 +533,7 @@ External configuration: ``` export EXAMPLE_SERVICE_AUTH_TYPE=vpc export EXAMPLE_SERVICE_IAM_PROFILE_CRN=crn:iam-profile-123 +export EXAMPLE_SERVICE_VERSION=2025-08-26 ``` Application code: ```js From 3616e5522c7796aecd0cc706196f75ed5f15306e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADdia=20Tarcza?= <100163235+diatrcz@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:47:27 +0200 Subject: [PATCH 6/9] fix: add requested changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lídia Tarcza <100163235+diatrcz@users.noreply.github.com> --- Authentication.md | 41 ++++++++++++++++++- .../vpc-instance-authenticator.ts | 8 ++-- .../vpc-instance-token-manager.ts | 34 ++++++++++----- 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/Authentication.md b/Authentication.md index 164c373d9..c76785927 100644 --- a/Authentication.md +++ b/Authentication.md @@ -528,11 +528,50 @@ const service = new ExampleServiceV1(options); // 'service' can now be used to invoke operations. ``` +### Programming example with the new service version and custom token lifetime +```js +const { VpcInstanceAuthenticator } = require('ibm-cloud-sdk-core'); +const ExampleServiceV1 = require('/example-service/v1'); + +const authenticator = new VpcInstanceAuthenticator({ + iamProfileCrn: 'crn:iam-profile-123', + serviceVersion: '2025-08-26', + tokenLifetime: 600, +}); + +const options = { + authenticator, +}; + +const service = new ExampleServiceV1(options); + +// 'service' can now be used to invoke operations. +``` + ### Configuration example External configuration: ``` export EXAMPLE_SERVICE_AUTH_TYPE=vpc export EXAMPLE_SERVICE_IAM_PROFILE_CRN=crn:iam-profile-123 +``` +Application code: +```js +const ExampleServiceV1 = require('/example-service/v1'); + +const options = { + serviceName: 'example_service', +}; + +const service = ExampleServiceV1.newInstance(options); + +// 'service' can now be used to invoke operations. +``` + +### Configuration example with the new service version and custom token lifetime +External configuration: +``` +export EXAMPLE_SERVICE_AUTH_TYPE=vpc +export EXAMPLE_SERVICE_IAM_PROFILE_CRN=crn:iam-profile-123 export EXAMPLE_SERVICE_VERSION=2025-08-26 ``` Application code: @@ -541,7 +580,6 @@ const ExampleServiceV1 = require('/example-service/v1'); const options = { serviceName: 'example_service', - serviceVersion: '2025-08-26', tokenLifetime: 600, }; @@ -550,7 +588,6 @@ const service = ExampleServiceV1.newInstance(options); // 'service' can now be used to invoke operations. ``` - ## Cloud Pak for Data Authentication The `CloudPakForDataAuthenticator` will accept a user-supplied username value, along with either a password or apikey, and will diff --git a/auth/authenticators/vpc-instance-authenticator.ts b/auth/authenticators/vpc-instance-authenticator.ts index b1e5e77a1..4b8581eaa 100644 --- a/auth/authenticators/vpc-instance-authenticator.ts +++ b/auth/authenticators/vpc-instance-authenticator.ts @@ -24,10 +24,10 @@ export interface Options extends BaseOptions { iamProfileCrn?: string; /** The ID of the linked trusted IAM profile to be used when obtaining the IAM access token */ iamProfileId?: string; - - serviceVersion: string; - - tokenLifetime: number; + /** The version of the base service version to be used with the service */ + serviceVersion?: string; + /** The base token lifetime to use */ + tokenLifetime?: number; } /** diff --git a/auth/token-managers/vpc-instance-token-manager.ts b/auth/token-managers/vpc-instance-token-manager.ts index 8ca69e38c..9a4c7e1f7 100644 --- a/auth/token-managers/vpc-instance-token-manager.ts +++ b/auth/token-managers/vpc-instance-token-manager.ts @@ -21,13 +21,14 @@ import { JwtTokenManager, JwtTokenManagerOptions } from './jwt-token-manager'; const DEFAULT_IMS_ENDPOINT = 'http://169.254.169.254'; const METADATA_SERVICE_VERSION = '2022-03-01'; +const METADATA_SERVICE_VERSION2 = '2025-08-26'; const IAM_EXPIRATION_WINDOW = 10; const METADATA_TOKEN_LIFETIME = 300; const DEFAULT_OPERATION_PATH_CREATE_ACCESS_TOKEN = '/instance_identity/v1/token'; const DEFAULT_OPERATION_PATH_CREATE_IAM_TOKEN = '/instance_identity/v1/iam_token'; const DEFAULT_OPERATION_PATH_CREATE_ACCESS_TOKEN2 = '/identity/v1/token'; const DEFAULT_OPERATION_PATH_CREATE_IAM_TOKEN2 = '/identity/v1/iam_tokens'; -const metadataServiceSupportedVersions = ['2022-03-01', '2025-08-26']; +const metadataServiceSupportedVersions = [METADATA_SERVICE_VERSION, METADATA_SERVICE_VERSION2]; /** Configuration options for VPC token retrieval. */ interface Options extends JwtTokenManagerOptions { @@ -35,10 +36,10 @@ interface Options extends JwtTokenManagerOptions { iamProfileCrn?: string; /** The ID of the linked trusted IAM profile to be used when obtaining the IAM access token */ iamProfileId?: string; - - serviceVersion: string; - - tokenLifetime: number; + /** The version of the base service version to be used with the service */ + serviceVersion?: string; + /** The base token lifetime to use */ + tokenLifetime?: number; } // this interface is a representation of the response received from @@ -95,14 +96,22 @@ export class VpcInstanceTokenManager extends JwtTokenManager { } this.url = options.url || DEFAULT_IMS_ENDPOINT; - this.serviceVersion = options.serviceVersion || METADATA_SERVICE_VERSION; - this.tokenLifetime = options.tokenLifetime || METADATA_TOKEN_LIFETIME; - - if (!metadataServiceSupportedVersions.includes(this.serviceVersion)) { + + // Validate and set serviceVersion + const serviceVersion = options.serviceVersion || METADATA_SERVICE_VERSION; + if (!metadataServiceSupportedVersions.includes(serviceVersion)) { throw new Error( `Invalid serviceVersion. Must be one of: ${metadataServiceSupportedVersions.join(', ')}` ); } + this.serviceVersion = serviceVersion; + + // Validate and set tokenLifetime + const tokenLifetime = options.tokenLifetime || METADATA_TOKEN_LIFETIME; + if (typeof tokenLifetime !== 'number' || tokenLifetime < 0) { + throw new Error('tokenLifetime must be a non-negative number'); + } + this.tokenLifetime = tokenLifetime; if (options.iamProfileCrn) { this.iamProfileCrn = options.iamProfileCrn; @@ -140,18 +149,21 @@ export class VpcInstanceTokenManager extends JwtTokenManager { } public setTokenLifetime(tokenLifetime: number): void { + if (typeof tokenLifetime !== 'number' || tokenLifetime < 0) { + throw new Error('tokenLifetime must be a non-negative number'); + } this.tokenLifetime = tokenLifetime; } protected getAccessTokenPath(): string { - if (this.serviceVersion === '2025-08-26') { + if (this.serviceVersion === METADATA_SERVICE_VERSION2) { return DEFAULT_OPERATION_PATH_CREATE_ACCESS_TOKEN2; } return DEFAULT_OPERATION_PATH_CREATE_ACCESS_TOKEN; } protected getIamTokenPath(): string { - if (this.serviceVersion === '2025-08-26') { + if (this.serviceVersion === METADATA_SERVICE_VERSION2) { return DEFAULT_OPERATION_PATH_CREATE_IAM_TOKEN2; } return DEFAULT_OPERATION_PATH_CREATE_IAM_TOKEN; From 9e01b3ab4ca2516f7405c1cad4b31ca53746875b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=ADdia=20Tarcza?= <100163235+diatrcz@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:50:09 +0200 Subject: [PATCH 7/9] fix: add linter fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lídia Tarcza <100163235+diatrcz@users.noreply.github.com> --- auth/token-managers/vpc-instance-token-manager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth/token-managers/vpc-instance-token-manager.ts b/auth/token-managers/vpc-instance-token-manager.ts index 9a4c7e1f7..f8fe2f71c 100644 --- a/auth/token-managers/vpc-instance-token-manager.ts +++ b/auth/token-managers/vpc-instance-token-manager.ts @@ -96,7 +96,7 @@ export class VpcInstanceTokenManager extends JwtTokenManager { } this.url = options.url || DEFAULT_IMS_ENDPOINT; - + // Validate and set serviceVersion const serviceVersion = options.serviceVersion || METADATA_SERVICE_VERSION; if (!metadataServiceSupportedVersions.includes(serviceVersion)) { @@ -105,7 +105,7 @@ export class VpcInstanceTokenManager extends JwtTokenManager { ); } this.serviceVersion = serviceVersion; - + // Validate and set tokenLifetime const tokenLifetime = options.tokenLifetime || METADATA_TOKEN_LIFETIME; if (typeof tokenLifetime !== 'number' || tokenLifetime < 0) { From 58928caa87656b7a6d4150216f399e35148bb77d Mon Sep 17 00:00:00 2001 From: Norbert Biczo Date: Fri, 19 Jun 2026 21:00:15 +0200 Subject: [PATCH 8/9] chore: little update the to auth docs and to some comments Signed-off-by: Norbert Biczo --- Authentication.md | 32 +++---------------- .../vpc-instance-authenticator.ts | 4 +-- .../vpc-instance-token-manager.ts | 4 +-- 3 files changed, 8 insertions(+), 32 deletions(-) diff --git a/Authentication.md b/Authentication.md index c76785927..40cfc2f64 100644 --- a/Authentication.md +++ b/Authentication.md @@ -528,24 +528,13 @@ const service = new ExampleServiceV1(options); // 'service' can now be used to invoke operations. ``` -### Programming example with the new service version and custom token lifetime +To use the new service version with custom token lifetime: ```js -const { VpcInstanceAuthenticator } = require('ibm-cloud-sdk-core'); -const ExampleServiceV1 = require('/example-service/v1'); - const authenticator = new VpcInstanceAuthenticator({ iamProfileCrn: 'crn:iam-profile-123', serviceVersion: '2025-08-26', tokenLifetime: 600, }); - -const options = { - authenticator, -}; - -const service = new ExampleServiceV1(options); - -// 'service' can now be used to invoke operations. ``` ### Configuration example @@ -554,33 +543,20 @@ External configuration: export EXAMPLE_SERVICE_AUTH_TYPE=vpc export EXAMPLE_SERVICE_IAM_PROFILE_CRN=crn:iam-profile-123 ``` -Application code: -```js -const ExampleServiceV1 = require('/example-service/v1'); - -const options = { - serviceName: 'example_service', -}; - -const service = ExampleServiceV1.newInstance(options); - -// 'service' can now be used to invoke operations. -``` -### Configuration example with the new service version and custom token lifetime -External configuration: +To use the new service version: ``` export EXAMPLE_SERVICE_AUTH_TYPE=vpc export EXAMPLE_SERVICE_IAM_PROFILE_CRN=crn:iam-profile-123 -export EXAMPLE_SERVICE_VERSION=2025-08-26 +export EXAMPLE_SERVICE_VPC_IMS_VERSION=2025-08-26 ``` + Application code: ```js const ExampleServiceV1 = require('/example-service/v1'); const options = { serviceName: 'example_service', - tokenLifetime: 600, }; const service = ExampleServiceV1.newInstance(options); diff --git a/auth/authenticators/vpc-instance-authenticator.ts b/auth/authenticators/vpc-instance-authenticator.ts index 4b8581eaa..ca5d8f5f0 100644 --- a/auth/authenticators/vpc-instance-authenticator.ts +++ b/auth/authenticators/vpc-instance-authenticator.ts @@ -24,9 +24,9 @@ export interface Options extends BaseOptions { iamProfileCrn?: string; /** The ID of the linked trusted IAM profile to be used when obtaining the IAM access token */ iamProfileId?: string; - /** The version of the base service version to be used with the service */ + /** The version of the Instance Metadata Service to be used obtaining tokens */ serviceVersion?: string; - /** The base token lifetime to use */ + /** The lifetime of the Instance Identity Token */ tokenLifetime?: number; } diff --git a/auth/token-managers/vpc-instance-token-manager.ts b/auth/token-managers/vpc-instance-token-manager.ts index f8fe2f71c..201202885 100644 --- a/auth/token-managers/vpc-instance-token-manager.ts +++ b/auth/token-managers/vpc-instance-token-manager.ts @@ -36,9 +36,9 @@ interface Options extends JwtTokenManagerOptions { iamProfileCrn?: string; /** The ID of the linked trusted IAM profile to be used when obtaining the IAM access token */ iamProfileId?: string; - /** The version of the base service version to be used with the service */ + /** The version of the Instance Metadata Service to be used obtaining tokens */ serviceVersion?: string; - /** The base token lifetime to use */ + /** The lifetime of the Instance Identity Token */ tokenLifetime?: number; } From 9b939764adcc4a65433023498a9991b6406ebfb8 Mon Sep 17 00:00:00 2001 From: Norbert Biczo Date: Fri, 19 Jun 2026 21:04:45 +0200 Subject: [PATCH 9/9] fix: add missing header to IAM token retrieval Signed-off-by: Norbert Biczo --- auth/token-managers/vpc-instance-token-manager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/auth/token-managers/vpc-instance-token-manager.ts b/auth/token-managers/vpc-instance-token-manager.ts index 201202885..656847f3b 100644 --- a/auth/token-managers/vpc-instance-token-manager.ts +++ b/auth/token-managers/vpc-instance-token-manager.ts @@ -197,6 +197,7 @@ export class VpcInstanceTokenManager extends JwtTokenManager { 'User-Agent': this.userAgent, Accept: 'application/json', Authorization: `Bearer ${instanceIdentityToken}`, + 'Metadata-Flavor': 'ibm', }, }, };