From 707b4f2fe1d67878bcd8f1434e4cbb57c951994e Mon Sep 17 00:00:00 2001 From: Patrick Menlove Date: Wed, 27 Aug 2025 17:29:44 +0200 Subject: [PATCH 1/2] fix: respect useAuthWithCustomEndpoint flag for resumable uploads (#2637) Fixes resumable uploads to respect the useAuthWithCustomEndpoint configuration flag when using custom API endpoints. Previously, resumable uploads would automatically bypass authentication for any non-googleapis.com domain, even when useAuthWithCustomEndpoint: true was explicitly set. This prevented using authenticated proxies or custom storage endpoints with resumable uploads. Changes: - Pass useAuthWithCustomEndpoint flag through to resumable upload configuration - Only bypass authentication if the flag is not explicitly set to true - Add useAuthWithCustomEndpoint property to Service class for proper access - Maintains backward compatibility with existing behavior Fixes #[issue-number] --- src/file.ts | 1 + src/nodejs-common/service.ts | 7 +++ src/resumable-upload.ts | 14 ++++-- test/resumable-upload.ts | 89 ++++++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 3 deletions(-) diff --git a/src/file.ts b/src/file.ts index 57f0645a1..0d18458bb 100644 --- a/src/file.ts +++ b/src/file.ts @@ -1835,6 +1835,7 @@ class File extends ServiceObject { retryOptions: retryOptions, params: options?.preconditionOpts || this.instancePreconditionOpts, universeDomain: this.bucket.storage.universeDomain, + useAuthWithCustomEndpoint: this.storage.useAuthWithCustomEndpoint, [GCCL_GCS_CMD_KEY]: options[GCCL_GCS_CMD_KEY], }, callback! diff --git a/src/nodejs-common/service.ts b/src/nodejs-common/service.ts index 8156cd176..9ade0478e 100644 --- a/src/nodejs-common/service.ts +++ b/src/nodejs-common/service.ts @@ -72,6 +72,11 @@ export interface ServiceConfig { * Set to true if the endpoint is a custom URL */ customEndpoint?: boolean; + + /** + * Controls whether or not to use authentication when using a custom endpoint. + */ + useAuthWithCustomEndpoint?: boolean; } export interface ServiceOptions extends Omit { @@ -98,6 +103,7 @@ export class Service { timeout?: number; universeDomain: string; customEndpoint: boolean; + useAuthWithCustomEndpoint?: boolean; /** * Service is a base class, meant to be inherited from by a "service," like @@ -128,6 +134,7 @@ export class Service { this.providedUserAgent = options.userAgent; this.universeDomain = options.universeDomain || DEFAULT_UNIVERSE; this.customEndpoint = config.customEndpoint || false; + this.useAuthWithCustomEndpoint = config.useAuthWithCustomEndpoint; this.makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory({ ...config, diff --git a/src/resumable-upload.ts b/src/resumable-upload.ts index 22e58df1b..e6a51fd14 100644 --- a/src/resumable-upload.ts +++ b/src/resumable-upload.ts @@ -243,6 +243,11 @@ export interface UploadConfig extends Pick { */ retryOptions: RetryOptions; + /** + * Controls whether or not to use authentication when using a custom endpoint. + */ + useAuthWithCustomEndpoint?: boolean; + [GCCL_GCS_CMD_KEY]?: string; } @@ -391,9 +396,12 @@ export class Upload extends Writable { !isSubDomainOfUniverse && !isSubDomainOfDefaultUniverse ) { - // a custom, non-universe domain, - // use gaxios - this.authClient = gaxios; + // Check if we should use auth with custom endpoint + if (cfg.useAuthWithCustomEndpoint !== true) { + // Only bypass auth if explicitly not requested + this.authClient = gaxios; + } + // Otherwise keep the authenticated client } } diff --git a/test/resumable-upload.ts b/test/resumable-upload.ts index 0080767e0..ab0973aba 100644 --- a/test/resumable-upload.ts +++ b/test/resumable-upload.ts @@ -1650,6 +1650,95 @@ describe('resumable-upload', () => { assert.deepStrictEqual(res.headers, {}); }); + it('should use authentication with custom endpoint when useAuthWithCustomEndpoint is true', async () => { + up = upload({ + bucket: BUCKET, + file: FILE, + customRequestOptions: CUSTOM_REQUEST_OPTIONS, + generation: GENERATION, + metadata: METADATA, + origin: ORIGIN, + params: PARAMS, + predefinedAcl: PREDEFINED_ACL, + userProject: USER_PROJECT, + authConfig: {keyFile}, + apiEndpoint: 'https://custom-proxy.example.com', + useAuthWithCustomEndpoint: true, + retryOptions: RETRY_OPTIONS, + }); + + // Mock the authorization request + mockAuthorizeRequest(); + + // Mock the actual request with auth header expectation + const scopes = [ + nock(REQ_OPTS.url!) + .matchHeader('authorization', /Bearer .+/) + .get(queryPath) + .reply(200, undefined, {}), + ]; + + const res = await up.makeRequest(REQ_OPTS); + scopes.forEach(x => x.done()); + assert.strictEqual(res.config.url, REQ_OPTS.url + queryPath.slice(1)); + // Headers should include authorization + assert.ok(res.config.headers?.['Authorization']); + }); + + it('should bypass authentication with custom endpoint when useAuthWithCustomEndpoint is false', async () => { + up = upload({ + bucket: BUCKET, + file: FILE, + customRequestOptions: CUSTOM_REQUEST_OPTIONS, + generation: GENERATION, + metadata: METADATA, + origin: ORIGIN, + params: PARAMS, + predefinedAcl: PREDEFINED_ACL, + userProject: USER_PROJECT, + authConfig: {keyFile}, + apiEndpoint: 'https://storage-emulator.local', + useAuthWithCustomEndpoint: false, + retryOptions: RETRY_OPTIONS, + }); + + const scopes = [ + nock(REQ_OPTS.url!).get(queryPath).reply(200, undefined, {}), + ]; + const res = await up.makeRequest(REQ_OPTS); + scopes.forEach(x => x.done()); + assert.strictEqual(res.config.url, REQ_OPTS.url + queryPath.slice(1)); + // When auth is bypassed, no auth headers should be present + assert.deepStrictEqual(res.headers, {}); + }); + + it('should bypass authentication with custom endpoint when useAuthWithCustomEndpoint is undefined (backward compatibility)', async () => { + up = upload({ + bucket: BUCKET, + file: FILE, + customRequestOptions: CUSTOM_REQUEST_OPTIONS, + generation: GENERATION, + metadata: METADATA, + origin: ORIGIN, + params: PARAMS, + predefinedAcl: PREDEFINED_ACL, + userProject: USER_PROJECT, + authConfig: {keyFile}, + apiEndpoint: 'https://storage-emulator.local', + // useAuthWithCustomEndpoint is intentionally not set + retryOptions: RETRY_OPTIONS, + }); + + const scopes = [ + nock(REQ_OPTS.url!).get(queryPath).reply(200, undefined, {}), + ]; + const res = await up.makeRequest(REQ_OPTS); + scopes.forEach(x => x.done()); + assert.strictEqual(res.config.url, REQ_OPTS.url + queryPath.slice(1)); + // When auth is bypassed (backward compatibility), no auth headers should be present + assert.deepStrictEqual(res.headers, {}); + }); + it('should combine customRequestOptions', done => { const up = upload({ bucket: BUCKET, From 3a2ade8343911e628d2e061fd04f596f3755e30d Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 11:48:29 -0400 Subject: [PATCH 2/2] chore(main): release 7.17.1 (#2638) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ package.json | 2 +- samples/package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 221b624bb..97e95c736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://www.npmjs.com/package/@google-cloud/storage?activeTab=versions +## [7.17.1](https://github.com/googleapis/nodejs-storage/compare/v7.17.0...v7.17.1) (2025-08-27) + + +### Bug Fixes + +* Respect useAuthWithCustomEndpoint flag for resumable uploads ([#2637](https://github.com/googleapis/nodejs-storage/issues/2637)) ([707b4f2](https://github.com/googleapis/nodejs-storage/commit/707b4f2fe1d67878bcd8f1434e4cbb57c951994e)) + ## [7.17.0](https://github.com/googleapis/nodejs-storage/compare/v7.16.0...v7.17.0) (2025-08-18) diff --git a/package.json b/package.json index 46dbf04d0..23434caf0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@google-cloud/storage", "description": "Cloud Storage Client Library for Node.js", - "version": "7.17.0", + "version": "7.17.1", "license": "Apache-2.0", "author": "Google Inc.", "engines": { diff --git a/samples/package.json b/samples/package.json index 401334cec..6b1c10d2e 100644 --- a/samples/package.json +++ b/samples/package.json @@ -17,7 +17,7 @@ }, "dependencies": { "@google-cloud/pubsub": "^4.0.0", - "@google-cloud/storage": "^7.17.0", + "@google-cloud/storage": "^7.17.1", "node-fetch": "^2.6.7", "uuid": "^8.0.0", "yargs": "^16.0.0"