Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement mockUserToken for Storage and fix bugs. #5282

Merged
merged 6 commits into from
Aug 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out I forgot this in #4792. Fixing in this PR.

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