Skip to content
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
9 changes: 6 additions & 3 deletions src/auth/tenant.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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,
});
}
}

Expand Down
1 change: 0 additions & 1 deletion src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,6 @@ declare namespace admin.auth {
providerId: string;

/**

* @return A JSON-serializable representation of this object.
*/
toJSON(): Object;
Expand Down
157 changes: 155 additions & 2 deletions test/integration/auth.spec.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<Promise<any>> = [];
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<void> => {
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),
Expand Down
15 changes: 15 additions & 0 deletions test/unit/auth/tenant.spec.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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()', () => {
Expand Down