Skip to content

Commit 6251400

Browse files
committed
send api key to DBE requests (#9331)
1 parent 81698f9 commit 6251400

File tree

12 files changed

+138
-16
lines changed

12 files changed

+138
-16
lines changed

packages/firestore/src/api/database.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ export function configureFirestore(firestore: Firestore): void {
302302
firestore._databaseId,
303303
firestore._app?.options.appId || '',
304304
firestore._persistenceKey,
305+
firestore._app?.options.apiKey,
305306
settings
306307
);
307308
if (!firestore._componentsProvider) {

packages/firestore/src/core/database_info.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ export class DatabaseInfo {
4949
readonly autoDetectLongPolling: boolean,
5050
readonly longPollingOptions: ExperimentalLongPollingOptions,
5151
readonly useFetchStreams: boolean,
52-
readonly isUsingEmulator: boolean
52+
readonly isUsingEmulator: boolean,
53+
readonly apiKey: string | undefined
5354
) {}
5455
}
5556

packages/firestore/src/core/firestore_client.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,11 @@ export class FirestoreClient {
146146
* an async I/O to complete).
147147
*/
148148
public asyncQueue: AsyncQueue,
149-
private databaseInfo: DatabaseInfo,
149+
/**
150+
* @internal
151+
* Exposed for testing
152+
*/
153+
public _databaseInfo: DatabaseInfo,
150154
componentProvider?: {
151155
_offline: OfflineComponentProvider;
152156
_online: OnlineComponentProvider;
@@ -167,7 +171,7 @@ export class FirestoreClient {
167171
get configuration(): ComponentConfiguration {
168172
return {
169173
asyncQueue: this.asyncQueue,
170-
databaseInfo: this.databaseInfo,
174+
databaseInfo: this._databaseInfo,
171175
clientId: this.clientId,
172176
authCredentials: this.authCredentials,
173177
appCheckCredentials: this.appCheckCredentials,

packages/firestore/src/lite-api/components.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export function getDatastore(firestore: FirestoreService): Datastore {
7575
firestore._databaseId,
7676
firestore.app.options.appId || '',
7777
firestore._persistenceKey,
78+
firestore.app.options.apiKey,
7879
firestore._freezeSettings()
7980
);
8081
const connection = newConnection(databaseInfo);
@@ -108,6 +109,7 @@ export function makeDatabaseInfo(
108109
databaseId: DatabaseId,
109110
appId: string,
110111
persistenceKey: string,
112+
apiKey: string | undefined,
111113
settings: FirestoreSettingsImpl
112114
): DatabaseInfo {
113115
return new DatabaseInfo(
@@ -120,6 +122,7 @@ export function makeDatabaseInfo(
120122
settings.experimentalAutoDetectLongPolling,
121123
cloneLongPollingOptions(settings.experimentalLongPollingOptions),
122124
settings.useFetchStreams,
123-
settings.isUsingEmulator
125+
settings.isUsingEmulator,
126+
apiKey
124127
);
125128
}

packages/firestore/src/platform/node/grpc_connection.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ function createMetadata(
4444
databasePath: string,
4545
authToken: Token | null,
4646
appCheckToken: Token | null,
47-
appId: string
47+
appId: string,
48+
apiKey: string | undefined
4849
): grpc.Metadata {
4950
hardAssert(
5051
authToken === null || authToken.type === 'OAuth',
@@ -69,6 +70,9 @@ function createMetadata(
6970
// 11 from Google3.
7071
metadata.set('Google-Cloud-Resource-Prefix', databasePath);
7172
metadata.set('x-goog-request-params', databasePath);
73+
if (apiKey) {
74+
metadata.set('X-Goog-Api-Key', apiKey);
75+
}
7276
return metadata;
7377
}
7478

@@ -100,7 +104,8 @@ export class GrpcConnection implements Connection {
100104
this.databasePath = `projects/${databaseInfo.databaseId.projectId}/databases/${databaseInfo.databaseId.database}`;
101105
}
102106

103-
private ensureActiveStub(): GeneratedGrpcStub {
107+
/** made protected for testing */
108+
protected ensureActiveStub(): GeneratedGrpcStub {
104109
if (!this.cachedStub) {
105110
logDebug(LOG_TAG, 'Creating Firestore stub.');
106111
const credentials = this.databaseInfo.ssl
@@ -127,7 +132,8 @@ export class GrpcConnection implements Connection {
127132
this.databasePath,
128133
authToken,
129134
appCheckToken,
130-
this.databaseInfo.appId
135+
this.databaseInfo.appId,
136+
this.databaseInfo.apiKey
131137
);
132138
const jsonRequest = { database: this.databasePath, ...request };
133139

@@ -187,7 +193,8 @@ export class GrpcConnection implements Connection {
187193
this.databasePath,
188194
authToken,
189195
appCheckToken,
190-
this.databaseInfo.appId
196+
this.databaseInfo.appId,
197+
this.databaseInfo.apiKey
191198
);
192199
const jsonRequest = { ...request, database: this.databasePath };
193200
const stream = stub[rpcName](jsonRequest, metadata);
@@ -239,7 +246,8 @@ export class GrpcConnection implements Connection {
239246
this.databasePath,
240247
authToken,
241248
appCheckToken,
242-
this.databaseInfo.appId
249+
this.databaseInfo.appId,
250+
this.databaseInfo.apiKey
243251
);
244252
const grpcStream = stub[rpcName](metadata);
245253

packages/firestore/src/remote/rest_connection.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export abstract class RestConnection implements Connection {
6464
protected readonly baseUrl: string;
6565
private readonly databasePath: string;
6666
private readonly requestParams: string;
67+
private readonly apiKey: string | undefined;
6768

6869
get shouldResourcePathBeIncludedInRequest(): boolean {
6970
// Both `invokeRPC()` and `invokeStreamingRPC()` use their `path` arguments to determine
@@ -82,6 +83,7 @@ export abstract class RestConnection implements Connection {
8283
this.databaseId.database === DEFAULT_DATABASE_NAME
8384
? `project_id=${projectId}`
8485
: `project_id=${projectId}&database_id=${databaseId}`;
86+
this.apiKey = databaseInfo.apiKey;
8587
}
8688

8789
invokeRPC<Req, Resp>(
@@ -194,13 +196,17 @@ export abstract class RestConnection implements Connection {
194196
_forwardCredentials: boolean
195197
): Promise<Resp>;
196198

197-
private makeUrl(rpcName: string, path: string): string {
199+
protected makeUrl(rpcName: string, path: string): string {
198200
const urlRpcName = RPC_NAME_URL_MAPPING[rpcName];
199201
debugAssert(
200202
urlRpcName !== undefined,
201203
'Unknown REST mapping for: ' + rpcName
202204
);
203-
return `${this.baseUrl}/${RPC_URL_VERSION}/${path}:${urlRpcName}`;
205+
let url = `${this.baseUrl}/${RPC_URL_VERSION}/${path}:${urlRpcName}`;
206+
if (this.apiKey) {
207+
url = `${url}?key=${encodeURIComponent(this.apiKey)}`;
208+
}
209+
return url;
204210
}
205211

206212
/**

packages/firestore/test/integration/api/provider.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ import {
2929
enableIndexedDbPersistence,
3030
setDoc,
3131
memoryLocalCache,
32-
getDocFromCache
32+
getDocFromCache,
33+
ensureFirestoreConfigured
3334
} from '../util/firebase_export';
3435
import { DEFAULT_SETTINGS } from '../util/settings';
3536

@@ -200,4 +201,17 @@ describe('Firestore Provider', () => {
200201

201202
return terminate(firestore).then(() => terminate(firestore));
202203
});
204+
205+
it('passes API key to database info', () => {
206+
const app = initializeApp(
207+
{ apiKey: 'fake-api-key-x', projectId: 'test-project' },
208+
'test-app-getFirestore-x'
209+
);
210+
const fs = getFirestore(app);
211+
ensureFirestoreConfigured(fs);
212+
213+
expect(fs._firestoreClient?._databaseInfo.apiKey).to.equal(
214+
'fake-api-key-x'
215+
);
216+
});
203217
});

packages/firestore/test/integration/util/internal_helpers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ export function getDefaultDatabaseInfo(): DatabaseInfo {
6262
DEFAULT_SETTINGS.experimentalLongPollingOptions ?? {}
6363
),
6464
/*use FetchStreams= */ false,
65-
/*isUsingEmulator=*/ false
65+
/*isUsingEmulator=*/ false,
66+
undefined
6667
);
6768
}
6869

packages/firestore/test/unit/remote/fetch_connection.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ describe('Fetch Connection', () => {
4343
DatabaseId.empty(),
4444
'',
4545
'',
46+
'',
4647
new FirestoreSettingsImpl({
4748
host: 'abc.cloudworkstations.dev'
4849
})
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { Metadata } from '@grpc/grpc-js';
19+
import { expect } from 'chai';
20+
21+
import { DatabaseId, DatabaseInfo } from '../../../src/core/database_info';
22+
import { ResourcePath } from '../../../src/model/path';
23+
import { GrpcConnection } from '../../../src/platform/node/grpc_connection';
24+
25+
export class TestGrpcConnection extends GrpcConnection {
26+
mockStub = {
27+
lastMetadata: null,
28+
mockRpc(
29+
req: unknown,
30+
metadata: Metadata,
31+
callback: (err: unknown, resp: unknown) => void
32+
) {
33+
this.lastMetadata = metadata;
34+
callback(null, null);
35+
}
36+
} as {
37+
lastMetadata: null | Metadata;
38+
[index: string]: unknown;
39+
};
40+
41+
protected ensureActiveStub(): unknown {
42+
return this.mockStub;
43+
}
44+
}
45+
46+
describe('GrpcConnection', () => {
47+
const testDatabaseInfo = new DatabaseInfo(
48+
new DatabaseId('testproject'),
49+
'test-app-id',
50+
'persistenceKey',
51+
'example.com',
52+
/*ssl=*/ false,
53+
/*forceLongPolling=*/ false,
54+
/*autoDetectLongPolling=*/ false,
55+
/*longPollingOptions=*/ {},
56+
/*useFetchStreams=*/ false,
57+
/*isUsingEmulator=*/ false,
58+
'grpc-connection-test-api-key'
59+
);
60+
const connection = new TestGrpcConnection(
61+
{ google: { firestore: { v1: {} } } },
62+
testDatabaseInfo
63+
);
64+
65+
it('Passes the API Key from DatabaseInfo to the grpc stub', async () => {
66+
const request = {
67+
database: 'projects/testproject/databases/(default)',
68+
writes: []
69+
};
70+
await connection.invokeRPC(
71+
'mockRpc',
72+
ResourcePath.emptyPath(),
73+
request,
74+
null,
75+
null
76+
);
77+
expect(
78+
connection.mockStub.lastMetadata?.get('x-goog-api-key')
79+
).to.deep.equal(['grpc-connection-test-api-key']);
80+
});
81+
});

0 commit comments

Comments
 (0)