From d73cfab8852d4578164c9f5997a3658c4a36c6a9 Mon Sep 17 00:00:00 2001 From: BitGo Agent Date: Mon, 3 Aug 2026 14:00:45 +0000 Subject: [PATCH] fix(sdk-api): guard v1 auth path against undefined accessToken in testnet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When BitGoAPI is constructed with accessToken: undefined (e.g. TESTNET_ACCESS_TOKEN env var not set) and a request sets forceV1Auth = true, the v1 auth condition previously evaluated as: (undefined && ...) || true → true causing the request to set Authorization: 'Bearer undefined', which the server rejects with an 'undefined access token' error. Fix: require this._token to be truthy before entering the v1 path, so forceV1Auth cannot bypass the token guard. Unauthenticated requests now proceed without an Authorization header (the existing behaviour for requests with no token) rather than sending a literally-undefined bearer value. Ticket: WCI-1213 Session-Id: 90ff7750-f83b-4600-a00a-fb07afa1cb4a Task-Id: a55a5860-e651-49e1-9ea1-0867be6b19d3 --- modules/sdk-api/src/bitgoAPI.ts | 2 +- modules/sdk-api/test/unit/bitgoAPI.ts | 42 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/modules/sdk-api/src/bitgoAPI.ts b/modules/sdk-api/src/bitgoAPI.ts index 126d5e5c31..9e6836e15b 100644 --- a/modules/sdk-api/src/bitgoAPI.ts +++ b/modules/sdk-api/src/bitgoAPI.ts @@ -645,7 +645,7 @@ export class BitGoAPI implements BitGoBase { req.isV2Authenticated = true; req.authenticationToken = this._token ?? (strategyAuthenticated ? 'strategy-authenticated' : undefined); // some of the older tokens appear to be only 40 characters long - if ((this._token && this._token.length !== 67 && this._token.indexOf('v2x') !== 0) || req.forceV1Auth) { + if (this._token && ((this._token.length !== 67 && this._token.indexOf('v2x') !== 0) || req.forceV1Auth)) { // use the old method req.isV2Authenticated = false; diff --git a/modules/sdk-api/test/unit/bitgoAPI.ts b/modules/sdk-api/test/unit/bitgoAPI.ts index 68c6a2f750..7ac8be056a 100644 --- a/modules/sdk-api/test/unit/bitgoAPI.ts +++ b/modules/sdk-api/test/unit/bitgoAPI.ts @@ -1192,3 +1192,45 @@ describe('wallets() v1 facade', function () { wallets.resendShareInvite.should.be.a.Function(); }); }); + +describe('undefined accessToken in testnet (WCI-1213)', function () { + const ROOT = 'https://app.bitgo-test.com'; + + afterEach(function () { + nock.cleanAll(); + }); + + it('does not send "Bearer undefined" when accessToken is undefined (no forceV1Auth)', async function () { + const bitgo = new BitGoAPI({ env: 'test', accessToken: undefined }); + + let receivedAuthHeader: string | null = null; + nock(ROOT) + .post('/api/auth/v1/session') + .reply(function (uri, body) { + receivedAuthHeader = this.req.headers['authorization']?.[0] ?? null; + return [200, { user: { username: 'test@example.com' }, access_token: 'v2xtoken' }]; + }); + + await bitgo.authenticate({ username: 'test@example.com', password: 'pw', otp: '000000' }); + + // With no token set, the v1 path must not be entered — so no 'Bearer undefined' + assert.notStrictEqual(receivedAuthHeader, 'Bearer undefined'); + }); + + it('does not send "Bearer undefined" when accessToken is undefined and forceV1Auth is set', async function () { + const bitgo = new BitGoAPI({ env: 'test', accessToken: undefined }); + + let receivedAuthHeader: string | null = null; + nock(ROOT) + .post('/api/auth/v1/session') + .reply(function (uri, body) { + receivedAuthHeader = this.req.headers['authorization']?.[0] ?? null; + return [200, { user: { username: 'test@example.com' }, access_token: 'v2xtoken' }]; + }); + + await bitgo.authenticate({ username: 'test@example.com', password: 'pw', otp: '000000', forceV1Auth: true }); + + // forceV1Auth must not bypass the token guard — header must not be 'Bearer undefined' + assert.notStrictEqual(receivedAuthHeader, 'Bearer undefined'); + }); +});