mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 05:43:41 +00:00
fix(change-password-prompt) [Auth/PM-22356] Scope org invite email to submitted email (#15783)
Adds a check to make sure that the email on the Org Invite matches the email submitted in the form. If it matches, only then do we apply the org invite to get the MP policies. But if the emails do not match, it means the user attempting to login is no longer the user who originally clicked the emailed org invite link. Therefore, we clear the Org Invite + Deep Link and allow the user to login as normal.
This commit is contained in:
@@ -9,6 +9,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { ResetPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/reset-password-policy-options";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { OrganizationInvite } from "@bitwarden/common/auth/services/organization-invite/organization-invite";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
@@ -86,26 +87,29 @@ describe("WebLoginComponentService", () => {
|
||||
});
|
||||
|
||||
describe("getOrgPoliciesFromOrgInvite", () => {
|
||||
const mockEmail = "test@example.com";
|
||||
const orgInvite: OrganizationInvite = {
|
||||
organizationId: "org-id",
|
||||
token: "token",
|
||||
email: mockEmail,
|
||||
organizationUserId: "org-user-id",
|
||||
initOrganization: false,
|
||||
orgSsoIdentifier: "sso-id",
|
||||
orgUserHasExistingUser: false,
|
||||
organizationName: "org-name",
|
||||
};
|
||||
|
||||
it("returns undefined if organization invite is null", async () => {
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
const result = await service.getOrgPoliciesFromOrgInvite();
|
||||
const result = await service.getOrgPoliciesFromOrgInvite(mockEmail);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("logs an error if getPoliciesByToken throws an error", async () => {
|
||||
const error = new Error("Test error");
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue({
|
||||
organizationId: "org-id",
|
||||
token: "token",
|
||||
email: "email",
|
||||
organizationUserId: "org-user-id",
|
||||
initOrganization: false,
|
||||
orgSsoIdentifier: "sso-id",
|
||||
orgUserHasExistingUser: false,
|
||||
organizationName: "org-name",
|
||||
});
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
policyApiService.getPoliciesByToken.mockRejectedValue(error);
|
||||
await service.getOrgPoliciesFromOrgInvite();
|
||||
await service.getOrgPoliciesFromOrgInvite(mockEmail);
|
||||
expect(logService.error).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
@@ -120,16 +124,7 @@ describe("WebLoginComponentService", () => {
|
||||
const resetPasswordPolicyOptions = new ResetPasswordPolicyOptions();
|
||||
resetPasswordPolicyOptions.autoEnrollEnabled = autoEnrollEnabled;
|
||||
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue({
|
||||
organizationId: "org-id",
|
||||
token: "token",
|
||||
email: "email",
|
||||
organizationUserId: "org-user-id",
|
||||
initOrganization: false,
|
||||
orgSsoIdentifier: "sso-id",
|
||||
orgUserHasExistingUser: false,
|
||||
organizationName: "org-name",
|
||||
});
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
policyApiService.getPoliciesByToken.mockResolvedValue(policies);
|
||||
|
||||
internalPolicyService.getResetPasswordPolicyOptions.mockReturnValue([
|
||||
@@ -141,7 +136,7 @@ describe("WebLoginComponentService", () => {
|
||||
masterPasswordPolicyOptions,
|
||||
);
|
||||
|
||||
const result = await service.getOrgPoliciesFromOrgInvite();
|
||||
const result = await service.getOrgPoliciesFromOrgInvite(mockEmail);
|
||||
|
||||
expect(result).toEqual({
|
||||
policies: policies,
|
||||
@@ -151,5 +146,40 @@ describe("WebLoginComponentService", () => {
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe("given the orgInvite email does not match the provided email", () => {
|
||||
const mockMismatchedEmail = "mismatched@example.com";
|
||||
it("should clear the login redirect URL and organization invite", async () => {
|
||||
// Arrange
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue({
|
||||
...orgInvite,
|
||||
email: mockMismatchedEmail,
|
||||
});
|
||||
|
||||
// Act
|
||||
await service.getOrgPoliciesFromOrgInvite(mockEmail);
|
||||
|
||||
// Assert
|
||||
expect(routerService.getAndClearLoginRedirectUrl).toHaveBeenCalledTimes(1);
|
||||
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should log an error and return undefined", async () => {
|
||||
// Arrange
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue({
|
||||
...orgInvite,
|
||||
email: mockMismatchedEmail,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await service.getOrgPoliciesFromOrgInvite(mockEmail);
|
||||
|
||||
// Assert
|
||||
expect(logService.error).toHaveBeenCalledWith(
|
||||
`WebLoginComponentService.getOrgPoliciesFromOrgInvite: Email mismatch. Expected: ${mockMismatchedEmail}, Received: ${mockEmail}`,
|
||||
);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,10 +66,27 @@ export class WebLoginComponentService
|
||||
return;
|
||||
}
|
||||
|
||||
async getOrgPoliciesFromOrgInvite(): Promise<PasswordPolicies | undefined> {
|
||||
async getOrgPoliciesFromOrgInvite(email: string): Promise<PasswordPolicies | undefined> {
|
||||
const orgInvite = await this.organizationInviteService.getOrganizationInvite();
|
||||
|
||||
if (orgInvite != null) {
|
||||
/**
|
||||
* Check if the email on the org invite matches the email submitted in the login form. This is
|
||||
* important because say userA at "userA@mail.com" clicks an emailed org invite link, but then
|
||||
* on the login page form they change the email to "userB@mail.com". We don't want to apply the org
|
||||
* invite in state to userB. Therefore we clear the login redirect url as well as the org invite,
|
||||
* allowing userB to login as normal.
|
||||
*/
|
||||
if (orgInvite.email !== email.toLowerCase()) {
|
||||
await this.routerService.getAndClearLoginRedirectUrl();
|
||||
await this.organizationInviteService.clearOrganizationInvitation();
|
||||
|
||||
this.logService.error(
|
||||
`WebLoginComponentService.getOrgPoliciesFromOrgInvite: Email mismatch. Expected: ${orgInvite.email}, Received: ${email}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let policies: Policy[];
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user