Multi-tenancy changes to shared Auth API requests#539
Multi-tenancy changes to shared Auth API requests#539hiranya911 merged 4 commits intoauth-multi-tenancyfrom
Conversation
…s related to user management APIs and SAML/OIDC config mgmt APIs, link generation, etc. Defines FirebaseAuthRequestHandler which extends the base class for project level only calls and which will also be extended to include tenant mgmt APIs. Defines FirebaseTenantRequestHandler which extends the base class for tenant level only calls. Unit tests have been modified to run tests on both subclasses.
| phoneNumber: true, | ||
| customAttributes: true, | ||
| validSince: true, | ||
| tenantId: true, |
There was a problem hiding this comment.
What does the boolean value mean here?
There was a problem hiding this comment.
These are the list of allowed parameters to pass to the backend for create/edit requests (it's a quick way to generate a Set). If the developer passes other parameters, they will be either ignored or an error thrown.
| } | ||
| } | ||
| if (typeof request.tenantId !== 'undefined' && | ||
| !validator.isNonEmptyString(request.tenantId)) { |
There was a problem hiding this comment.
Shouldn't the check be the mismatch between tenantId in the request and the tenantId in the user?
There was a problem hiding this comment.
For now, I am only doing basic validation (ensure tenant ID is non-empty string when provided). I am leaving the check to the backend to enforce the match. I wasn't sure that uploadAccount allows uploading users with different tenants. That is why I am not checking mismatch. For now, I will rely on backend check. I added the mismatch tenant ID backend error catching I think i can followup with a mismatch check client side.
There was a problem hiding this comment.
There probably won't be a mismatch tenant ID backend error. For UploadAccount you can always rely on backend returning error messages in the response for specific failed users. For the other endpoints like SetAccountInfo and SignupNewUsers, the backend won't be able to return the error since it only receives one tenant_id from the request as I mentioned in a separate email thread. Adding the client side check in a follow-up change sounds good to me.
There was a problem hiding this comment.
I see. Yeah I will add the mismatch check in a follow up.
hiranya911
left a comment
There was a problem hiding this comment.
Tests are somewhat complicated to review. I trust it's just the existing test cases configured to run with both handler implementations.
Just a couple of suggestions to improve readability and the design.
src/auth/auth-api-request.ts
Outdated
| constructor(protected projectId: string, protected version: string, protected tenantId: string) { | ||
| super(projectId, version); | ||
| // Inject tenant path in URL. | ||
| this.urlFormat = this.urlFormat.replace( |
There was a problem hiding this comment.
It looks like urlFormat in the parent class is really just a constant. Can we just define a constant for this as well?
src/auth/auth-api-request.ts
Outdated
| private readonly httpClient: AuthorizedHttpClient; | ||
| private readonly authUrlBuilder: AuthResourceUrlBuilder; | ||
| private readonly projectConfigUrlBuilder: AuthResourceUrlBuilder; | ||
| export class BaseFirebaseAuthRequestHandler { |
There was a problem hiding this comment.
Can we make this class abstract and rename it to AbstractAuthRequestHandler?
getAuthUrlBuilder() and getProjectConfigUrlBuilder() could be abstract methods of the class.
src/auth/auth-api-request.ts
Outdated
| * tenant management related APIs. This extends the BaseFirebaseAuthRequestHandler class and defines | ||
| * additional tenant management related APIs. | ||
| */ | ||
| export class FirebaseAuthRequestHandler extends BaseFirebaseAuthRequestHandler { |
There was a problem hiding this comment.
My suggestions for names would be AuthRequestHandler and TenantAwareAuthRequestHandler.
bojeil-google
left a comment
There was a problem hiding this comment.
Regarding the test changes, this is basically what I am doing:
Old test:
describe('FirebaseAuthRequestHandler', () => {
describe('getAccountInfoByUid', () => {
const path = '/v1/projects/project_id/accounts:lookup';
const method = 'POST';
it('should be fulfilled given a valid localId', () => {
const expectedResult = utils.responseFrom({
users : [
{localId: 'uid'},
],
});
const data = {localId: ['uid']};
const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedResult);
stubs.push(stub);
const requestHandler = new FirebaseAuthRequestHandler(mockApp);
return requestHandler.getAccountInfoByUid('uid')
.then((result) => {
expect(result).to.deep.equal(expectedResult.data);
expect(stub).to.have.been.calledOnce.and.calledWith(callParams(path, method, data));
});
});
});
});
Changed test:
const AUTH_REQUEST_HANDLER_TESTS: HandlerTest[] = [
{
name: 'FirebaseAuthRequestHandler',
init: (app: FirebaseApp) => {
return new AuthRequestHandler(app);
},
path: (version: string, api: string, projectId: string) => {
return `/${version}/projects/${projectId}${api}`;
},
supportsTenantManagement: true,
},
{
name: 'FirebaseTenantRequestHandler',
init: (app: FirebaseApp) => {
return new TenantAwareAuthRequestHandler(app, TENANT_ID);
},
path: (version: string, api: string, projectId: string) => {
return `/${version}/projects/${projectId}/tenants/${TENANT_ID}${api}`;
},
supportsTenantManagement: false,
},
];
const TENANT_ID = 'tenantId';
AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => {
describe(handler.name, () => {
describe('getAccountInfoByUid', () => {
const path = handler.path('v1', '/accounts:lookup', 'project_id');
const method = 'POST';
it('should be fulfilled given a valid localId', () => {
const expectedResult = utils.responseFrom({
users : [
{localId: 'uid'},
],
});
const data = {localId: ['uid']};
const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedResult);
stubs.push(stub);
const requestHandler = handler.init(mockApp);
return requestHandler.getAccountInfoByUid('uid')
.then((result) => {
expect(result).to.deep.equal(expectedResult.data);
expect(stub).to.have.been.calledOnce.and.calledWith(callParams(path, method, data));
});
});
});
});
The main changes is that I introduced AUTH_REQUEST_HANDLER_TESTS to test both subclasses.
Each test I get the associated handler and get path to check in request by calling handler.path().
In addition, instead of initializing const requestHandler = new AuthRequestHandler(mockApp) manually, I use const requestHandler = handler.init(mockApp).
The logic is the same. However, GitHub's diff gets super confused from these changes. Note that Visual Studio Code does a great job catching the actual changes.
src/auth/auth-api-request.ts
Outdated
| constructor(protected projectId: string, protected version: string, protected tenantId: string) { | ||
| super(projectId, version); | ||
| // Inject tenant path in URL. | ||
| this.urlFormat = this.urlFormat.replace( |
| phoneNumber: true, | ||
| customAttributes: true, | ||
| validSince: true, | ||
| tenantId: true, |
There was a problem hiding this comment.
These are the list of allowed parameters to pass to the backend for create/edit requests (it's a quick way to generate a Set). If the developer passes other parameters, they will be either ignored or an error thrown.
| } | ||
| } | ||
| if (typeof request.tenantId !== 'undefined' && | ||
| !validator.isNonEmptyString(request.tenantId)) { |
There was a problem hiding this comment.
For now, I am only doing basic validation (ensure tenant ID is non-empty string when provided). I am leaving the check to the backend to enforce the match. I wasn't sure that uploadAccount allows uploading users with different tenants. That is why I am not checking mismatch. For now, I will rely on backend check. I added the mismatch tenant ID backend error catching I think i can followup with a mismatch check client side.
src/auth/auth-api-request.ts
Outdated
| * tenant management related APIs. This extends the BaseFirebaseAuthRequestHandler class and defines | ||
| * additional tenant management related APIs. | ||
| */ | ||
| export class FirebaseAuthRequestHandler extends BaseFirebaseAuthRequestHandler { |
src/auth/auth-api-request.ts
Outdated
| private readonly httpClient: AuthorizedHttpClient; | ||
| private readonly authUrlBuilder: AuthResourceUrlBuilder; | ||
| private readonly projectConfigUrlBuilder: AuthResourceUrlBuilder; | ||
| export class BaseFirebaseAuthRequestHandler { |
hiranya911
left a comment
There was a problem hiding this comment.
Looks great. Just one suggestion to keep couple of attributes as readonly.
src/auth/auth-api-request.ts
Outdated
| protected readonly projectId: string; | ||
| protected readonly httpClient: AuthorizedHttpClient; | ||
| protected authUrlBuilder: AuthResourceUrlBuilder; | ||
| protected projectConfigUrlBuilder: AuthResourceUrlBuilder; |
There was a problem hiding this comment.
We can keep these 2 properties readonly. Lets initialize them in the constructor of the abstract class, by calling the abstract methods:
this.authUrlBuilder = this.getAuthUrlBuilder();
this.projectConfigUrlBuilder = this.getProjectConfigUrlBuilder();
There was a problem hiding this comment.
You can call the abstract methods newAuthUrlBuilder() and newProjectConfigUrlBuilder() if that sounds better.
src/auth/auth-api-request.ts
Outdated
| /** | ||
| * @return {AuthResourceUrlBuilder} The Auth user management resource URL builder. | ||
| */ | ||
| protected getAuthUrlBuilder(): AuthResourceUrlBuilder { |
There was a problem hiding this comment.
Once we make the authUrlBuilder readonly, these method implementation will become simplified:
protected getAuthUrlBuilder() {
return new AuthResourceUrlBuilder(this.projectId, 'v1');
}
Defines BaseFirebaseAuthRequestHandler class for sending Auth requests related to user management APIs and SAML/OIDC config mgmt APIs, link generation, etc.
Defines FirebaseAuthRequestHandler which extends the base class for project level only calls and which will also be extended to include tenant mgmt APIs.
Defines FirebaseTenantRequestHandler which extends the base class for tenant level only calls.
Unit tests have been modified to run tests on both subclasses. 99% of the unit test logic is the same. It has been tweaked to accept a common interface handler. 2 tests are run for each. One for FirebaseAuthRequestHandler and another for FirebaseTenantRequestHandler.