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" 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,