diff --git a/src/auth/tenant.ts b/src/auth/tenant.ts old mode 100644 new mode 100755 index 6d08d9cafb..2b14487e7a --- a/src/auth/tenant.ts +++ b/src/auth/tenant.ts @@ -45,8 +45,8 @@ export interface TenantServerResponse { name: string; type?: TenantServerType; displayName?: string; - allowPasswordSignup: boolean; - enableEmailLinkSignin: boolean; + allowPasswordSignup?: boolean; + enableEmailLinkSignin?: boolean; } /** The interface representing the listTenant API response. */ @@ -181,7 +181,10 @@ export class Tenant { try { this.emailSignInConfig = new EmailSignInConfig(response); } catch (e) { - this.emailSignInConfig = undefined; + // If allowPasswordSignup is undefined, it is disabled by default. + this.emailSignInConfig = new EmailSignInConfig({ + allowPasswordSignup: false, + }); } } diff --git a/src/index.d.ts b/src/index.d.ts index 3cd8de1a19..f144cd4cf7 100755 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -507,7 +507,6 @@ declare namespace admin.auth { providerId: string; /** - * @return A JSON-serializable representation of this object. */ toJSON(): Object; diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts old mode 100644 new mode 100755 index 6cf3ba8040..4f29720292 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -23,11 +23,17 @@ import * as scrypt from 'scrypt'; import firebase from '@firebase/app'; import '@firebase/auth'; import {clone} from 'lodash'; -import {generateRandomString, projectId, apiKey, noServiceAccountApp} from './setup'; +import { + generateRandomString, projectId, apiKey, noServiceAccountApp, cmdArgs, +} from './setup'; import url = require('url'); import * as mocks from '../resources/mocks'; import { AuthProviderConfig } from '../../src/auth/auth-config'; -import { deepExtend } from '../../src/utils/deep-copy'; +import { deepExtend, deepCopy } from '../../src/utils/deep-copy'; + +/* tslint:disable:no-var-requires */ +const chalk = require('chalk'); +/* tslint:enable:no-var-requires */ chai.should(); chai.use(chaiAsPromised); @@ -428,6 +434,153 @@ describe('admin.auth', () => { }); }); + describe('Tenant management operations', () => { + // TODO: Add basic user management tests for multi-tenancy when Auth client SDK starts supporting it. + let createdTenantId: string; + const createdTenants: string[] = []; + const tenantOptions: admin.auth.CreateTenantRequest = { + displayName: 'testTenant1', + type: 'lightweight', + emailSignInConfig: { + enabled: true, + passwordRequired: true, + }, + }; + const expectedCreatedTenant: any = { + displayName: 'testTenant1', + emailSignInConfig: { + enabled: true, + passwordRequired: true, + }, + type: 'lightweight', + }; + const expectedUpdatedTenant: any = { + displayName: 'testTenantUpdated', + emailSignInConfig: { + enabled: false, + passwordRequired: true, + }, + type: 'lightweight', + }; + const expectedUpdatedTenant2: any = { + displayName: 'testTenantUpdated', + emailSignInConfig: { + enabled: true, + passwordRequired: false, + }, + type: 'lightweight', + }; + + // https://mochajs.org/ + // Passing arrow functions (aka "lambdas") to Mocha is discouraged. + // Lambdas lexically bind this and cannot access the Mocha context. + before(function() { + /* tslint:disable:no-console */ + if (!cmdArgs.testMultiTenancy) { + // To enable, run: npm run test:integration -- --testMultiTenancy + // By default we skip multi-tenancy as it is a Google Cloud Identity Platform + // feature only and requires to be enabled via the Cloud Console. + console.log(chalk.yellow(' Skipping multi-tenancy tests.')); + this.skip(); + } + /* tslint:enable:no-console */ + }); + + // Delete test tenants at the end of test suite. + after(() => { + const promises: Array> = []; + createdTenants.forEach((tenantId) => { + promises.push( + admin.auth().deleteTenant(tenantId).catch((error) => {/** Ignore. */})); + }); + return Promise.all(promises); + }); + + it('createTenant() should resolve with a new tenant', () => { + return admin.auth().createTenant(tenantOptions) + .then((actualTenant) => { + createdTenantId = actualTenant.tenantId; + createdTenants.push(createdTenantId); + expectedCreatedTenant.tenantId = createdTenantId; + expect(actualTenant.toJSON()).to.deep.equal(expectedCreatedTenant); + }); + }); + + it('getTenant() should resolve with expected tenant', () => { + return admin.auth().getTenant(createdTenantId) + .then((actualTenant) => { + expect(actualTenant.toJSON()).to.deep.equal(expectedCreatedTenant); + }); + }); + + it('updateTenant() should resolve with the updated tenant', () => { + expectedUpdatedTenant.tenantId = createdTenantId; + expectedUpdatedTenant2.tenantId = createdTenantId; + const updatedOptions: admin.auth.UpdateTenantRequest = { + displayName: expectedUpdatedTenant.displayName, + emailSignInConfig: { + enabled: false, + }, + }; + const updatedOptions2: admin.auth.UpdateTenantRequest = { + emailSignInConfig: { + enabled: true, + passwordRequired: false, + }, + }; + return admin.auth().updateTenant(createdTenantId, updatedOptions) + .then((actualTenant) => { + expect(actualTenant.toJSON()).to.deep.equal(expectedUpdatedTenant); + return admin.auth().updateTenant(createdTenantId, updatedOptions2); + }) + .then((actualTenant) => { + expect(actualTenant.toJSON()).to.deep.equal(expectedUpdatedTenant2); + }); + }); + + it('listTenants() should resolve with expected number of tenants', () => { + const allTenantIds: string[] = []; + const tenantOptions2 = deepCopy(tenantOptions); + tenantOptions2.displayName = 'testTenant2'; + const listAllTenantIds = (tenantIds: string[], nextPageToken?: string): Promise => { + return admin.auth().listTenants(100, nextPageToken) + .then((result) => { + result.tenants.forEach((tenant) => { + tenantIds.push(tenant.tenantId); + }); + if (result.pageToken) { + return listAllTenantIds(tenantIds, result.pageToken); + } + }); + }; + return admin.auth().createTenant(tenantOptions2) + .then((actualTenant) => { + createdTenants.push(actualTenant.tenantId); + // Test listTenants returns the expected tenants. + return listAllTenantIds(allTenantIds); + }) + .then(() => { + // All created tenants should be in the list of tenants. + createdTenants.forEach((tenantId) => { + expect(allTenantIds).to.contain(tenantId); + }); + }); + }); + + it('deleteTenant() should successfully delete the provided tenant', () => { + return admin.auth().deleteTenant(createdTenantId) + .then(() => { + return admin.auth().getTenant(createdTenantId); + }) + .then((result) => { + throw new Error('unexpected success'); + }) + .catch((error) => { + expect(error.code).to.equal('auth/tenant-not-found'); + }); + }); + }); + describe('SAML configuration operations', () => { const authProviderConfig1 = { providerId: 'saml.' + generateRandomString(5), diff --git a/test/unit/auth/tenant.spec.ts b/test/unit/auth/tenant.spec.ts old mode 100644 new mode 100755 index be1a3a576b..a803fdc414 --- a/test/unit/auth/tenant.spec.ts +++ b/test/unit/auth/tenant.spec.ts @@ -247,6 +247,21 @@ describe('Tenant', () => { expect(() => new Tenant(invalidOptions)) .to.throw('INTERNAL ASSERT FAILED: Invalid tenant response'); }); + + it('should set default EmailSignInConfig when allowPasswordSignup is undefined', () => { + const serverResponse: TenantServerResponse = { + name: 'projects/project1/tenants/TENANT_ID', + displayName: 'TENANT_DISPLAY_NAME', + }; + expect(() => { + const tenantWithoutAllowPasswordSignup = new Tenant(serverResponse); + + expect(tenantWithoutAllowPasswordSignup.displayName).to.equal(serverResponse.displayName); + expect(tenantWithoutAllowPasswordSignup.tenantId).to.equal('TENANT_ID'); + expect(tenantWithoutAllowPasswordSignup.emailSignInConfig.enabled).to.be.false; + expect(tenantWithoutAllowPasswordSignup.emailSignInConfig.passwordRequired).to.be.true; + }).not.to.throw(); + }); }); describe('toJSON()', () => {