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

Make App Check initialization explicit #4897

Merged
merged 5 commits into from
May 12, 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
6 changes: 6 additions & 0 deletions .changeset/fair-garlics-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@firebase/app': patch
'@firebase/app-check': patch
---

Make App Check initialization explicit, to prevent unexpected errors for users who do not intend to use App Check.
12 changes: 11 additions & 1 deletion packages-exp/app-compat/src/firebaseApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
Component,
ComponentContainer,
ComponentType,
InstantiationMode,
Name
} from '@firebase/component';
import {
Expand Down Expand Up @@ -121,8 +122,17 @@ export class FirebaseAppImpl implements Compat<_FirebaseAppExp>, _FirebaseApp {
): _FirebaseService {
this._delegate.checkDestroyed();

// Initialize instance if InstatiationMode is `EXPLICIT`.
const provider = this._delegate.container.getProvider(name as Name);
if (
!provider.isInitialized() &&
provider.getComponent()?.instantiationMode === InstantiationMode.EXPLICIT
) {
provider.initialize();
}

// getImmediate will always succeed because _getService is only called for registered components.
return (this._delegate.container.getProvider(name as Name).getImmediate({
return (provider.getImmediate({
identifier: instanceIdentifier
}) as unknown) as _FirebaseService;
}
Expand Down
72 changes: 71 additions & 1 deletion packages-exp/app-compat/test/firebaseAppCompat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import { stub } from 'sinon';
import { FirebaseNamespace, FirebaseOptions } from '../src/public-types';
import { _FirebaseApp, _FirebaseNamespace } from '../src/types';
import { _components, _clearComponents } from '@firebase/app-exp';
import { ComponentType } from '@firebase/component';
import {
Component,
ComponentType,
InstantiationMode
} from '@firebase/component';

import { createFirebaseNamespace } from '../src/firebaseNamespace';
import { createFirebaseNamespaceLite } from '../src/lite/firebaseNamespaceLite';
Expand Down Expand Up @@ -67,6 +71,7 @@ function executeFirebaseTests(): void {

expect(serviceNamespace).to.eq(serviceNamespace2);
expect(registerStub).to.have.not.thrown();
registerStub.restore();
});

it('returns cached service instances', () => {
Expand All @@ -80,6 +85,71 @@ function executeFirebaseTests(): void {
expect(service).to.eq((firebase as any).test());
});

it('does not instantiate explicit components unless called explicitly', () => {
firebase.initializeApp({});
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
createTestComponent('explicit1').setInstantiationMode(
InstantiationMode.EXPLICIT
)
);

let explicitService;

// Expect getImmediate in a consuming component to return null.
const consumerComponent = new Component(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
'consumer' as any,
container => {
explicitService = container
.getProvider('explicit1' as any)
.getImmediate({ optional: true });
return new TestService(
container.getProvider('app-compat').getImmediate()
);
},
ComponentType.PUBLIC
);
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
consumerComponent
);

(firebase as any).consumer();
expect(explicitService).to.be.null;
});

it('does instantiate explicit components when called explicitly', () => {
firebase.initializeApp({});
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
createTestComponent('explicit2').setInstantiationMode(
InstantiationMode.EXPLICIT
)
);

let explicitService;

// Expect getImmediate in a consuming component to return the service.
const consumerComponent = new Component(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
'consumer' as any,
container => {
explicitService = container
.getProvider('explicit2' as any)
.getImmediate({ optional: true });
return new TestService(
container.getProvider('app-compat').getImmediate()
);
},
ComponentType.PUBLIC
);
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
consumerComponent
);

(firebase as any).explicit2();
(firebase as any).consumer();
expect(explicitService).to.not.be.null;
});

it(`creates a new instance of a service after removing the existing instance`, () => {
const app = firebase.initializeApp({});
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
Expand Down
28 changes: 26 additions & 2 deletions packages/app-check/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
*/
import firebase from '@firebase/app';
import { _FirebaseNamespace } from '@firebase/app-types/private';
import { Component, ComponentType } from '@firebase/component';
import {
Component,
ComponentType,
InstantiationMode
} from '@firebase/component';
import {
FirebaseAppCheck,
AppCheckComponentName
Expand All @@ -41,6 +45,26 @@ function registerAppCheck(firebase: _FirebaseNamespace): void {
},
ComponentType.PUBLIC
)
/**
* AppCheck can only be initialized by explicitly calling firebase.appCheck()
* We don't want firebase products that consume AppCheck to gate on AppCheck
* if the user doesn't intend them to, just because the AppCheck component
* is registered.
*/
.setInstantiationMode(InstantiationMode.EXPLICIT)
/**
* Because all firebase products that depend on app-check depend on app-check-internal directly,
* we need to initialize app-check-internal after app-check is initialized to make it
* available to other firebase products.
*/
.setInstanceCreatedCallback(
(container, _instanceIdentifier, _instance) => {
const appCheckInternalProvider = container.getProvider(
APP_CHECK_NAME_INTERNAL
);
appCheckInternalProvider.initialize();
}
)
);

// The internal interface used by other Firebase products
Expand All @@ -54,7 +78,7 @@ function registerAppCheck(firebase: _FirebaseNamespace): void {
return internalFactory(app, platformLoggerProvider);
},
ComponentType.PUBLIC
)
).setInstantiationMode(InstantiationMode.EXPLICIT)
);

