Skip to content

Commit

Permalink
Implement mockUserToken for Storage and fix bugs. (#5282)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
yuchenshi authored Aug 13, 2021
1 parent 064e1ca commit 3c6a11c
Show file tree
Hide file tree
Showing 24 changed files with 232 additions and 51 deletions.
12 changes: 12 additions & 0 deletions .changeset/wise-toys-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@firebase/database-types': minor
'@firebase/database': minor
'firebase': minor
'@firebase/firestore-types': minor
'@firebase/firestore': minor
'@firebase/storage-types': minor
'@firebase/storage': minor
'@firebase/util': minor
---

Implement mockUserToken for Storage and fix JWT format bugs.
2 changes: 1 addition & 1 deletion common/api-review/database.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function child(parent: DatabaseReference, path: string): DatabaseReferenc

// @public
export function connectDatabaseEmulator(db: Database, host: string, port: number, options?: {
mockUserToken?: EmulatorMockTokenOptions;
mockUserToken?: EmulatorMockTokenOptions | string;
}): void;

// @public
Expand Down
2 changes: 1 addition & 1 deletion common/api-review/firestore-lite.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class CollectionReference<T = DocumentData> extends Query<T> {

// @public
export function connectFirestoreEmulator(firestore: Firestore, host: string, port: number, options?: {
mockUserToken?: EmulatorMockTokenOptions;
mockUserToken?: EmulatorMockTokenOptions | string;
}): void;

// @public
Expand Down
2 changes: 1 addition & 1 deletion common/api-review/firestore.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class CollectionReference<T = DocumentData> extends Query<T> {

// @public
export function connectFirestoreEmulator(firestore: Firestore, host: string, port: number, options?: {
mockUserToken?: EmulatorMockTokenOptions;
mockUserToken?: EmulatorMockTokenOptions | string;
}): void;

// @public
Expand Down
5 changes: 4 additions & 1 deletion common/api-review/storage.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types';
import { CompleteFn } from '@firebase/util';
import { EmulatorMockTokenOptions } from '@firebase/util';
import { FirebaseApp } from '@firebase/app';
import { FirebaseAuthInternalName } from '@firebase/auth-interop-types';
import { FirebaseError } from '@firebase/util';
Expand All @@ -16,7 +17,9 @@ import { Subscribe } from '@firebase/util';
import { Unsubscribe } from '@firebase/util';

// @public
export function connectStorageEmulator(storage: FirebaseStorage, host: string, port: number): void;
export function connectStorageEmulator(storage: FirebaseStorage, host: string, port: number, options?: {
mockUserToken?: EmulatorMockTokenOptions | string;
}): void;

// @public
export function deleteObject(ref: StorageReference): Promise<void>;
Expand Down
17 changes: 15 additions & 2 deletions packages/database-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

import { FirebaseApp } from '@firebase/app-types';
import { EmulatorMockTokenOptions } from '@firebase/util';

export interface DataSnapshot {
child(path: string): DataSnapshot;
Expand All @@ -34,7 +35,13 @@ export interface DataSnapshot {

export interface Database {
app: FirebaseApp;
useEmulator(host: string, port: number): void;
useEmulator(
host: string,
port: number,
options?: {
mockUserToken?: EmulatorMockTokenOptions | string;
}
): void;
goOffline(): void;
goOnline(): void;
ref(path?: string | Reference): Reference;
Expand All @@ -44,7 +51,13 @@ export interface Database {
export class FirebaseDatabase implements Database {
private constructor();
app: FirebaseApp;
useEmulator(host: string, port: number): void;
useEmulator(
host: string,
port: number,
options?: {
mockUserToken?: EmulatorMockTokenOptions | string;
}
): void;
goOffline(): void;
goOnline(): void;
ref(path?: string | Reference): Reference;
Expand Down
3 changes: 2 additions & 1 deletion packages/database-types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"index.d.ts"
],
"dependencies": {
"@firebase/app-types": "0.6.3"
"@firebase/app-types": "0.6.3",
"@firebase/util": "1.2.0"
},
"repository": {
"directory": "packages/database-types",
Expand Down
10 changes: 5 additions & 5 deletions packages/database/src/exp/Database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ export function connectDatabaseEmulator(
host: string,
port: number,
options: {
mockUserToken?: EmulatorMockTokenOptions;
mockUserToken?: EmulatorMockTokenOptions | string;
} = {}
): void {
db = getModularInstance(db);
Expand All @@ -329,10 +329,10 @@ export function connectDatabaseEmulator(
}
tokenProvider = new EmulatorTokenProvider(EmulatorTokenProvider.OWNER);
} else if (options.mockUserToken) {
const token = createMockUserToken(
options.mockUserToken,
db.app.options.projectId
);
const token =
typeof options.mockUserToken === 'string'
? options.mockUserToken
: createMockUserToken(options.mockUserToken, db.app.options.projectId);
tokenProvider = new EmulatorTokenProvider(token);
}

Expand Down
9 changes: 6 additions & 3 deletions packages/firebase/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5907,7 +5907,7 @@ declare namespace firebase.database {
host: string,
port: number,
options?: {
mockUserToken?: EmulatorMockTokenOptions;
mockUserToken?: EmulatorMockTokenOptions | string;
}
): void;
/**
Expand Down Expand Up @@ -7850,8 +7850,11 @@ declare namespace firebase.storage {
*
* @param host - The emulator host (ex: localhost)
* @param port - The emulator port (ex: 5001)
* @param options.mockUserToken the mock auth token to use for unit testing Security Rules
*/
useEmulator(host: string, port: number): void;
useEmulator(host: string, port: number, options?: {
mockUserToken?: EmulatorMockTokenOptions | string;
}): void;
}

/**
Expand Down Expand Up @@ -8382,7 +8385,7 @@ declare namespace firebase.firestore {
host: string,
port: number,
options?: {
mockUserToken?: EmulatorMockTokenOptions;
mockUserToken?: EmulatorMockTokenOptions | string;
}
): void;

Expand Down
2 changes: 1 addition & 1 deletion packages/firestore-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class FirebaseFirestore {
host: string,
port: number,
options?: {
mockUserToken?: EmulatorMockTokenOptions;
mockUserToken?: EmulatorMockTokenOptions | string;
}
): void;

Expand Down
2 changes: 1 addition & 1 deletion packages/firestore/src/api/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export class Firestore
host: string,
port: number,
options: {
mockUserToken?: EmulatorMockTokenOptions;
mockUserToken?: EmulatorMockTokenOptions | string;
} = {}
): void {
connectFirestoreEmulator(this._delegate, host, port, options);
Expand Down
1 change: 1 addition & 0 deletions packages/firestore/src/auth/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class User {
// non-FirebaseAuth providers.
static readonly GOOGLE_CREDENTIALS = new User('google-credentials-uid');
static readonly FIRST_PARTY = new User('first-party-uid');
static readonly MOCK_USER = new User('mock-user');

constructor(readonly uid: string | null) {}

Expand Down
31 changes: 21 additions & 10 deletions packages/firestore/src/lite/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export function connectFirestoreEmulator(
host: string,
port: number,
options: {
mockUserToken?: EmulatorMockTokenOptions;
mockUserToken?: EmulatorMockTokenOptions | string;
} = {}
): void {
firestore = cast(firestore, Firestore);
Expand All @@ -258,19 +258,30 @@ export function connectFirestoreEmulator(
});

if (options.mockUserToken) {
// Let createMockUserToken validate first (catches common mistakes like
// invalid field "uid" and missing field "sub" / "user_id".)
const token = createMockUserToken(options.mockUserToken);
const uid = options.mockUserToken.sub || options.mockUserToken.user_id;
if (!uid) {
throw new FirestoreError(
Code.INVALID_ARGUMENT,
"mockUserToken must contain 'sub' or 'user_id' field!"
let token: string;
let user: User;
if (typeof options.mockUserToken === 'string') {
token = options.mockUserToken;
user = User.MOCK_USER;
} else {
// Let createMockUserToken validate first (catches common mistakes like
// invalid field "uid" and missing field "sub" / "user_id".)
token = createMockUserToken(
options.mockUserToken,
firestore._app?.options.projectId
);
const uid = options.mockUserToken.sub || options.mockUserToken.user_id;
if (!uid) {
throw new FirestoreError(
Code.INVALID_ARGUMENT,
"mockUserToken must contain 'sub' or 'user_id' field!"
);
}
user = new User(uid);
}

firestore._credentials = new EmulatorCredentialsProvider(
new OAuthToken(token, new User(uid))
new OAuthToken(token, user)
);
}
}
Expand Down
26 changes: 21 additions & 5 deletions packages/firestore/test/integration/api/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,27 @@ apiDescribe('Validation:', (persistence: boolean) => {
}
);

validationIt(persistence, 'useEmulator can set mockUserToken', () => {
const db = newTestFirestore('test-project');
// Verify that this doesn't throw.
db.useEmulator('localhost', 9000, { mockUserToken: { sub: 'foo' } });
});
validationIt(
persistence,
'useEmulator can set mockUserToken object',
() => {
const db = newTestFirestore('test-project');
// Verify that this doesn't throw.
db.useEmulator('localhost', 9000, { mockUserToken: { sub: 'foo' } });
}
);

validationIt(
persistence,
'useEmulator can set mockUserToken string',
() => {
const db = newTestFirestore('test-project');
// Verify that this doesn't throw.
db.useEmulator('localhost', 9000, {
mockUserToken: 'my-mock-user-token'
});
}
);

validationIt(
persistence,
Expand Down
17 changes: 16 additions & 1 deletion packages/firestore/test/unit/api/database.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import { expect } from 'chai';

import { EmulatorCredentialsProvider } from '../../../src/api/credentials';
import { User } from '../../../src/auth/user';
import {
collectionReference,
documentReference,
Expand Down Expand Up @@ -252,7 +253,7 @@ describe('Settings', () => {
expect(db._delegate._getSettings().ssl).to.be.false;
});

it('sets credentials based on mockUserToken', async () => {
it('sets credentials based on mockUserToken object', async () => {
// Use a new instance of Firestore in order to configure settings.
const db = newTestFirestore();
const mockUserToken = { sub: 'foobar' };
Expand All @@ -264,4 +265,18 @@ describe('Settings', () => {
expect(token!.type).to.eql('OAuth');
expect(token!.user.uid).to.eql(mockUserToken.sub);
});

it('sets credentials based on mockUserToken string', async () => {
// Use a new instance of Firestore in order to configure settings.
const db = newTestFirestore();
db.useEmulator('localhost', 9000, {
mockUserToken: 'my-custom-mock-user-token'
});

const credentials = db._delegate._credentials;
expect(credentials).to.be.instanceOf(EmulatorCredentialsProvider);
const token = await credentials.getToken();
expect(token!.type).to.eql('OAuth');
expect(token!.user).to.eql(User.MOCK_USER);
});
});
6 changes: 3 additions & 3 deletions packages/rules-unit-testing/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export type FirebaseEmulatorOptions = {

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

function createUnsecuredJwt(token: TokenOptions, projectId?: string): string {
Expand Down Expand Up @@ -498,7 +498,7 @@ function initializeApp(
ComponentType.PRIVATE
);

(app as unknown as _FirebaseApp)._addOrOverwriteComponent(
((app as unknown) as _FirebaseApp)._addOrOverwriteComponent(
mockAuthComponent
);
}
Expand Down Expand Up @@ -703,7 +703,7 @@ export function assertFails(pr: Promise<any>): any {
errCode === 'permission-denied' ||
errCode === 'permission_denied' ||
errMessage.indexOf('permission_denied') >= 0 ||
errMessage.indexOf('permission denied') >= 0 ||
errMessage.indexOf('permission denied') >= 0 ||
// Storage permission errors contain message: (storage/unauthorized)
errMessage.indexOf('unauthorized') >= 0;

Expand Down
17 changes: 15 additions & 2 deletions packages/storage-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@
*/

import { FirebaseApp } from '@firebase/app-types';
import { CompleteFn, FirebaseError, NextFn, Unsubscribe } from '@firebase/util';
import {
CompleteFn,
EmulatorMockTokenOptions,
FirebaseError,
NextFn,
Unsubscribe
} from '@firebase/util';

export interface FullMetadata extends UploadMetadata {
bucket: string;
Expand Down Expand Up @@ -135,7 +141,14 @@ export class FirebaseStorage {
refFromURL(url: string): Reference;
setMaxOperationRetryTime(time: number): void;
setMaxUploadRetryTime(time: number): void;
useEmulator(host: string, port: number): void;

useEmulator(
host: string,
port: number,
options?: {
mockUserToken?: EmulatorMockTokenOptions | string;
}
): void;
}

declare module '@firebase/component' {
Expand Down
12 changes: 9 additions & 3 deletions packages/storage/compat/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
import { ReferenceCompat } from './reference';
import { isUrl, FirebaseStorageImpl } from '../src/service';
import { invalidArgument } from '../src/implementation/error';
import { Compat } from '@firebase/util';
import { Compat, EmulatorMockTokenOptions } from '@firebase/util';

/**
* A service that provides firebaseStorage.Reference instances.
Expand Down Expand Up @@ -87,7 +87,13 @@ export class StorageServiceCompat
this._delegate.maxOperationRetryTime = time;
}

useEmulator(host: string, port: number): void {
connectStorageEmulator(this._delegate, host, port);
useEmulator(
host: string,
port: number,
options: {
mockUserToken?: EmulatorMockTokenOptions | string;
} = {}
): void {
connectStorageEmulator(this._delegate, host, port, options);
}
}
Loading

0 comments on commit 3c6a11c

Please sign in to comment.