From c21556e7c4ef9772f721b3d0c482a17fb4e55a78 Mon Sep 17 00:00:00 2001 From: Anton Nikitiuk Date: Fri, 17 Jul 2026 17:44:03 +0300 Subject: [PATCH] Add CA bundle version and CA pinning status to the user-agent string --- src/client.ts | 8 +++----- src/constants.ts | 1 + test/client/client.test.ts | 28 ++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/client.ts b/src/client.ts index c53abd4..06dc69e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -69,10 +69,13 @@ export class Client { rejectUnauthorized: true, }); + const userAgent = `${constants.USER_AGENT} node/${process.versions.node} v8/${process.versions.v8} ca_bundle/${constants.CA_BUNDLE_VERSION} (ca_pinning=${this.enableCAPinning ? 'enabled' : 'disabled'})`; + this.axios = axios.create({ baseURL: this.baseURL, httpsAgent: agent, httpAgent: Error('HTTP disabled. Must use HTTPS'), + headers: { 'User-Agent': userAgent }, }); } @@ -311,11 +314,6 @@ export class Client { const { data } = await this.axios.post( this.TOKEN_ENDPOINT, new globalThis.URLSearchParams(request), - { - headers: { - 'user-agent': `${constants.USER_AGENT} node/${process.versions.node} v8/${process.versions.v8}`, - }, - }, ); /* Verify that we are receiving the expected response from Duo */ diff --git a/src/constants.ts b/src/constants.ts index ab0bf17..99f21ba 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -14,6 +14,7 @@ export const JTI_LENGTH = 36; export const JWT_EXPIRATION = 300; export const JWT_LEEWAY = 60; +export const CA_BUNDLE_VERSION = '1.0'; export const USER_AGENT = `duo_universal_node/${version}`; export const SIG_ALGORITHM = 'HS512'; export const GRANT_TYPE = 'authorization_code'; diff --git a/test/client/client.test.ts b/test/client/client.test.ts index 2680f91..127ba3b 100644 --- a/test/client/client.test.ts +++ b/test/client/client.test.ts @@ -86,6 +86,34 @@ describe('Client instance', () => { }); }); +describe('User Agent', () => { + let axiosCreateSpy: MockInstance; + + beforeAll(() => { + axiosCreateSpy = vi.spyOn(axios, 'create').mockReturnThis(); + }); + + it('User agent includes ca_bundle version and ca_pinning=enabled when pinning is on', () => { + new Client(clientOps); + + const config = axiosCreateSpy.mock.lastCall?.[0]; + const ua = config?.headers?.['User-Agent'] as string; + + expect(ua).toContain(`ca_bundle/${constants.CA_BUNDLE_VERSION}`); + expect(ua).toContain('(ca_pinning=enabled)'); + }); + + it('User agent includes ca_pinning=disabled when pinning is off', () => { + new Client({ ...clientOps, enableCAPinning: false }); + + const config = axiosCreateSpy.mock.lastCall?.[0]; + const ua = config?.headers?.['User-Agent'] as string; + + expect(ua).toContain(`ca_bundle/${constants.CA_BUNDLE_VERSION}`); + expect(ua).toContain('(ca_pinning=disabled)'); + }); +}); + describe('CA Pinning', () => { let agentSpy: MockInstance;