firebase.registerVersion(name, version);
Expand Down
14 changes: 12 additions & 2 deletions packages/app/src/firebaseApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import {
ComponentContainer,
Component,
ComponentType,
Name
Name,
InstantiationMode
} from '@firebase/component';
import { AppError, ERROR_FACTORY } from './errors';
import { DEFAULT_ENTRY_NAME } from './constants';
Expand Down Expand Up @@ -122,8 +123,17 @@ export class FirebaseAppImpl implements FirebaseApp {
): FirebaseService {
this.checkDestroyed_();

// Initialize instance if InstatiationMode is `EXPLICIT`.
const provider = this.container.getProvider(name as Name);
if (
!provider.isInitialized() &&
provider.getComponent()?.instantiationMode === InstantiationMode.EXPLICIT
) {
provider.initialize();
}

hsubox76 marked this conversation as resolved.
Show resolved Hide resolved
// getImmediate will always succeed because _getService is only called for registered components.
return (this.container.getProvider(name as Name).getImmediate({
return (provider.getImmediate({
identifier: instanceIdentifier
}) as unknown) as FirebaseService;
}
Expand Down
13 changes: 9 additions & 4 deletions packages/app/test/clientLogger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
*/

import { FirebaseNamespace, VersionService } from '@firebase/app-types';
import { _FirebaseNamespace } from '@firebase/app-types/private';
import {
_FirebaseNamespace,
FirebaseService
} from '@firebase/app-types/private';
import { createFirebaseNamespace } from '../src/firebaseNamespace';
import { expect } from 'chai';
import { spy as Spy } from 'sinon';
Expand All @@ -29,7 +32,7 @@ declare module '@firebase/component' {
interface NameServiceMapping {
'vs1': VersionService;
'vs2': VersionService;
'test-shell': Promise<void>;
'test-shell': FirebaseService;
}
}

Expand All @@ -51,7 +54,7 @@ describe('User Log Methods', () => {
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
new Component(
'test-shell',
async () => {
() => {
const logger = new Logger('@firebase/logger-test');
logger.warn('hello');
expect(warnSpy.called).to.be.true;
Expand All @@ -67,6 +70,7 @@ describe('User Log Methods', () => {
expect(infoSpy.called).to.be.true;
logger.log('hi');
expect(logSpy.called).to.be.true;
return {} as FirebaseService;
},
ComponentType.PUBLIC
)
Expand All @@ -81,7 +85,7 @@ describe('User Log Methods', () => {
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
new Component(
'test-shell',
async () => {
() => {
const logger = new Logger('@firebase/logger-test');
(firebase as _FirebaseNamespace).onLog(logData => {
result = logData;
Expand All @@ -92,6 +96,7 @@ describe('User Log Methods', () => {
expect(result.args).to.deep.equal(['hi']);
expect(result.type).to.equal('@firebase/logger-test');
expect(infoSpy.called).to.be.true;
return {} as FirebaseService;
},
ComponentType.PUBLIC
)
Expand Down
67 changes: 66 additions & 1 deletion packages/app/test/firebaseApp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ import { createFirebaseNamespace } from '../src/firebaseNamespace';
import { createFirebaseNamespaceLite } from '../src/lite/firebaseNamespaceLite';
import { expect } from 'chai';
import { stub } from 'sinon';
import { Component, ComponentType } from '@firebase/component';
import {
Component,
ComponentType,
InstantiationMode
} from '@firebase/component';
import './setup';

executeFirebaseTests();
Expand Down Expand Up @@ -80,6 +84,67 @@ function executeFirebaseTests(): void {
expect(service).to.eq((firebase as any).test());
});

it('does not instantiate explicit components unless called explicitly', () => {
firebase.initializeApp({});
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
createTestComponent('test').setInstantiationMode(
InstantiationMode.EXPLICIT
)
);

let explicitService;

// Expect getImmediate in a consuming component to return null.
const consumerComponent = new Component(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
'consumer' as any,
container => {
explicitService = container
.getProvider('test' as any)
.getImmediate({ optional: true });
return new TestService(container.getProvider('app').getImmediate());
},
ComponentType.PUBLIC
);
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
consumerComponent
);

(firebase as any).consumer();
expect(explicitService).to.be.null;
});

it('does instantiate explicit components when called explicitly', () => {
firebase.initializeApp({});
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
createTestComponent('test').setInstantiationMode(
InstantiationMode.EXPLICIT
)
);

let explicitService;

// Expect getImmediate in a consuming component to return the service.
const consumerComponent = new Component(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
'consumer' as any,
container => {
explicitService = container
.getProvider('test' as any)
.getImmediate({ optional: true });
return new TestService(container.getProvider('app').getImmediate());
},
ComponentType.PUBLIC
);
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
consumerComponent
);

(firebase as any).test();
(firebase as any).consumer();
expect(explicitService).to.not.be.null;
});

it(`creates a new instance of a service after removing the existing instance`, () => {
const app = firebase.initializeApp({});
(firebase as _FirebaseNamespace).INTERNAL.registerComponent(
Expand Down
Loading