Skip to content

Commit 3c6a11c

Browse files
authored
Implement mockUserToken for Storage and fix bugs. (#5282)
* Add mockUserToken typing for storage. * Implement mockUserToken for Storage and fix bugs. * Revert RUT changes. * Create wise-toys-care.md * Set EOL to unix style. * Address review feedback.
1 parent 064e1ca commit 3c6a11c

File tree

24 files changed

+232
-51
lines changed

24 files changed

+232
-51
lines changed

.changeset/wise-toys-care.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@firebase/database-types': minor
3+
'@firebase/database': minor
4+
'firebase': minor
5+
'@firebase/firestore-types': minor
6+
'@firebase/firestore': minor
7+
'@firebase/storage-types': minor
8+
'@firebase/storage': minor
9+
'@firebase/util': minor
10+
---
11+
12+
Implement mockUserToken for Storage and fix JWT format bugs.

common/api-review/database.api.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export function child(parent: DatabaseReference, path: string): DatabaseReferenc
1212

1313
// @public
1414
export function connectDatabaseEmulator(db: Database, host: string, port: number, options?: {
15-
mockUserToken?: EmulatorMockTokenOptions;
15+
mockUserToken?: EmulatorMockTokenOptions | string;
1616
}): void;
1717

1818
// @public

common/api-review/firestore-lite.api.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class CollectionReference<T = DocumentData> extends Query<T> {
5151

5252
// @public
5353
export function connectFirestoreEmulator(firestore: Firestore, host: string, port: number, options?: {
54-
mockUserToken?: EmulatorMockTokenOptions;
54+
mockUserToken?: EmulatorMockTokenOptions | string;
5555
}): void;
5656

5757
// @public

common/api-review/firestore.api.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class CollectionReference<T = DocumentData> extends Query<T> {
5757

5858
// @public
5959
export function connectFirestoreEmulator(firestore: Firestore, host: string, port: number, options?: {
60-
mockUserToken?: EmulatorMockTokenOptions;
60+
mockUserToken?: EmulatorMockTokenOptions | string;
6161
}): void;
6262

6363
// @public

common/api-review/storage.api.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types';
88
import { CompleteFn } from '@firebase/util';
9+
import { EmulatorMockTokenOptions } from '@firebase/util';
910
import { FirebaseApp } from '@firebase/app';
1011
import { FirebaseAuthInternalName } from '@firebase/auth-interop-types';
1112
import { FirebaseError } from '@firebase/util';
@@ -16,7 +17,9 @@ import { Subscribe } from '@firebase/util';
1617
import { Unsubscribe } from '@firebase/util';
1718

1819
// @public
19-
export function connectStorageEmulator(storage: FirebaseStorage, host: string, port: number): void;
20+
export function connectStorageEmulator(storage: FirebaseStorage, host: string, port: number, options?: {
21+
mockUserToken?: EmulatorMockTokenOptions | string;
22+
}): void;
2023

2124
// @public
2225
export function deleteObject(ref: StorageReference): Promise<void>;

packages/database-types/index.d.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
import { FirebaseApp } from '@firebase/app-types';
19+
import { EmulatorMockTokenOptions } from '@firebase/util';
1920

2021
export interface DataSnapshot {
2122
child(path: string): DataSnapshot;
@@ -34,7 +35,13 @@ export interface DataSnapshot {
3435

3536
export interface Database {
3637
app: FirebaseApp;
37-
useEmulator(host: string, port: number): void;
38+
useEmulator(
39+
host: string,
40+
port: number,
41+
options?: {
42+
mockUserToken?: EmulatorMockTokenOptions | string;
43+
}
44+
): void;
3845
goOffline(): void;
3946
goOnline(): void;
4047
ref(path?: string | Reference): Reference;
@@ -44,7 +51,13 @@ export interface Database {
4451
export class FirebaseDatabase implements Database {
4552
private constructor();
4653
app: FirebaseApp;
47-
useEmulator(host: string, port: number): void;
54+
useEmulator(
55+
host: string,
56+
port: number,
57+
options?: {
58+
mockUserToken?: EmulatorMockTokenOptions | string;
59+
}
60+
): void;
4861
goOffline(): void;
4962
goOnline(): void;
5063
ref(path?: string | Reference): Reference;

packages/database-types/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"index.d.ts"
1313
],
1414
"dependencies": {
15-
"@firebase/app-types": "0.6.3"
15+
"@firebase/app-types": "0.6.3",
16+
"@firebase/util": "1.2.0"
1617
},
1718
"repository": {
1819
"directory": "packages/database-types",

packages/database/src/exp/Database.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ export function connectDatabaseEmulator(
308308
host: string,
309309
port: number,
310310
options: {
311-
mockUserToken?: EmulatorMockTokenOptions;
311+
mockUserToken?: EmulatorMockTokenOptions | string;
312312
} = {}
313313
): void {
314314
db = getModularInstance(db);
@@ -329,10 +329,10 @@ export function connectDatabaseEmulator(
329329
}
330330
tokenProvider = new EmulatorTokenProvider(EmulatorTokenProvider.OWNER);
331331
} else if (options.mockUserToken) {
332-
const token = createMockUserToken(
333-
options.mockUserToken,
334-
db.app.options.projectId
335-
);
332+
const token =
333+
typeof options.mockUserToken === 'string'
334+
? options.mockUserToken
335+
: createMockUserToken(options.mockUserToken, db.app.options.projectId);
336336
tokenProvider = new EmulatorTokenProvider(token);
337337
}
338338

packages/firebase/index.d.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -5907,7 +5907,7 @@ declare namespace firebase.database {
59075907
host: string,
59085908
port: number,
59095909
options?: {
5910-
mockUserToken?: EmulatorMockTokenOptions;
5910+
mockUserToken?: EmulatorMockTokenOptions | string;
59115911
}
59125912
): void;
59135913
/**
@@ -7850,8 +7850,11 @@ declare namespace firebase.storage {
78507850
*
78517851
* @param host - The emulator host (ex: localhost)
78527852
* @param port - The emulator port (ex: 5001)
7853+
* @param options.mockUserToken the mock auth token to use for unit testing Security Rules
78537854
*/
7854-
useEmulator(host: string, port: number): void;
7855+
useEmulator(host: string, port: number, options?: {
7856+
mockUserToken?: EmulatorMockTokenOptions | string;
7857+
}): void;
78557858
}
78567859

78577860
/**
@@ -8382,7 +8385,7 @@ declare namespace firebase.firestore {
83828385
host: string,
83838386
port: number,
83848387
options?: {
8385-
mockUserToken?: EmulatorMockTokenOptions;
8388+
mockUserToken?: EmulatorMockTokenOptions | string;
83868389
}
83878390
): void;
83888391

packages/firestore-types/index.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export class FirebaseFirestore {
6565
host: string,
6666
port: number,
6767
options?: {
68-
mockUserToken?: EmulatorMockTokenOptions;
68+
mockUserToken?: EmulatorMockTokenOptions | string;
6969
}
7070
): void;
7171

packages/firestore/src/api/database.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ export class Firestore
244244
host: string,
245245
port: number,
246246
options: {
247-
mockUserToken?: EmulatorMockTokenOptions;
247+
mockUserToken?: EmulatorMockTokenOptions | string;
248248
} = {}
249249
): void {
250250
connectFirestoreEmulator(this._delegate, host, port, options);

packages/firestore/src/auth/user.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export class User {
2727
// non-FirebaseAuth providers.
2828
static readonly GOOGLE_CREDENTIALS = new User('google-credentials-uid');
2929
static readonly FIRST_PARTY = new User('first-party-uid');
30+
static readonly MOCK_USER = new User('mock-user');
3031

3132
constructor(readonly uid: string | null) {}
3233

packages/firestore/src/lite/database.ts

+21-10
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ export function connectFirestoreEmulator(
238238
host: string,
239239
port: number,
240240
options: {
241-
mockUserToken?: EmulatorMockTokenOptions;
241+
mockUserToken?: EmulatorMockTokenOptions | string;
242242
} = {}
243243
): void {
244244
firestore = cast(firestore, Firestore);
@@ -258,19 +258,30 @@ export function connectFirestoreEmulator(
258258
});
259259

260260
if (options.mockUserToken) {
261-
// Let createMockUserToken validate first (catches common mistakes like
262-
// invalid field "uid" and missing field "sub" / "user_id".)
263-
const token = createMockUserToken(options.mockUserToken);
264-
const uid = options.mockUserToken.sub || options.mockUserToken.user_id;
265-
if (!uid) {
266-
throw new FirestoreError(
267-
Code.INVALID_ARGUMENT,
268-
"mockUserToken must contain 'sub' or 'user_id' field!"
261+
let token: string;
262+
let user: User;
263+
if (typeof options.mockUserToken === 'string') {
264+
token = options.mockUserToken;
265+
user = User.MOCK_USER;
266+
} else {
267+
// Let createMockUserToken validate first (catches common mistakes like
268+
// invalid field "uid" and missing field "sub" / "user_id".)
269+
token = createMockUserToken(
270+
options.mockUserToken,
271+
firestore._app?.options.projectId
269272
);
273+
const uid = options.mockUserToken.sub || options.mockUserToken.user_id;
274+
if (!uid) {
275+
throw new FirestoreError(
276+
Code.INVALID_ARGUMENT,
277+
"mockUserToken must contain 'sub' or 'user_id' field!"
278+
);
279+
}
280+
user = new User(uid);
270281
}
271282

272283
firestore._credentials = new EmulatorCredentialsProvider(
273-
new OAuthToken(token, new User(uid))
284+
new OAuthToken(token, user)
274285
);
275286
}
276287
}

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

+21-5
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,27 @@ apiDescribe('Validation:', (persistence: boolean) => {
158158
}
159159
);
160160

161-
validationIt(persistence, 'useEmulator can set mockUserToken', () => {
162-
const db = newTestFirestore('test-project');
163-
// Verify that this doesn't throw.
164-
db.useEmulator('localhost', 9000, { mockUserToken: { sub: 'foo' } });
165-
});
161+
validationIt(
162+
persistence,
163+
'useEmulator can set mockUserToken object',
164+
() => {
165+
const db = newTestFirestore('test-project');
166+
// Verify that this doesn't throw.
167+
db.useEmulator('localhost', 9000, { mockUserToken: { sub: 'foo' } });
168+
}
169+
);
170+
171+
validationIt(
172+
persistence,
173+
'useEmulator can set mockUserToken string',
174+
() => {
175+
const db = newTestFirestore('test-project');
176+
// Verify that this doesn't throw.
177+
db.useEmulator('localhost', 9000, {
178+
mockUserToken: 'my-mock-user-token'
179+
});
180+
}
181+
);
166182

167183
validationIt(
168184
persistence,

packages/firestore/test/unit/api/database.test.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import { expect } from 'chai';
1919

2020
import { EmulatorCredentialsProvider } from '../../../src/api/credentials';
21+
import { User } from '../../../src/auth/user';
2122
import {
2223
collectionReference,
2324
documentReference,
@@ -252,7 +253,7 @@ describe('Settings', () => {
252253
expect(db._delegate._getSettings().ssl).to.be.false;
253254
});
254255

255-
it('sets credentials based on mockUserToken', async () => {
256+
it('sets credentials based on mockUserToken object', async () => {
256257
// Use a new instance of Firestore in order to configure settings.
257258
const db = newTestFirestore();
258259
const mockUserToken = { sub: 'foobar' };
@@ -264,4 +265,18 @@ describe('Settings', () => {
264265
expect(token!.type).to.eql('OAuth');
265266
expect(token!.user.uid).to.eql(mockUserToken.sub);
266267
});
268+
269+
it('sets credentials based on mockUserToken string', async () => {
270+
// Use a new instance of Firestore in order to configure settings.
271+
const db = newTestFirestore();
272+
db.useEmulator('localhost', 9000, {
273+
mockUserToken: 'my-custom-mock-user-token'
274+
});
275+
276+
const credentials = db._delegate._credentials;
277+
expect(credentials).to.be.instanceOf(EmulatorCredentialsProvider);
278+
const token = await credentials.getToken();
279+
expect(token!.type).to.eql('OAuth');
280+
expect(token!.user).to.eql(User.MOCK_USER);
281+
});
267282
});

packages/rules-unit-testing/src/api/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export type FirebaseEmulatorOptions = {
160160

161161
function trimmedBase64Encode(val: string): string {
162162
// Use base64url encoding and remove padding in the end (dot characters).
163-
return base64Encode(val).replace(/\./g, '');
163+
return base64Encode(val).replace(/\./g, "");
164164
}
165165

166166
function createUnsecuredJwt(token: TokenOptions, projectId?: string): string {
@@ -498,7 +498,7 @@ function initializeApp(
498498
ComponentType.PRIVATE
499499
);
500500

501-
(app as unknown as _FirebaseApp)._addOrOverwriteComponent(
501+
((app as unknown) as _FirebaseApp)._addOrOverwriteComponent(
502502
mockAuthComponent
503503
);
504504
}
@@ -703,7 +703,7 @@ export function assertFails(pr: Promise<any>): any {
703703
errCode === 'permission-denied' ||
704704
errCode === 'permission_denied' ||
705705
errMessage.indexOf('permission_denied') >= 0 ||
706-
errMessage.indexOf('permission denied') >= 0 ||
706+
errMessage.indexOf('permission denied') >= 0 ||
707707
// Storage permission errors contain message: (storage/unauthorized)
708708
errMessage.indexOf('unauthorized') >= 0;
709709

packages/storage-types/index.d.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@
1616
*/
1717

1818
import { FirebaseApp } from '@firebase/app-types';
19-
import { CompleteFn, FirebaseError, NextFn, Unsubscribe } from '@firebase/util';
19+
import {
20+
CompleteFn,
21+
EmulatorMockTokenOptions,
22+
FirebaseError,
23+
NextFn,
24+
Unsubscribe
25+
} from '@firebase/util';
2026

2127
export interface FullMetadata extends UploadMetadata {
2228
bucket: string;
@@ -135,7 +141,14 @@ export class FirebaseStorage {
135141
refFromURL(url: string): Reference;
136142
setMaxOperationRetryTime(time: number): void;
137143
setMaxUploadRetryTime(time: number): void;
138-
useEmulator(host: string, port: number): void;
144+
145+
useEmulator(
146+
host: string,
147+
port: number,
148+
options?: {
149+
mockUserToken?: EmulatorMockTokenOptions | string;
150+
}
151+
): void;
139152
}
140153

141154
declare module '@firebase/component' {

packages/storage/compat/service.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
import { ReferenceCompat } from './reference';
2828
import { isUrl, FirebaseStorageImpl } from '../src/service';
2929
import { invalidArgument } from '../src/implementation/error';
30-
import { Compat } from '@firebase/util';
30+
import { Compat, EmulatorMockTokenOptions } from '@firebase/util';
3131

3232
/**
3333
* A service that provides firebaseStorage.Reference instances.
@@ -87,7 +87,13 @@ export class StorageServiceCompat
8787
this._delegate.maxOperationRetryTime = time;
8888
}
8989

90-
useEmulator(host: string, port: number): void {
91-
connectStorageEmulator(this._delegate, host, port);
90+
useEmulator(
91+
host: string,
92+
port: number,
93+
options: {
94+
mockUserToken?: EmulatorMockTokenOptions | string;
95+
} = {}
96+
): void {
97+
connectStorageEmulator(this._delegate, host, port, options);
9298
}
9399
}

0 commit comments

Comments
 (0)