diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 37d67a9b16..95078fabfd 100755 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -34,7 +34,7 @@ import { AuthProviderConfig, AuthProviderConfigFilter, ListProviderConfigResults, UpdateAuthProviderRequest, SAMLConfig, OIDCConfig, OIDCConfigServerResponse, SAMLConfigServerResponse, } from './auth-config'; -import {Tenant, TenantOptions, ListTenantsResult, TenantServerResponse} from './tenant'; +import {TenantManager} from './tenant-manager'; /** @@ -722,7 +722,7 @@ export class TenantAwareAuth extends BaseAuth { */ export class Auth extends BaseAuth implements FirebaseServiceInterface { public INTERNAL: AuthInternals = new AuthInternals(); - private readonly tenantsMap: {[key: string]: TenantAwareAuth}; + private readonly tenantManager_: TenantManager; private readonly app_: FirebaseApp; /** @@ -751,7 +751,7 @@ export class Auth extends BaseAuth implements FirebaseServic new AuthRequestHandler(app), cryptoSignerFromApp(app)); this.app_ = app; - this.tenantsMap = {}; + this.tenantManager_ = new TenantManager(app); } /** @@ -763,107 +763,8 @@ export class Auth extends BaseAuth implements FirebaseServic return this.app_; } - /** - * Returns a TenantAwareAuth instance for the corresponding tenant ID. - * - * @param {string} tenantId The tenant ID whose TenantAwareAuth is to be returned. - * @return {TenantAwareAuth} The corresponding TenantAwareAuth instance. - */ - public forTenant(tenantId: string): TenantAwareAuth { - if (!validator.isNonEmptyString(tenantId)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_TENANT_ID); - } - if (typeof this.tenantsMap[tenantId] === 'undefined') { - this.tenantsMap[tenantId] = new TenantAwareAuth(this.app, tenantId); - } - return this.tenantsMap[tenantId]; - } - - /** - * Looks up the tenant identified by the provided tenant ID and returns a promise that is - * fulfilled with the corresponding tenant if it is found. - * - * @param {string} tenantId The tenant ID of the tenant to look up. - * @return {Promise} A promise that resolves with the corresponding tenant. - */ - public getTenant(tenantId: string): Promise { - return this.authRequestHandler.getTenant(tenantId) - .then((response: TenantServerResponse) => { - return new Tenant(response); - }); - } - - /** - * Exports a batch of tenant accounts. Batch size is determined by the maxResults argument. - * Starting point of the batch is determined by the pageToken argument. - * - * @param {number=} maxResults The page size, 1000 if undefined. This is also the maximum - * allowed limit. - * @param {string=} pageToken The next page token. If not specified, returns users starting - * without any offset. - * @return {Promise<{users: Tenant[], pageToken?: string}>} A promise that resolves with - * the current batch of downloaded tenants and the next page token. For the last page, an - * empty list of tenants and no page token are returned. - */ - public listTenants( - maxResults?: number, - pageToken?: string): Promise { - return this.authRequestHandler.listTenants(maxResults, pageToken) - .then((response: {tenants: TenantServerResponse[], nextPageToken?: string}) => { - // List of tenants to return. - const tenants: Tenant[] = []; - // Convert each user response to a Tenant. - response.tenants.forEach((tenantResponse: TenantServerResponse) => { - tenants.push(new Tenant(tenantResponse)); - }); - // Return list of tenants and the next page token if available. - const result = { - tenants, - pageToken: response.nextPageToken, - }; - // Delete result.pageToken if undefined. - if (typeof result.pageToken === 'undefined') { - delete result.pageToken; - } - return result; - }); - } - - /** - * Deletes the tenant identified by the provided tenant ID and returns a promise that is - * fulfilled when the tenant is found and successfully deleted. - * - * @param {string} tenantId The tenant ID of the tenant to delete. - * @return {Promise} A promise that resolves when the tenant is successfully deleted. - */ - public deleteTenant(tenantId: string): Promise { - return this.authRequestHandler.deleteTenant(tenantId); - } - - /** - * Creates a new tenant with the properties provided. - * - * @param {TenantOptions} tenantOptions The properties to set on the new tenant to be created. - * @return {Promise} A promise that resolves with the newly created tenant. - */ - public createTenant(tenantOptions: TenantOptions): Promise { - return this.authRequestHandler.createTenant(tenantOptions) - .then((response: TenantServerResponse) => { - return new Tenant(response); - }); - } - - /** - * Updates an existing tenant identified by the tenant ID with the properties provided. - * - * @param {string} tenantId The tenant identifier of the tenant to update. - * @param {TenantOptions} tenantOptions The properties to update on the existing tenant. - * @return {Promise} A promise that resolves with the modified tenant. - */ - public updateTenant(tenantId: string, tenantOptions: TenantOptions): Promise { - return this.authRequestHandler.updateTenant(tenantId, tenantOptions) - .then((response: TenantServerResponse) => { - return new Tenant(response); - }); + /** @return The current Auth instance's tenant manager. */ + public tenantManager(): TenantManager { + return this.tenantManager_; } } diff --git a/src/index.d.ts b/src/index.d.ts index 3817208c71..1a72e8bc35 100755 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1928,7 +1928,7 @@ declare namespace admin.auth { * tenant. * * `TenantAwareAuth` instances for a specific `tenantId` can be instantiated by calling - * `auth.forTenant(tenantId)`. + * `auth.tenantManager().authForTenant(tenantId)`. */ interface TenantAwareAuth extends BaseAuth { @@ -1943,12 +1943,30 @@ declare namespace admin.auth { interface Auth extends admin.auth.BaseAuth { app: admin.app.App; + /** + * @return The tenant manager instance associated with the current project. + */ + tenantManager(): admin.auth.TenantManager; + } + + /** + * Defines the tenant manager used to help manage tenant related operations. + * This includes: + *
    + *
  • The ability to create, update, list, get and delete tenants for the underlying + * project.
  • + *
  • Getting a `TenantAwareAuth` instance for running Auth related operations + * (user management, provider configuration management, token verification, + * email link generation, etc) in the context of a specified tenant.
  • + *
+ */ + interface TenantManager { /** * @param tenantId The tenant ID whose `TenantAwareAuth` instance is to be returned. * * @return The `TenantAwareAuth` instance corresponding to this tenant identifier. */ - forTenant(tenantId: string): admin.auth.TenantAwareAuth; + authForTenant(tenantId: string): admin.auth.TenantAwareAuth; /** * Gets the tenant configuration for the tenant corresponding to a given `tenantId`. diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index 1fbe2d81f7..b326dc5685 100755 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -486,13 +486,14 @@ describe('admin.auth', () => { const promises: Array> = []; createdTenants.forEach((tenantId) => { promises.push( - admin.auth().deleteTenant(tenantId).catch((error) => {/** Ignore. */})); + admin.auth().tenantManager().deleteTenant(tenantId) + .catch((error) => {/** Ignore. */})); }); return Promise.all(promises); }); it('createTenant() should resolve with a new tenant', () => { - return admin.auth().createTenant(tenantOptions) + return admin.auth().tenantManager().createTenant(tenantOptions) .then((actualTenant) => { createdTenantId = actualTenant.tenantId; createdTenants.push(createdTenantId); @@ -520,7 +521,7 @@ describe('admin.auth', () => { const rawSalt = 'NaCl'; before(() => { - tenantAwareAuth = admin.auth().forTenant(createdTenantId); + tenantAwareAuth = admin.auth().tenantManager().authForTenant(createdTenantId); }); // Delete test user at the end of test suite. @@ -678,7 +679,7 @@ describe('admin.auth', () => { }; before(() => { - tenantAwareAuth = admin.auth().forTenant(createdTenantId); + tenantAwareAuth = admin.auth().tenantManager().authForTenant(createdTenantId); }); // Delete SAML configuration at the end of test suite. @@ -730,7 +731,7 @@ describe('admin.auth', () => { }; before(() => { - tenantAwareAuth = admin.auth().forTenant(createdTenantId); + tenantAwareAuth = admin.auth().tenantManager().authForTenant(createdTenantId); }); // Delete OIDC configuration at the end of test suite. @@ -766,7 +767,7 @@ describe('admin.auth', () => { }); it('getTenant() should resolve with expected tenant', () => { - return admin.auth().getTenant(createdTenantId) + return admin.auth().tenantManager().getTenant(createdTenantId) .then((actualTenant) => { expect(actualTenant.toJSON()).to.deep.equal(expectedCreatedTenant); }); @@ -787,10 +788,10 @@ describe('admin.auth', () => { passwordRequired: false, }, }; - return admin.auth().updateTenant(createdTenantId, updatedOptions) + return admin.auth().tenantManager().updateTenant(createdTenantId, updatedOptions) .then((actualTenant) => { expect(actualTenant.toJSON()).to.deep.equal(expectedUpdatedTenant); - return admin.auth().updateTenant(createdTenantId, updatedOptions2); + return admin.auth().tenantManager().updateTenant(createdTenantId, updatedOptions2); }) .then((actualTenant) => { expect(actualTenant.toJSON()).to.deep.equal(expectedUpdatedTenant2); @@ -802,7 +803,7 @@ describe('admin.auth', () => { const tenantOptions2 = deepCopy(tenantOptions); tenantOptions2.displayName = 'testTenant2'; const listAllTenantIds = (tenantIds: string[], nextPageToken?: string): Promise => { - return admin.auth().listTenants(100, nextPageToken) + return admin.auth().tenantManager().listTenants(100, nextPageToken) .then((result) => { result.tenants.forEach((tenant) => { tenantIds.push(tenant.tenantId); @@ -812,7 +813,7 @@ describe('admin.auth', () => { } }); }; - return admin.auth().createTenant(tenantOptions2) + return admin.auth().tenantManager().createTenant(tenantOptions2) .then((actualTenant) => { createdTenants.push(actualTenant.tenantId); // Test listTenants returns the expected tenants. @@ -827,9 +828,9 @@ describe('admin.auth', () => { }); it('deleteTenant() should successfully delete the provided tenant', () => { - return admin.auth().deleteTenant(createdTenantId) + return admin.auth().tenantManager().deleteTenant(createdTenantId) .then(() => { - return admin.auth().getTenant(createdTenantId); + return admin.auth().tenantManager().getTenant(createdTenantId); }) .then((result) => { throw new Error('unexpected success'); diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index f9520cc4e2..1e2db9cff7 100755 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -41,7 +41,7 @@ import { OIDCConfigServerResponse, SAMLConfigServerResponse, } from '../../../src/auth/auth-config'; import {deepCopy} from '../../../src/utils/deep-copy'; -import {Tenant, TenantOptions, TenantServerResponse, ListTenantsResult} from '../../../src/auth/tenant'; +import { TenantManager } from '../../../src/auth/tenant-manager'; chai.should(); chai.use(sinonChai); @@ -312,39 +312,17 @@ AUTH_CONFIGS.forEach((testConfig) => { }); }); - describe('Auth.forTenant()', () => { - it('should cache the returned TenantAwareAuth', () => { - const tenantAwareAuth1 = (auth as Auth).forTenant('tenantId1'); - const tenantAwareAuth2 = (auth as Auth).forTenant('tenantId2'); - expect((auth as Auth).forTenant('tenantId1')).to.equal(tenantAwareAuth1); - expect((auth as Auth).forTenant('tenantId2')).to.equal(tenantAwareAuth2); - expect(tenantAwareAuth1).to.not.be.equal(tenantAwareAuth2); - expect(tenantAwareAuth1.tenantId).to.equal('tenantId1'); - expect(tenantAwareAuth2.tenantId).to.equal('tenantId2'); - }); - }); - } else { - // Run tests for TenantAwareAuth. - describe('forTenant()', () => { - const invalidTenantIds = [null, NaN, 0, 1, true, false, '', [], [1, 'a'], {}, { a: 1 }, _.noop]; - invalidTenantIds.forEach((invalidTenantId) => { - it('should throw given invalid tenant ID: ' + JSON.stringify(invalidTenantId), () => { - expect(() => { - const testAuth = new Auth(mockApp); - return testAuth.forTenant(invalidTenantId as any); - }).to.throw('The tenant ID must be a valid non-empty string.'); - }); - }); - - it('should return a TenantAwareAuth with the expected tenant ID', () => { - const testAuth = new Auth(mockApp); - expect(testAuth.forTenant(TENANT_ID).tenantId).to.equal(TENANT_ID); + describe('tenantManager()', () => { + it('should return a TenantManager with the expected attributes', () => { + const tenantManager1 = (auth as Auth).tenantManager(); + const tenantManager2 = new TenantManager(mockApp); + expect(tenantManager1).to.deep.equal(tenantManager2); }); - it('should return a TenantAwareAuth with read-only tenant ID', () => { - expect(() => { - (auth as any).tenantId = 'OTHER_TENANT_ID'; - }).to.throw('Cannot assign to read only property \'tenantId\' of object \'#\''); + it('should return the same cached instance', () => { + const tenantManager1 = (auth as Auth).tenantManager(); + const tenantManager2 = (auth as Auth).tenantManager(); + expect(tenantManager1).to.equal(tenantManager2); }); }); } @@ -3053,505 +3031,6 @@ AUTH_CONFIGS.forEach((testConfig) => { }); }); - if (testConfig.supportsTenantManagement) { - describe('getTenant()', () => { - const tenantId = 'tenant_id'; - const serverResponse: TenantServerResponse = { - name: 'projects/project_id/tenants/tenant_id', - displayName: 'TENANT_DISPLAY_NAME', - allowPasswordSignup: true, - enableEmailLinkSignin: false, - }; - const expectedTenant = new Tenant(serverResponse); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.TENANT_NOT_FOUND); - // Stubs used to simulate underlying API calls. - let stubs: sinon.SinonStub[] = []; - afterEach(() => { - _.forEach(stubs, (stub) => stub.restore()); - stubs = []; - }); - - it('should be rejected given no tenant ID', () => { - return (auth as any).getTenant() - .should.eventually.be.rejected.and.have.property('code', 'auth/invalid-tenant-id'); - }); - - it('should be rejected given an invalid tenant ID', () => { - const invalidTenantId = ''; - return (auth as Auth).getTenant(invalidTenantId) - .then(() => { - throw new Error('Unexpected success'); - }) - .catch((error) => { - expect(error).to.have.property('code', 'auth/invalid-tenant-id'); - }); - }); - - it('should be rejected given an app which returns null access tokens', () => { - return (nullAccessTokenAuth as Auth).getTenant(tenantId) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should be rejected given an app which returns invalid access tokens', () => { - return (malformedAccessTokenAuth as Auth).getTenant(tenantId) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should be rejected given an app which fails to generate access tokens', () => { - return (rejectedPromiseAccessTokenAuth as Auth).getTenant(tenantId) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should resolve with a Tenant on success', () => { - // Stub getTenant to return expected result. - const stub = sinon.stub(testConfig.RequestHandler.prototype, 'getTenant') - .returns(Promise.resolve(serverResponse)); - stubs.push(stub); - return (auth as Auth).getTenant(tenantId) - .then((result) => { - // Confirm underlying API called with expected parameters. - expect(stub).to.have.been.calledOnce.and.calledWith(tenantId); - // Confirm expected tenant returned. - expect(result).to.deep.equal(expectedTenant); - }); - }); - - it('should throw an error when the backend returns an error', () => { - // Stub getTenant to throw a backend error. - const stub = sinon.stub(testConfig.RequestHandler.prototype, 'getTenant') - .returns(Promise.reject(expectedError)); - stubs.push(stub); - return (auth as Auth).getTenant(tenantId) - .then((userRecord) => { - throw new Error('Unexpected success'); - }, (error) => { - // Confirm underlying API called with expected parameters. - expect(stub).to.have.been.calledOnce.and.calledWith(tenantId); - // Confirm expected error returned. - expect(error).to.equal(expectedError); - }); - }); - }); - - describe('listTenants()', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR); - const pageToken = 'PAGE_TOKEN'; - const maxResult = 500; - const listTenantsResponse: any = { - tenants : [ - {name: 'projects/project_id/tenants/tenant_id1'}, - {name: 'projects/project_id/tenants/tenant_id2'}, - ], - nextPageToken: 'NEXT_PAGE_TOKEN', - }; - const expectedResult: ListTenantsResult = { - tenants: [ - new Tenant({name: 'projects/project_id/tenants/tenant_id1'}), - new Tenant({name: 'projects/project_id/tenants/tenant_id2'}), - ], - pageToken: 'NEXT_PAGE_TOKEN', - }; - const emptyListTenantsResponse: any = { - tenants: [], - }; - const emptyExpectedResult: any = { - tenants: [], - }; - // Stubs used to simulate underlying API calls. - let stubs: sinon.SinonStub[] = []; - - afterEach(() => { - _.forEach(stubs, (stub) => stub.restore()); - stubs = []; - }); - - it('should be rejected given an invalid page token', () => { - const invalidToken = {}; - return (auth as Auth).listTenants(undefined, invalidToken as any) - .then(() => { - throw new Error('Unexpected success'); - }) - .catch((error) => { - expect(error).to.have.property('code', 'auth/invalid-page-token'); - }); - }); - - it('should be rejected given an invalid max result', () => { - const invalidResults = 5000; - return (auth as Auth).listTenants(invalidResults) - .then(() => { - throw new Error('Unexpected success'); - }) - .catch((error) => { - expect(error).to.have.property('code', 'auth/argument-error'); - }); - }); - - it('should be rejected given an app which returns null access tokens', () => { - return (nullAccessTokenAuth as Auth).listTenants(maxResult) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should be rejected given an app which returns invalid access tokens', () => { - return (malformedAccessTokenAuth as Auth).listTenants(maxResult) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should be rejected given an app which fails to generate access tokens', () => { - return (rejectedPromiseAccessTokenAuth as Auth).listTenants(maxResult) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should resolve on listTenants request success with tenants in response', () => { - // Stub listTenants to return expected response. - const listTenantsStub = sinon - .stub(testConfig.RequestHandler.prototype, 'listTenants') - .returns(Promise.resolve(listTenantsResponse)); - stubs.push(listTenantsStub); - return (auth as Auth).listTenants(maxResult, pageToken) - .then((response) => { - expect(response).to.deep.equal(expectedResult); - // Confirm underlying API called with expected parameters. - expect(listTenantsStub) - .to.have.been.calledOnce.and.calledWith(maxResult, pageToken); - }); - }); - - it('should resolve on listTenants request success with default options', () => { - // Stub listTenants to return expected response. - const listTenantsStub = sinon - .stub(testConfig.RequestHandler.prototype, 'listTenants') - .returns(Promise.resolve(listTenantsResponse)); - stubs.push(listTenantsStub); - return (auth as Auth).listTenants() - .then((response) => { - expect(response).to.deep.equal(expectedResult); - // Confirm underlying API called with expected parameters. - expect(listTenantsStub) - .to.have.been.calledOnce.and.calledWith(undefined, undefined); - }); - }); - - it('should resolve on listTenants request success with no tenants in response', () => { - // Stub listTenants to return expected response. - const listTenantsStub = sinon - .stub(testConfig.RequestHandler.prototype, 'listTenants') - .returns(Promise.resolve(emptyListTenantsResponse)); - stubs.push(listTenantsStub); - return (auth as Auth).listTenants(maxResult, pageToken) - .then((response) => { - expect(response).to.deep.equal(emptyExpectedResult); - // Confirm underlying API called with expected parameters. - expect(listTenantsStub) - .to.have.been.calledOnce.and.calledWith(maxResult, pageToken); - }); - }); - - it('should throw an error when listTenants returns an error', () => { - // Stub listTenants to throw a backend error. - const listTenantsStub = sinon - .stub(testConfig.RequestHandler.prototype, 'listTenants') - .returns(Promise.reject(expectedError)); - stubs.push(listTenantsStub); - return (auth as Auth).listTenants(maxResult, pageToken) - .then((results) => { - throw new Error('Unexpected success'); - }, (error) => { - // Confirm underlying API called with expected parameters. - expect(listTenantsStub) - .to.have.been.calledOnce.and.calledWith(maxResult, pageToken); - // Confirm expected error returned. - expect(error).to.equal(expectedError); - }); - }); - }); - - describe('deleteTenant()', () => { - const tenantId = 'tenant_id'; - const expectedError = new FirebaseAuthError(AuthClientErrorCode.TENANT_NOT_FOUND); - // Stubs used to simulate underlying API calls. - let stubs: sinon.SinonStub[] = []; - afterEach(() => { - _.forEach(stubs, (stub) => stub.restore()); - stubs = []; - }); - - it('should be rejected given no tenant ID', () => { - return (auth as any).deleteTenant() - .should.eventually.be.rejected.and.have.property('code', 'auth/invalid-tenant-id'); - }); - - it('should be rejected given an invalid tenant ID', () => { - const invalidTenantId = ''; - return (auth as Auth).deleteTenant(invalidTenantId) - .then(() => { - throw new Error('Unexpected success'); - }) - .catch((error) => { - expect(error).to.have.property('code', 'auth/invalid-tenant-id'); - }); - }); - - it('should be rejected given an app which returns null access tokens', () => { - return (nullAccessTokenAuth as Auth).deleteTenant(tenantId) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should be rejected given an app which returns invalid access tokens', () => { - return (malformedAccessTokenAuth as Auth).deleteTenant(tenantId) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should be rejected given an app which fails to generate access tokens', () => { - return (rejectedPromiseAccessTokenAuth as Auth).deleteTenant(tenantId) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should resolve with void on success', () => { - // Stub deleteTenant to return expected result. - const stub = sinon.stub(testConfig.RequestHandler.prototype, 'deleteTenant') - .returns(Promise.resolve()); - stubs.push(stub); - return (auth as Auth).deleteTenant(tenantId) - .then((result) => { - // Confirm underlying API called with expected parameters. - expect(stub).to.have.been.calledOnce.and.calledWith(tenantId); - // Confirm expected result is undefined. - expect(result).to.be.undefined; - }); - }); - - it('should throw an error when the backend returns an error', () => { - // Stub deleteTenant to throw a backend error. - const stub = sinon.stub(testConfig.RequestHandler.prototype, 'deleteTenant') - .returns(Promise.reject(expectedError)); - stubs.push(stub); - return (auth as Auth).deleteTenant(tenantId) - .then((userRecord) => { - throw new Error('Unexpected success'); - }, (error) => { - // Confirm underlying API called with expected parameters. - expect(stub).to.have.been.calledOnce.and.calledWith(tenantId); - // Confirm expected error returned. - expect(error).to.equal(expectedError); - }); - }); - }); - - describe('createTenant()', () => { - const tenantId = 'tenant_id'; - const tenantOptions: TenantOptions = { - displayName: 'TENANT_DISPLAY_NAME', - emailSignInConfig: { - enabled: true, - passwordRequired: true, - }, - }; - const serverResponse: TenantServerResponse = { - name: 'projects/project_id/tenants/tenant_id', - displayName: 'TENANT_DISPLAY_NAME', - allowPasswordSignup: true, - enableEmailLinkSignin: false, - }; - const expectedTenant = new Tenant(serverResponse); - const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, - 'Unable to create the tenant provided.'); - // Stubs used to simulate underlying API calls. - let stubs: sinon.SinonStub[] = []; - afterEach(() => { - _.forEach(stubs, (stub) => stub.restore()); - stubs = []; - }); - - it('should be rejected given no properties', () => { - return (auth as any).createTenant() - .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); - }); - - it('should be rejected given invalid TenantOptions', () => { - return (auth as Auth).createTenant(null) - .then(() => { - throw new Error('Unexpected success'); - }) - .catch((error) => { - expect(error).to.have.property('code', 'auth/argument-error'); - }); - }); - - it('should be rejected given TenantOptions with invalid type property', () => { - // Create tenant using invalid type. This should throw an argument error. - return (auth as Auth).createTenant({type: 'invalid'} as any) - .then(() => { - throw new Error('Unexpected success'); - }) - .catch((error) => { - expect(error).to.have.property('code', 'auth/argument-error'); - }); - }); - - it('should be rejected given an app which returns null access tokens', () => { - return (nullAccessTokenAuth as Auth).createTenant(tenantOptions) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should be rejected given an app which returns invalid access tokens', () => { - return (malformedAccessTokenAuth as Auth).createTenant(tenantOptions) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should be rejected given an app which fails to generate access tokens', () => { - return (rejectedPromiseAccessTokenAuth as Auth).createTenant(tenantOptions) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should resolve with a Tenant on createTenant request success', () => { - // Stub createTenant to return expected result. - const createTenantStub = sinon.stub(testConfig.RequestHandler.prototype, 'createTenant') - .returns(Promise.resolve(serverResponse)); - stubs.push(createTenantStub); - return (auth as Auth).createTenant(tenantOptions) - .then((actualTenant) => { - // Confirm underlying API called with expected parameters. - expect(createTenantStub).to.have.been.calledOnce.and.calledWith(tenantOptions); - // Confirm expected Tenant object returned. - expect(actualTenant).to.deep.equal(expectedTenant); - }); - }); - - it('should throw an error when createTenant returns an error', () => { - // Stub createTenant to throw a backend error. - const createTenantStub = sinon.stub(testConfig.RequestHandler.prototype, 'createTenant') - .returns(Promise.reject(expectedError)); - stubs.push(createTenantStub); - return (auth as Auth).createTenant(tenantOptions) - .then((actualTenant) => { - throw new Error('Unexpected success'); - }, (error) => { - // Confirm underlying API called with expected parameters. - expect(createTenantStub).to.have.been.calledOnce.and.calledWith(tenantOptions); - // Confirm expected error returned. - expect(error).to.equal(expectedError); - }); - }); - }); - - describe('updateTenant()', () => { - const tenantId = 'tenant_id'; - const tenantOptions: TenantOptions = { - displayName: 'TENANT_DISPLAY_NAME', - emailSignInConfig: { - enabled: true, - passwordRequired: true, - }, - }; - const serverResponse: TenantServerResponse = { - name: 'projects/project_id/tenants/tenant_id', - displayName: 'TENANT_DISPLAY_NAME', - allowPasswordSignup: true, - enableEmailLinkSignin: false, - }; - const expectedTenant = new Tenant(serverResponse); - const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, - 'Unable to update the tenant provided.'); - // Stubs used to simulate underlying API calls. - let stubs: sinon.SinonStub[] = []; - afterEach(() => { - _.forEach(stubs, (stub) => stub.restore()); - stubs = []; - }); - - it('should be rejected given no tenant ID', () => { - return (auth as any).updateTenant(undefined, tenantOptions) - .should.eventually.be.rejected.and.have.property('code', 'auth/invalid-tenant-id'); - }); - - it('should be rejected given an invalid tenant ID', () => { - const invalidTenantId = ''; - return (auth as Auth).updateTenant(invalidTenantId, tenantOptions) - .then(() => { - throw new Error('Unexpected success'); - }) - .catch((error) => { - expect(error).to.have.property('code', 'auth/invalid-tenant-id'); - }); - }); - - it('should be rejected given no TenantOptions', () => { - return (auth as any).updateTenant(tenantId) - .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); - }); - - it('should be rejected given invalid TenantOptions', () => { - return (auth as Auth).updateTenant(tenantId, null) - .then(() => { - throw new Error('Unexpected success'); - }) - .catch((error) => { - expect(error).to.have.property('code', 'auth/argument-error'); - }); - }); - - it('should be rejected given TenantOptions with invalid update property', () => { - // Updating the tenantId of an existing tenant will throw an error as tenantId is - // an immutable property. - return (auth as Auth).updateTenant(tenantId, {tenantId: 'unmodifiable'} as any) - .then(() => { - throw new Error('Unexpected success'); - }) - .catch((error) => { - expect(error).to.have.property('code', 'auth/argument-error'); - }); - }); - - it('should be rejected given an app which returns null access tokens', () => { - return (nullAccessTokenAuth as Auth).updateTenant(tenantId, tenantOptions) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should be rejected given an app which returns invalid access tokens', () => { - return (malformedAccessTokenAuth as Auth).updateTenant(tenantId, tenantOptions) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should be rejected given an app which fails to generate access tokens', () => { - return (rejectedPromiseAccessTokenAuth as Auth).updateTenant(tenantId, tenantOptions) - .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); - }); - - it('should resolve with a Tenant on updateTenant request success', () => { - // Stub updateTenant to return expected result. - const updateTenantStub = sinon.stub(testConfig.RequestHandler.prototype, 'updateTenant') - .returns(Promise.resolve(serverResponse)); - stubs.push(updateTenantStub); - return (auth as Auth).updateTenant(tenantId, tenantOptions) - .then((actualTenant) => { - // Confirm underlying API called with expected parameters. - expect(updateTenantStub).to.have.been.calledOnce.and.calledWith(tenantId, tenantOptions); - // Confirm expected Tenant object returned. - expect(actualTenant).to.deep.equal(expectedTenant); - }); - }); - - it('should throw an error when updateTenant returns an error', () => { - // Stub updateTenant to throw a backend error. - const updateTenantStub = sinon.stub(testConfig.RequestHandler.prototype, 'updateTenant') - .returns(Promise.reject(expectedError)); - stubs.push(updateTenantStub); - return (auth as Auth).updateTenant(tenantId, tenantOptions) - .then((actualTenant) => { - throw new Error('Unexpected success'); - }, (error) => { - // Confirm underlying API called with expected parameters. - expect(updateTenantStub).to.have.been.calledOnce.and.calledWith(tenantId, tenantOptions); - // Confirm expected error returned. - expect(error).to.equal(expectedError); - }); - }); - }); - } - if (testConfig.Auth === Auth) { describe('INTERNAL.delete()', () => { it('should delete Auth instance', () => {