mirror of
https://github.com/bitwarden/browser
synced 2026-02-06 19:53:59 +00:00
fix(change-password-component): Change Password Update [18720] - Took org invite state out of service and made it accessible.
This commit is contained in:
@@ -4,10 +4,10 @@ import {
|
||||
LoginDecryptionOptionsService,
|
||||
DefaultLoginDecryptionOptionsService,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite-service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
|
||||
import { RouterService } from "../../../../core/router.service";
|
||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
||||
|
||||
export class WebLoginDecryptionOptionsService
|
||||
extends DefaultLoginDecryptionOptionsService
|
||||
@@ -16,7 +16,7 @@ export class WebLoginDecryptionOptionsService
|
||||
constructor(
|
||||
protected messagingService: MessagingService,
|
||||
private routerService: RouterService,
|
||||
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
||||
private organizationInviteService: OrganizationInviteService,
|
||||
) {
|
||||
super(messagingService);
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export class WebLoginDecryptionOptionsService
|
||||
// accepted while being enrolled in admin recovery. So we need to clear
|
||||
// the redirect and stored org invite.
|
||||
await this.routerService.getAndClearLoginRedirectUrl();
|
||||
await this.acceptOrganizationInviteService.clearOrganizationInvitation();
|
||||
await this.organizationInviteService.clearOrganizationInvitation();
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite-service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
@@ -25,7 +26,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
|
||||
import { RouterService } from "../../../../core/router.service";
|
||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
||||
|
||||
@Injectable()
|
||||
export class WebLoginComponentService
|
||||
@@ -33,7 +33,7 @@ export class WebLoginComponentService
|
||||
implements LoginComponentService
|
||||
{
|
||||
constructor(
|
||||
protected acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
||||
protected organizationInviteService: OrganizationInviteService,
|
||||
protected logService: LogService,
|
||||
protected policyApiService: PolicyApiServiceAbstraction,
|
||||
protected policyService: InternalPolicyService,
|
||||
@@ -70,8 +70,8 @@ export class WebLoginComponentService
|
||||
return;
|
||||
}
|
||||
|
||||
async getOrgPoliciesFromOrgInvite(): Promise<PasswordPolicies | null> {
|
||||
const orgInvite = await this.acceptOrganizationInviteService.getOrganizationInvite();
|
||||
async getOrgPoliciesFromOrgInvite(): Promise<PasswordPolicies | undefined> {
|
||||
const orgInvite = await this.organizationInviteService.getOrganizationInvite();
|
||||
|
||||
if (orgInvite != null) {
|
||||
let policies: Policy[];
|
||||
@@ -88,7 +88,7 @@ export class WebLoginComponentService
|
||||
}
|
||||
|
||||
if (policies == null) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions(
|
||||
@@ -104,7 +104,8 @@ export class WebLoginComponentService
|
||||
if (
|
||||
await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)
|
||||
) {
|
||||
enforcedPasswordPolicyOptions = this.policyService.combineMasterPasswordPolicies(policies);
|
||||
enforcedPasswordPolicyOptions =
|
||||
this.policyService.combinePoliciesIntoMasterPasswordPolicyOptions(policies);
|
||||
} else {
|
||||
enforcedPasswordPolicyOptions = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(
|
||||
|
||||
@@ -9,19 +9,15 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service";
|
||||
import { OrganizationInvite } from "@bitwarden/common/auth/services/organization-invite/organization-invite";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite-service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
||||
import { OrganizationInvite } from "../../../organization-invite/organization-invite";
|
||||
|
||||
import { WebRegistrationFinishService } from "./web-registration-finish.service";
|
||||
|
||||
describe("WebRegistrationFinishService", () => {
|
||||
@@ -29,30 +25,26 @@ describe("WebRegistrationFinishService", () => {
|
||||
|
||||
let keyService: MockProxy<KeyService>;
|
||||
let accountApiService: MockProxy<AccountApiService>;
|
||||
let acceptOrgInviteService: MockProxy<AcceptOrganizationInviteService>;
|
||||
let organizationInviteService: MockProxy<OrganizationInviteService>;
|
||||
let policyApiService: MockProxy<PolicyApiServiceAbstraction>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let policyService: MockProxy<PolicyService>;
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
let accountService: FakeAccountService;
|
||||
|
||||
beforeEach(() => {
|
||||
keyService = mock<KeyService>();
|
||||
accountApiService = mock<AccountApiService>();
|
||||
acceptOrgInviteService = mock<AcceptOrganizationInviteService>();
|
||||
organizationInviteService = mock<OrganizationInviteService>();
|
||||
policyApiService = mock<PolicyApiServiceAbstraction>();
|
||||
logService = mock<LogService>();
|
||||
policyService = mock<PolicyService>();
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
|
||||
service = new WebRegistrationFinishService(
|
||||
keyService,
|
||||
accountApiService,
|
||||
acceptOrgInviteService,
|
||||
organizationInviteService,
|
||||
policyApiService,
|
||||
logService,
|
||||
policyService,
|
||||
accountService,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -72,21 +64,21 @@ describe("WebRegistrationFinishService", () => {
|
||||
});
|
||||
|
||||
it("returns null when the org invite is null", async () => {
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
|
||||
const result = await service.getOrgNameFromOrgInvite();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns the organization name from the organization invite when it exists", async () => {
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
|
||||
const result = await service.getOrgNameFromOrgInvite();
|
||||
|
||||
expect(result).toEqual(orgInvite.organizationName);
|
||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -102,22 +94,22 @@ describe("WebRegistrationFinishService", () => {
|
||||
});
|
||||
|
||||
it("returns null when the org invite is null", async () => {
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
|
||||
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns null when the policies are null", async () => {
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
policyApiService.getPoliciesByToken.mockResolvedValue(null);
|
||||
|
||||
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
expect(policyApiService.getPoliciesByToken).toHaveBeenCalledWith(
|
||||
orgInvite.organizationId,
|
||||
orgInvite.token,
|
||||
@@ -127,13 +119,13 @@ describe("WebRegistrationFinishService", () => {
|
||||
});
|
||||
|
||||
it("logs an error and returns null when policies cannot be fetched", async () => {
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
policyApiService.getPoliciesByToken.mockRejectedValue(new Error("error"));
|
||||
|
||||
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
expect(policyApiService.getPoliciesByToken).toHaveBeenCalledWith(
|
||||
orgInvite.organizationId,
|
||||
orgInvite.token,
|
||||
@@ -147,14 +139,14 @@ describe("WebRegistrationFinishService", () => {
|
||||
const masterPasswordPolicies = [new Policy()];
|
||||
const masterPasswordPolicyOptions = new MasterPasswordPolicyOptions();
|
||||
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
policyApiService.getPoliciesByToken.mockResolvedValue(masterPasswordPolicies);
|
||||
policyService.masterPasswordPolicyOptions$.mockReturnValue(of(masterPasswordPolicyOptions));
|
||||
|
||||
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
||||
|
||||
expect(result).toEqual(masterPasswordPolicyOptions);
|
||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
expect(policyApiService.getPoliciesByToken).toHaveBeenCalledWith(
|
||||
orgInvite.organizationId,
|
||||
orgInvite.token,
|
||||
@@ -221,7 +213,7 @@ describe("WebRegistrationFinishService", () => {
|
||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||
accountApiService.registerFinish.mockResolvedValue();
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
|
||||
await service.finishRegistration(email, passwordInputResult, emailVerificationToken);
|
||||
|
||||
@@ -257,7 +249,7 @@ describe("WebRegistrationFinishService", () => {
|
||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||
accountApiService.registerFinish.mockResolvedValue();
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
|
||||
await service.finishRegistration(email, passwordInputResult);
|
||||
|
||||
@@ -293,7 +285,7 @@ describe("WebRegistrationFinishService", () => {
|
||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||
accountApiService.registerFinish.mockResolvedValue();
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
|
||||
await service.finishRegistration(
|
||||
email,
|
||||
@@ -334,7 +326,7 @@ describe("WebRegistrationFinishService", () => {
|
||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||
accountApiService.registerFinish.mockResolvedValue();
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
|
||||
await service.finishRegistration(
|
||||
email,
|
||||
@@ -377,7 +369,7 @@ describe("WebRegistrationFinishService", () => {
|
||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||
accountApiService.registerFinish.mockResolvedValue();
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
|
||||
await service.finishRegistration(
|
||||
email,
|
||||
|
||||
@@ -12,14 +12,12 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { RegisterFinishRequest } from "@bitwarden/common/auth/models/request/registration/register-finish.request";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite-service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
||||
|
||||
export class WebRegistrationFinishService
|
||||
extends DefaultRegistrationFinishService
|
||||
implements RegistrationFinishService
|
||||
@@ -27,17 +25,16 @@ export class WebRegistrationFinishService
|
||||
constructor(
|
||||
protected keyService: KeyService,
|
||||
protected accountApiService: AccountApiService,
|
||||
private acceptOrgInviteService: AcceptOrganizationInviteService,
|
||||
private organizationInviteService: OrganizationInviteService,
|
||||
private policyApiService: PolicyApiServiceAbstraction,
|
||||
private logService: LogService,
|
||||
private policyService: PolicyService,
|
||||
private accountService: AccountService,
|
||||
) {
|
||||
super(keyService, accountApiService);
|
||||
}
|
||||
|
||||
override async getOrgNameFromOrgInvite(): Promise<string | null> {
|
||||
const orgInvite = await this.acceptOrgInviteService.getOrganizationInvite();
|
||||
const orgInvite = await this.organizationInviteService.getOrganizationInvite();
|
||||
if (orgInvite == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -47,7 +44,7 @@ export class WebRegistrationFinishService
|
||||
|
||||
override async getMasterPasswordPolicyOptsFromOrgInvite(): Promise<MasterPasswordPolicyOptions | null> {
|
||||
// If there's a deep linked org invite, use it to get the password policies
|
||||
const orgInvite = await this.acceptOrgInviteService.getOrganizationInvite();
|
||||
const orgInvite = await this.organizationInviteService.getOrganizationInvite();
|
||||
|
||||
if (orgInvite == null) {
|
||||
return null;
|
||||
@@ -100,7 +97,7 @@ export class WebRegistrationFinishService
|
||||
// web specific logic
|
||||
// Org invites are deep linked. Non-existent accounts are redirected to the register page.
|
||||
// Org user id and token are included here only for validation and two factor purposes.
|
||||
const orgInvite = await this.acceptOrgInviteService.getOrganizationInvite();
|
||||
const orgInvite = await this.organizationInviteService.getOrganizationInvite();
|
||||
if (orgInvite != null) {
|
||||
registerRequest.organizationUserId = orgInvite.organizationUserId;
|
||||
registerRequest.orgInviteToken = orgInvite.token;
|
||||
|
||||
@@ -1,20 +1,51 @@
|
||||
import { inject } from "@angular/core";
|
||||
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
DefaultSetPasswordJitService,
|
||||
SetPasswordCredentials,
|
||||
SetPasswordJitService,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite-service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { RouterService } from "../../../../core/router.service";
|
||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
||||
|
||||
export class WebSetPasswordJitService
|
||||
extends DefaultSetPasswordJitService
|
||||
implements SetPasswordJitService
|
||||
{
|
||||
routerService = inject(RouterService);
|
||||
acceptOrganizationInviteService = inject(AcceptOrganizationInviteService);
|
||||
constructor(
|
||||
protected encryptService: EncryptService,
|
||||
protected i18nService: I18nService,
|
||||
protected kdfConfigService: KdfConfigService,
|
||||
protected keyService: KeyService,
|
||||
protected masterPasswordApiService: MasterPasswordApiService,
|
||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
protected organizationApiService: OrganizationApiServiceAbstraction,
|
||||
protected organizationUserApiService: OrganizationUserApiService,
|
||||
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
private organizationInviteService: OrganizationInviteService,
|
||||
) {
|
||||
super(
|
||||
encryptService,
|
||||
i18nService,
|
||||
kdfConfigService,
|
||||
keyService,
|
||||
masterPasswordApiService,
|
||||
masterPasswordService,
|
||||
organizationApiService,
|
||||
organizationUserApiService,
|
||||
userDecryptionOptionsService,
|
||||
);
|
||||
}
|
||||
|
||||
override async setPassword(credentials: SetPasswordCredentials) {
|
||||
await super.setPassword(credentials);
|
||||
@@ -22,6 +53,6 @@ export class WebSetPasswordJitService
|
||||
// SSO JIT accepts org invites when setting their MP, meaning
|
||||
// we can clear the deep linked url for accepting it.
|
||||
await this.routerService.getAndClearLoginRedirectUrl();
|
||||
await this.acceptOrganizationInviteService.clearOrganizationInvitation();
|
||||
await this.organizationInviteService.clearOrganizationInvitation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { OrganizationInvite } from "@bitwarden/common/auth/services/organization-invite/organization-invite";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite-service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
import { BaseAcceptComponent } from "../../common/base.accept.component";
|
||||
|
||||
import { AcceptOrganizationInviteService } from "./accept-organization.service";
|
||||
import { OrganizationInvite } from "./organization-invite";
|
||||
|
||||
@Component({
|
||||
templateUrl: "accept-organization.component.html",
|
||||
@@ -21,18 +22,19 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
|
||||
protected requiredParameters: string[] = ["organizationId", "organizationUserId", "token"];
|
||||
|
||||
constructor(
|
||||
router: Router,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
route: ActivatedRoute,
|
||||
authService: AuthService,
|
||||
protected router: Router,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected i18nService: I18nService,
|
||||
protected route: ActivatedRoute,
|
||||
protected authService: AuthService,
|
||||
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
||||
private organizationInviteService: OrganizationInviteService,
|
||||
) {
|
||||
super(router, platformUtilsService, i18nService, route, authService);
|
||||
}
|
||||
|
||||
async authedHandler(qParams: Params): Promise<void> {
|
||||
const invite = OrganizationInvite.fromParams(qParams);
|
||||
const invite = this.fromParams(qParams);
|
||||
const success = await this.acceptOrganizationInviteService.validateAndAcceptInvite(invite);
|
||||
|
||||
if (!success) {
|
||||
@@ -52,9 +54,9 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
|
||||
}
|
||||
|
||||
async unauthedHandler(qParams: Params): Promise<void> {
|
||||
const invite = OrganizationInvite.fromParams(qParams);
|
||||
const invite = this.fromParams(qParams);
|
||||
|
||||
await this.acceptOrganizationInviteService.setOrganizationInvitation(invite);
|
||||
await this.organizationInviteService.setOrganizationInvitation(invite);
|
||||
await this.navigateInviteAcceptance(invite);
|
||||
}
|
||||
|
||||
@@ -94,4 +96,21 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
private fromParams(params: Params): OrganizationInvite | null {
|
||||
if (params == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new OrganizationInvite(), {
|
||||
email: params.email,
|
||||
initOrganization: params.initOrganization?.toLocaleLowerCase() === "true",
|
||||
orgSsoIdentifier: params.orgSsoIdentifier,
|
||||
orgUserHasExistingUser: params.orgUserHasExistingUser?.toLocaleLowerCase() === "true",
|
||||
organizationId: params.organizationId,
|
||||
organizationName: params.organizationName,
|
||||
organizationUserId: params.organizationUserId,
|
||||
token: params.token,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,22 +15,20 @@ import { ResetPasswordPolicyOptions } from "@bitwarden/common/admin-console/mode
|
||||
import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { OrganizationInvite } from "@bitwarden/common/auth/services/organization-invite/organization-invite";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite-service";
|
||||
import { ORGANIZATION_INVITE } from "@bitwarden/common/auth/services/organization-invite/organization-invite-state";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { FakeGlobalState } from "@bitwarden/common/spec/fake-state";
|
||||
import { OrgKey } from "@bitwarden/common/types/key";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { I18nService } from "../../core/i18n.service";
|
||||
|
||||
import {
|
||||
AcceptOrganizationInviteService,
|
||||
ORGANIZATION_INVITE,
|
||||
} from "./accept-organization.service";
|
||||
import { OrganizationInvite } from "./organization-invite";
|
||||
import { AcceptOrganizationInviteService } from "./accept-organization.service";
|
||||
|
||||
describe("AcceptOrganizationInviteService", () => {
|
||||
let sut: AcceptOrganizationInviteService;
|
||||
@@ -43,10 +41,10 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
let logService: MockProxy<LogService>;
|
||||
let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>;
|
||||
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
|
||||
let organizationInviteService: MockProxy<OrganizationInviteService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
let globalStateProvider: FakeGlobalStateProvider;
|
||||
let globalState: FakeGlobalState<OrganizationInvite>;
|
||||
let dialogService: MockProxy<DialogService>;
|
||||
let accountService: MockProxy<AccountService>;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -59,10 +57,10 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
logService = mock();
|
||||
organizationApiService = mock();
|
||||
organizationUserApiService = mock();
|
||||
organizationInviteService = mock();
|
||||
i18nService = mock();
|
||||
globalStateProvider = new FakeGlobalStateProvider();
|
||||
globalState = globalStateProvider.getFake(ORGANIZATION_INVITE);
|
||||
dialogService = mock();
|
||||
accountService = mock();
|
||||
|
||||
sut = new AcceptOrganizationInviteService(
|
||||
@@ -76,8 +74,7 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
organizationApiService,
|
||||
organizationUserApiService,
|
||||
i18nService,
|
||||
globalStateProvider,
|
||||
dialogService,
|
||||
organizationInviteService,
|
||||
accountService,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -17,36 +17,17 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { OrganizationInvite } from "@bitwarden/common/auth/services/organization-invite/organization-invite";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite-service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import {
|
||||
GlobalState,
|
||||
GlobalStateProvider,
|
||||
KeyDefinition,
|
||||
ORGANIZATION_INVITE_DISK,
|
||||
} from "@bitwarden/common/platform/state";
|
||||
import { OrgKey } from "@bitwarden/common/types/key";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { OrganizationInvite } from "./organization-invite";
|
||||
|
||||
// We're storing the organization invite for 2 reasons:
|
||||
// 1. If the org requires a MP policy check, we need to keep track that the user has already been redirected when they return.
|
||||
// 2. The MP policy check happens on login/register flows, we need to store the token to retrieve the policies then.
|
||||
export const ORGANIZATION_INVITE = new KeyDefinition<OrganizationInvite | null>(
|
||||
ORGANIZATION_INVITE_DISK,
|
||||
"organizationInvite",
|
||||
{
|
||||
deserializer: (invite) => (invite ? OrganizationInvite.fromJSON(invite) : null),
|
||||
},
|
||||
);
|
||||
|
||||
@Injectable()
|
||||
export class AcceptOrganizationInviteService {
|
||||
private organizationInvitationState: GlobalState<OrganizationInvite | null>;
|
||||
private orgNameSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);
|
||||
private policyCache: Policy[];
|
||||
|
||||
@@ -64,34 +45,9 @@ export class AcceptOrganizationInviteService {
|
||||
private readonly organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private readonly organizationUserApiService: OrganizationUserApiService,
|
||||
private readonly i18nService: I18nService,
|
||||
private readonly globalStateProvider: GlobalStateProvider,
|
||||
private readonly dialogService: DialogService,
|
||||
private readonly organizationInviteService: OrganizationInviteService,
|
||||
private readonly accountService: AccountService,
|
||||
) {
|
||||
this.organizationInvitationState = this.globalStateProvider.get(ORGANIZATION_INVITE);
|
||||
}
|
||||
|
||||
/** Returns the currently stored organization invite */
|
||||
async getOrganizationInvite(): Promise<OrganizationInvite | null> {
|
||||
return await firstValueFrom(this.organizationInvitationState.state$);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a new organization invite
|
||||
* @param invite an organization invite
|
||||
* @throws if the invite is nullish
|
||||
*/
|
||||
async setOrganizationInvitation(invite: OrganizationInvite): Promise<void> {
|
||||
if (invite == null) {
|
||||
throw new Error("Invite cannot be null. Use clearOrganizationInvitation instead.");
|
||||
}
|
||||
await this.organizationInvitationState.update(() => invite);
|
||||
}
|
||||
|
||||
/** Clears the currently stored organization invite */
|
||||
async clearOrganizationInvitation(): Promise<void> {
|
||||
await this.organizationInvitationState.update(() => null);
|
||||
}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Validates and accepts the organization invitation if possible.
|
||||
@@ -113,7 +69,7 @@ export class AcceptOrganizationInviteService {
|
||||
|
||||
// Accepting an org invite from existing org
|
||||
if (await this.masterPasswordPolicyCheckRequired(invite)) {
|
||||
await this.setOrganizationInvitation(invite);
|
||||
await this.organizationInviteService.setOrganizationInvitation(invite);
|
||||
this.authService.logOut(() => {
|
||||
/* Do nothing */
|
||||
});
|
||||
@@ -134,7 +90,7 @@ export class AcceptOrganizationInviteService {
|
||||
),
|
||||
);
|
||||
await this.apiService.refreshIdentityToken();
|
||||
await this.clearOrganizationInvitation();
|
||||
await this.organizationInviteService.clearOrganizationInvitation();
|
||||
}
|
||||
|
||||
private async prepareAcceptAndInitRequest(
|
||||
@@ -170,7 +126,7 @@ export class AcceptOrganizationInviteService {
|
||||
);
|
||||
|
||||
await this.apiService.refreshIdentityToken();
|
||||
await this.clearOrganizationInvitation();
|
||||
await this.organizationInviteService.clearOrganizationInvitation();
|
||||
}
|
||||
|
||||
private async prepareAcceptRequest(
|
||||
@@ -224,10 +180,10 @@ export class AcceptOrganizationInviteService {
|
||||
(p) => p.type === PolicyType.MasterPassword && p.enabled,
|
||||
);
|
||||
|
||||
let storedInvite = await this.getOrganizationInvite();
|
||||
let storedInvite = await this.organizationInviteService.getOrganizationInvite();
|
||||
if (storedInvite?.email !== invite.email) {
|
||||
// clear stored invites if the email doesn't match
|
||||
await this.clearOrganizationInvitation();
|
||||
await this.organizationInviteService.clearOrganizationInvitation();
|
||||
storedInvite = null;
|
||||
}
|
||||
// if we don't have an org invite stored, we know the user hasn't been redirected yet to check the MP policy
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Params } from "@angular/router";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
export class OrganizationInvite {
|
||||
email: string;
|
||||
initOrganization: boolean;
|
||||
orgSsoIdentifier: string;
|
||||
orgUserHasExistingUser: boolean;
|
||||
organizationId: string;
|
||||
organizationName: string;
|
||||
organizationUserId: string;
|
||||
token: string;
|
||||
|
||||
static fromJSON(json: Jsonify<OrganizationInvite>): OrganizationInvite | null {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new OrganizationInvite(), json);
|
||||
}
|
||||
|
||||
static fromParams(params: Params): OrganizationInvite | null {
|
||||
if (params == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new OrganizationInvite(), {
|
||||
email: params.email,
|
||||
initOrganization: params.initOrganization?.toLocaleLowerCase() === "true",
|
||||
orgSsoIdentifier: params.orgSsoIdentifier,
|
||||
orgUserHasExistingUser: params.orgUserHasExistingUser?.toLocaleLowerCase() === "true",
|
||||
organizationId: params.organizationId,
|
||||
organizationName: params.organizationName,
|
||||
organizationUserId: params.organizationUserId,
|
||||
token: params.token,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Component, inject } from "@angular/core";
|
||||
|
||||
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite-service";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { RouterService } from "../core";
|
||||
|
||||
import { AcceptOrganizationInviteService } from "./organization-invite/accept-organization.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-set-password",
|
||||
templateUrl: "set-password.component.html",
|
||||
@@ -15,7 +14,7 @@ import { AcceptOrganizationInviteService } from "./organization-invite/accept-or
|
||||
})
|
||||
export class SetPasswordComponent extends BaseSetPasswordComponent {
|
||||
routerService = inject(RouterService);
|
||||
acceptOrganizationInviteService = inject(AcceptOrganizationInviteService);
|
||||
organizationInviteService = inject(OrganizationInviteService);
|
||||
|
||||
protected override async onSetPasswordSuccess(
|
||||
masterKey: MasterKey,
|
||||
@@ -26,6 +25,6 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
|
||||
// SSO JIT accepts org invites when setting their MP, meaning
|
||||
// we can clear the deep linked url for accepting it.
|
||||
await this.routerService.getAndClearLoginRedirectUrl();
|
||||
await this.acceptOrganizationInviteService.clearOrganizationInvitation();
|
||||
await this.organizationInviteService.clearOrganizationInvitation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,34 +43,34 @@ export class ChangePasswordComponent
|
||||
characterMinimumMessage = "";
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
keyService: KeyService,
|
||||
messagingService: MessagingService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
policyService: PolicyService,
|
||||
private auditService: AuditService,
|
||||
private cipherService: CipherService,
|
||||
private syncService: SyncService,
|
||||
private keyRotationService: UserKeyRotationService,
|
||||
private masterPasswordApiService: MasterPasswordApiService,
|
||||
private router: Router,
|
||||
dialogService: DialogService,
|
||||
private syncService: SyncService,
|
||||
private userVerificationService: UserVerificationService,
|
||||
private keyRotationService: UserKeyRotationService,
|
||||
kdfConfigService: KdfConfigService,
|
||||
protected accountService: AccountService,
|
||||
protected dialogService: DialogService,
|
||||
protected i18nService: I18nService,
|
||||
protected kdfConfigService: KdfConfigService,
|
||||
protected keyService: KeyService,
|
||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
accountService: AccountService,
|
||||
toastService: ToastService,
|
||||
protected messagingService: MessagingService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected policyService: PolicyService,
|
||||
protected toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
accountService,
|
||||
dialogService,
|
||||
i18nService,
|
||||
kdfConfigService,
|
||||
keyService,
|
||||
masterPasswordService,
|
||||
messagingService,
|
||||
platformUtilsService,
|
||||
policyService,
|
||||
dialogService,
|
||||
kdfConfigService,
|
||||
masterPasswordService,
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -71,15 +71,15 @@ export class EmergencyAccessTakeoverComponent
|
||||
protected toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
accountService,
|
||||
dialogService,
|
||||
i18nService,
|
||||
kdfConfigService,
|
||||
keyService,
|
||||
masterPasswordService,
|
||||
messagingService,
|
||||
platformUtilsService,
|
||||
policyService,
|
||||
dialogService,
|
||||
kdfConfigService,
|
||||
masterPasswordService,
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Component, inject } from "@angular/core";
|
||||
|
||||
import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "@bitwarden/angular/auth/components/update-password.component";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite-service";
|
||||
|
||||
import { RouterService } from "../core";
|
||||
|
||||
import { AcceptOrganizationInviteService } from "./organization-invite/accept-organization.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-update-password",
|
||||
templateUrl: "update-password.component.html",
|
||||
@@ -13,13 +12,13 @@ import { AcceptOrganizationInviteService } from "./organization-invite/accept-or
|
||||
})
|
||||
export class UpdatePasswordComponent extends BaseUpdatePasswordComponent {
|
||||
private routerService = inject(RouterService);
|
||||
private acceptOrganizationInviteService = inject(AcceptOrganizationInviteService);
|
||||
private organizationInviteService = inject(OrganizationInviteService);
|
||||
|
||||
override async cancel() {
|
||||
// clearing the login redirect url so that the user
|
||||
// does not join the organization if they cancel
|
||||
await this.routerService.getAndClearLoginRedirectUrl();
|
||||
await this.acceptOrganizationInviteService.clearOrganizationInvitation();
|
||||
await this.organizationInviteService.clearOrganizationInvitation();
|
||||
await super.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite-service";
|
||||
import {
|
||||
OrganizationBillingServiceAbstraction as OrganizationBillingService,
|
||||
OrganizationInformation,
|
||||
@@ -31,7 +32,6 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { AcceptOrganizationInviteService } from "../../../auth/organization-invite/accept-organization.service";
|
||||
import {
|
||||
OrganizationCreatedEvent,
|
||||
SubscriptionProduct,
|
||||
@@ -112,7 +112,7 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy {
|
||||
private i18nService: I18nService,
|
||||
private routerService: RouterService,
|
||||
private organizationBillingService: OrganizationBillingService,
|
||||
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
||||
private organizationInviteService: OrganizationInviteService,
|
||||
private toastService: ToastService,
|
||||
private registrationFinishService: RegistrationFinishService,
|
||||
private validationService: ValidationService,
|
||||
@@ -171,7 +171,7 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy {
|
||||
this.setupFamilySponsorship(qParams.sponsorshipToken);
|
||||
});
|
||||
|
||||
const invite = await this.acceptOrganizationInviteService.getOrganizationInvite();
|
||||
const invite = await this.organizationInviteService.getOrganizationInvite();
|
||||
let policies: Policy[] | null = null;
|
||||
|
||||
if (invite != null) {
|
||||
|
||||
@@ -51,6 +51,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite-service";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
@@ -120,7 +121,6 @@ import {
|
||||
LinkSsoService,
|
||||
} from "../auth";
|
||||
import { WebSsoComponentService } from "../auth/core/services/login/web-sso-component.service";
|
||||
import { AcceptOrganizationInviteService } from "../auth/organization-invite/accept-organization.service";
|
||||
import { HtmlStorageService } from "../core/html-storage.service";
|
||||
import { I18nService } from "../core/i18n.service";
|
||||
import { WebFileDownloadService } from "../core/web-file-download.service";
|
||||
@@ -250,11 +250,10 @@ const safeProviders: SafeProvider[] = [
|
||||
deps: [
|
||||
KeyServiceAbstraction,
|
||||
AccountApiServiceAbstraction,
|
||||
AcceptOrganizationInviteService,
|
||||
OrganizationInviteService,
|
||||
PolicyApiServiceAbstraction,
|
||||
LogService,
|
||||
PolicyService,
|
||||
AccountService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
@@ -272,16 +271,16 @@ const safeProviders: SafeProvider[] = [
|
||||
provide: SetPasswordJitService,
|
||||
useClass: WebSetPasswordJitService,
|
||||
deps: [
|
||||
ApiService,
|
||||
MasterPasswordApiService,
|
||||
KeyServiceAbstraction,
|
||||
EncryptService,
|
||||
I18nServiceAbstraction,
|
||||
KdfConfigService,
|
||||
KeyServiceAbstraction,
|
||||
MasterPasswordApiService,
|
||||
InternalMasterPasswordServiceAbstraction,
|
||||
OrganizationApiServiceAbstraction,
|
||||
OrganizationUserApiService,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
OrganizationInviteService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
@@ -293,7 +292,7 @@ const safeProviders: SafeProvider[] = [
|
||||
provide: LoginComponentService,
|
||||
useClass: WebLoginComponentService,
|
||||
deps: [
|
||||
AcceptOrganizationInviteService,
|
||||
OrganizationInviteService,
|
||||
LogService,
|
||||
PolicyApiServiceAbstraction,
|
||||
InternalPolicyService,
|
||||
@@ -358,7 +357,7 @@ const safeProviders: SafeProvider[] = [
|
||||
safeProvider({
|
||||
provide: LoginDecryptionOptionsService,
|
||||
useClass: WebLoginDecryptionOptionsService,
|
||||
deps: [MessagingService, RouterService, AcceptOrganizationInviteService],
|
||||
deps: [MessagingService, RouterService, OrganizationInviteService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: IpcService,
|
||||
|
||||
@@ -37,15 +37,15 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
|
||||
protected destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
protected accountService: AccountService,
|
||||
protected dialogService: DialogService,
|
||||
protected i18nService: I18nService,
|
||||
protected kdfConfigService: KdfConfigService,
|
||||
protected keyService: KeyService,
|
||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
protected messagingService: MessagingService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected policyService: PolicyService,
|
||||
protected dialogService: DialogService,
|
||||
protected kdfConfigService: KdfConfigService,
|
||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
protected accountService: AccountService,
|
||||
protected toastService: ToastService,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
@@ -58,38 +57,37 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
|
||||
ForceSetPasswordReason = ForceSetPasswordReason;
|
||||
|
||||
constructor(
|
||||
accountService: AccountService,
|
||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
i18nService: I18nService,
|
||||
keyService: KeyService,
|
||||
messagingService: MessagingService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
private policyApiService: PolicyApiServiceAbstraction,
|
||||
policyService: PolicyService,
|
||||
protected accountService: AccountService,
|
||||
protected dialogService: DialogService,
|
||||
protected i18nService: I18nService,
|
||||
protected kdfConfigService: KdfConfigService,
|
||||
protected keyService: KeyService,
|
||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
protected messagingService: MessagingService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected policyService: PolicyService,
|
||||
protected router: Router,
|
||||
protected toastService: ToastService,
|
||||
private encryptService: EncryptService,
|
||||
private masterPasswordApiService: MasterPasswordApiService,
|
||||
private apiService: ApiService,
|
||||
private syncService: SyncService,
|
||||
private route: ActivatedRoute,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
private policyApiService: PolicyApiServiceAbstraction,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private ssoLoginService: SsoLoginServiceAbstraction,
|
||||
dialogService: DialogService,
|
||||
kdfConfigService: KdfConfigService,
|
||||
private encryptService: EncryptService,
|
||||
protected toastService: ToastService,
|
||||
private syncService: SyncService,
|
||||
private userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
) {
|
||||
super(
|
||||
accountService,
|
||||
dialogService,
|
||||
i18nService,
|
||||
kdfConfigService,
|
||||
keyService,
|
||||
masterPasswordService,
|
||||
messagingService,
|
||||
platformUtilsService,
|
||||
policyService,
|
||||
dialogService,
|
||||
kdfConfigService,
|
||||
masterPasswordService,
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
}
|
||||
@@ -108,7 +106,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
|
||||
this.masterPasswordService.forceSetPasswordReason$(this.activeUserId),
|
||||
);
|
||||
|
||||
this.route.queryParams
|
||||
this.activatedRoute.queryParams
|
||||
.pipe(
|
||||
first(),
|
||||
switchMap((qParams) => {
|
||||
|
||||
@@ -52,15 +52,15 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
accountService,
|
||||
dialogService,
|
||||
i18nService,
|
||||
kdfConfigService,
|
||||
keyService,
|
||||
masterPasswordService,
|
||||
messagingService,
|
||||
platformUtilsService,
|
||||
policyService,
|
||||
dialogService,
|
||||
kdfConfigService,
|
||||
masterPasswordService,
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,15 +64,15 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent imp
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
accountService,
|
||||
dialogService,
|
||||
i18nService,
|
||||
kdfConfigService,
|
||||
keyService,
|
||||
masterPasswordService,
|
||||
messagingService,
|
||||
platformUtilsService,
|
||||
policyService,
|
||||
dialogService,
|
||||
kdfConfigService,
|
||||
masterPasswordService,
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,20 +68,27 @@ export const authGuard: CanActivateFn = async (
|
||||
return router.createUrlTree(["/set-password"]);
|
||||
}
|
||||
|
||||
// When the PM16117_ChangeExistingPasswordRefactor flag is removed also remove the conditional check
|
||||
// for update-temp-password here. That route will no longer be in effect.
|
||||
if (
|
||||
forceSetPasswordReason !== ForceSetPasswordReason.None &&
|
||||
!routerState.url.includes("update-temp-password") &&
|
||||
!routerState.url.includes("change-password")
|
||||
) {
|
||||
const setInitialPasswordRefactorFlagOn = await configService.getFeatureFlag(
|
||||
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
|
||||
);
|
||||
if (await configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)) {
|
||||
// When the PM16117_ChangeExistingPasswordRefactor flag is removed AS WELL AS the cleanup for
|
||||
// update-temp-password also remove the conditional check for update-temp-password here.
|
||||
// That route will no longer be in effect.
|
||||
if (
|
||||
(forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset ||
|
||||
forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword) &&
|
||||
!routerState.url.includes("update-temp-password") &&
|
||||
!routerState.url.includes("change-password")
|
||||
) {
|
||||
return router.createUrlTree(["/change-password"]);
|
||||
}
|
||||
|
||||
const route = setInitialPasswordRefactorFlagOn ? "/change-password" : "/update-temp-password";
|
||||
|
||||
return router.createUrlTree([route]);
|
||||
// Remove this else condition when taking out the PM16117_ChangeExistingPasswordRefactor flag.
|
||||
} else {
|
||||
if (
|
||||
forceSetPasswordReason !== ForceSetPasswordReason.None &&
|
||||
!routerState.url.includes("update-temp-password")
|
||||
) {
|
||||
return router.createUrlTree(["/update-temp-password"]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1390,12 +1390,11 @@ const safeProviders: SafeProvider[] = [
|
||||
provide: SetPasswordJitService,
|
||||
useClass: DefaultSetPasswordJitService,
|
||||
deps: [
|
||||
ApiServiceAbstraction,
|
||||
MasterPasswordApiServiceAbstraction,
|
||||
KeyService,
|
||||
EncryptService,
|
||||
I18nServiceAbstraction,
|
||||
KdfConfigService,
|
||||
KeyService,
|
||||
MasterPasswordApiServiceAbstraction,
|
||||
InternalMasterPasswordServiceAbstraction,
|
||||
OrganizationApiServiceAbstraction,
|
||||
OrganizationUserApiService,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
@@ -60,7 +60,7 @@ export class ChangePasswordComponent implements OnInit {
|
||||
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
private anonLayoutWrapperDataService: AnonLayoutWrapperDataService,
|
||||
private messagingService: MessagingService,
|
||||
private policyApiService: PolicyApiServiceAbstraction,
|
||||
private policyService: PolicyService,
|
||||
private toastService: ToastService,
|
||||
private syncService: SyncService,
|
||||
private dialogService: DialogService,
|
||||
@@ -81,9 +81,10 @@ export class ChangePasswordComponent implements OnInit {
|
||||
throw new Error("userId not found");
|
||||
}
|
||||
|
||||
this.masterPasswordPolicyOptions = MasterPasswordPolicyOptions.fromResponse(
|
||||
await this.policyApiService.getMasterPasswordPoliciesForAcceptedOrConfirmedUser(),
|
||||
);
|
||||
// New Master Password Policy Options service
|
||||
// this.masterPasswordPolicyOptions = await firstValueFrom(
|
||||
// this.policyService.postAuthenticatedMasterPasswordPolicyOptions$(this.userId)
|
||||
// ) ?? undefined;
|
||||
|
||||
this.forceSetPasswordReason = await firstValueFrom(
|
||||
this.masterPasswordService.forceSetPasswordReason$(this.userId),
|
||||
|
||||
@@ -17,8 +17,10 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { ClientType, HttpStatusCode } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
@@ -124,6 +126,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
private logService: LogService,
|
||||
private validationService: ValidationService,
|
||||
private loginSuccessHandlerService: LoginSuccessHandlerService,
|
||||
private masterPasswordService: MasterPasswordServiceAbstraction,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
this.clientType = this.platformUtilsService.getClientType();
|
||||
@@ -333,8 +336,17 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
// The AuthGuard will handle routing to update-temp-password based on state
|
||||
|
||||
if (
|
||||
!(await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor))
|
||||
await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)
|
||||
) {
|
||||
const forceSetPasswordReason: ForceSetPasswordReason = await firstValueFrom(
|
||||
this.masterPasswordService.forceSetPasswordReason$(authResult.userId),
|
||||
);
|
||||
|
||||
if (forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword) {
|
||||
await this.router.navigate(["change-password"]);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// TODO: PM-18269 - evaluate if we can combine this with the
|
||||
// password evaluation done in the password login strategy.
|
||||
// If there's an existing org invite, use it to get the org's password policies
|
||||
|
||||
@@ -2,11 +2,17 @@ import { CommonModule } from "@angular/common";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
import { firstValueFrom, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { LoginSuccessHandlerService } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
@@ -61,6 +67,9 @@ export class NewDeviceVerificationComponent implements OnInit, OnDestroy {
|
||||
private logService: LogService,
|
||||
private i18nService: I18nService,
|
||||
private loginSuccessHandlerService: LoginSuccessHandlerService,
|
||||
private configService: ConfigService,
|
||||
private accountService: AccountService,
|
||||
private masterPasswordService: MasterPasswordServiceAbstraction,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -141,8 +150,27 @@ export class NewDeviceVerificationComponent implements OnInit, OnDestroy {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.loginSuccessHandlerService.run(authResult.userId);
|
||||
|
||||
// If verification succeeds, navigate to vault
|
||||
await this.router.navigate(["/vault"]);
|
||||
// TODO: PM-22663 use the new service to handle routing.
|
||||
if (
|
||||
await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)
|
||||
) {
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getUserId),
|
||||
);
|
||||
|
||||
const forceSetPasswordReason = await firstValueFrom(
|
||||
this.masterPasswordService.forceSetPasswordReason$(activeUserId),
|
||||
);
|
||||
|
||||
if (
|
||||
forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword ||
|
||||
forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset
|
||||
) {
|
||||
await this.router.navigate(["/change-password"]);
|
||||
}
|
||||
} else {
|
||||
await this.router.navigate(["/vault"]);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
let errorMessage =
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
OrganizationUserResetPasswordEnrollmentRequest,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
@@ -31,12 +30,11 @@ import {
|
||||
|
||||
export class DefaultSetPasswordJitService implements SetPasswordJitService {
|
||||
constructor(
|
||||
protected apiService: ApiService,
|
||||
protected masterPasswordApiService: MasterPasswordApiService,
|
||||
protected keyService: KeyService,
|
||||
protected encryptService: EncryptService,
|
||||
protected i18nService: I18nService,
|
||||
protected kdfConfigService: KdfConfigService,
|
||||
protected keyService: KeyService,
|
||||
protected masterPasswordApiService: MasterPasswordApiService,
|
||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
protected organizationApiService: OrganizationApiServiceAbstraction,
|
||||
protected organizationUserApiService: OrganizationUserApiService,
|
||||
|
||||
@@ -32,7 +32,10 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -169,6 +172,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
||||
private loginSuccessHandlerService: LoginSuccessHandlerService,
|
||||
private twoFactorAuthComponentCacheService: TwoFactorAuthComponentCacheService,
|
||||
private authService: AuthService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -506,6 +510,24 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
||||
return "lock";
|
||||
}
|
||||
|
||||
// TODO: PM-22663 use the new service to handle routing.
|
||||
if (
|
||||
await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)
|
||||
) {
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
|
||||
const forceSetPasswordReason = await firstValueFrom(
|
||||
this.masterPasswordService.forceSetPasswordReason$(activeUserId),
|
||||
);
|
||||
|
||||
if (
|
||||
forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword ||
|
||||
forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset
|
||||
) {
|
||||
return "change-password";
|
||||
}
|
||||
}
|
||||
|
||||
return "vault";
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +163,8 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||
credentials: PasswordLoginCredentials,
|
||||
authResult: AuthResult,
|
||||
): Promise<void> {
|
||||
// TODO: PM-21084 - investigate if we should be sending down masterPasswordPolicy on the IdentityDeviceVerificationResponse like we do for the IdentityTwoFactorResponse
|
||||
// TODO: PM-21084 - investigate if we should be sending down masterPasswordPolicy on the
|
||||
// IdentityDeviceVerificationResponse like we do for the IdentityTwoFactorResponse
|
||||
// If the response is a device verification response, we don't need to evaluate the password
|
||||
if (identityResponse instanceof IdentityDeviceVerificationResponse) {
|
||||
return;
|
||||
@@ -175,11 +176,11 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||
if (
|
||||
await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)
|
||||
) {
|
||||
// Either take credentials from a potential org invite first, then take from
|
||||
// the identity response if that doesn't exist.
|
||||
masterPasswordPolicyOptions = credentials.masterPasswordPoliciesFromOrgInvite
|
||||
? credentials.masterPasswordPoliciesFromOrgInvite
|
||||
: this.getMasterPasswordPolicyOptionsFromResponse(identityResponse);
|
||||
// Get the master password policy options from both the org invite and the identity response
|
||||
masterPasswordPolicyOptions = this.policyService.combineMasterPasswordPolicyOptions(
|
||||
credentials.masterPasswordPoliciesFromOrgInvite,
|
||||
this.getMasterPasswordPolicyOptionsFromResponse(identityResponse),
|
||||
);
|
||||
|
||||
if (!masterPasswordPolicyOptions?.enforceOnLogin) {
|
||||
return;
|
||||
@@ -208,6 +209,8 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||
return;
|
||||
}
|
||||
|
||||
// Also set master password policy options here
|
||||
|
||||
// Authentication was successful, save the force update password options with the state service
|
||||
// if there isn't already a reason set (this would only be AdminForcePasswordReset as that can be set server side
|
||||
// and would have already been processed in the base login strategy processForceSetPasswordReason method)
|
||||
|
||||
@@ -51,13 +51,24 @@ export abstract class PolicyService {
|
||||
) => Observable<MasterPasswordPolicyOptions | undefined>;
|
||||
|
||||
/**
|
||||
* Combines all Master Password policies that are passed in.
|
||||
* Combines all Master Password policies that are passed in and returns
|
||||
* back the strongest combination of all the policies in the form of a
|
||||
* MasterPasswordPolicyOptions.
|
||||
* @param policies
|
||||
*/
|
||||
abstract combineMasterPasswordPolicies(
|
||||
abstract combinePoliciesIntoMasterPasswordPolicyOptions(
|
||||
policies: Policy[],
|
||||
): MasterPasswordPolicyOptions | undefined;
|
||||
|
||||
/**
|
||||
* Takes an arbitrary amount of Master Password Policy options in any form and merges them
|
||||
* together using the strictest combination of all of them.
|
||||
* @param masterPasswordPolicyOptions
|
||||
*/
|
||||
abstract combineMasterPasswordPolicyOptions(
|
||||
...masterPasswordPolicyOptions: MasterPasswordPolicyOptions[]
|
||||
): MasterPasswordPolicyOptions | undefined;
|
||||
|
||||
/**
|
||||
* Evaluates whether a proposed Master Password complies with all Master Password policies that apply to the user.
|
||||
*/
|
||||
|
||||
@@ -19,16 +19,7 @@ export class MasterPasswordPolicyOptions extends Domain {
|
||||
enforceOnLogin = false;
|
||||
|
||||
static fromResponse(policy: MasterPasswordPolicyResponse): MasterPasswordPolicyOptions {
|
||||
// Check if the policy is null or if all the values in the response object is null.
|
||||
// Exclude the response object because the MasterPasswordPolicyResponse extends
|
||||
// BaseResponse and we should omit that when checking for null values. Doing this
|
||||
// programmatically makes this less brittle for future contract changes.
|
||||
if (
|
||||
policy == null ||
|
||||
Object.entries(policy)
|
||||
.filter(([key]) => key !== "response")
|
||||
.every(([, value]) => value == null)
|
||||
) {
|
||||
if (policy == null) {
|
||||
return null;
|
||||
}
|
||||
const options = new MasterPasswordPolicyOptions();
|
||||
|
||||
@@ -87,10 +87,14 @@ export class DefaultPolicyService implements PolicyService {
|
||||
policies?: Policy[],
|
||||
): Observable<MasterPasswordPolicyOptions | undefined> {
|
||||
const policies$ = policies ? of(policies) : this.policies$(userId);
|
||||
return policies$.pipe(map((obsPolicies) => this.combineMasterPasswordPolicies(obsPolicies)));
|
||||
return policies$.pipe(
|
||||
map((obsPolicies) => this.combinePoliciesIntoMasterPasswordPolicyOptions(obsPolicies)),
|
||||
);
|
||||
}
|
||||
|
||||
combineMasterPasswordPolicies(policies: Policy[]): MasterPasswordPolicyOptions | undefined {
|
||||
combinePoliciesIntoMasterPasswordPolicyOptions(
|
||||
policies: Policy[],
|
||||
): MasterPasswordPolicyOptions | undefined {
|
||||
let enforcedOptions: MasterPasswordPolicyOptions | undefined = undefined;
|
||||
const filteredPolicies = policies.filter((p) => p.type === PolicyType.MasterPassword) ?? [];
|
||||
|
||||
@@ -100,51 +104,35 @@ export class DefaultPolicyService implements PolicyService {
|
||||
|
||||
filteredPolicies.forEach((currentPolicy) => {
|
||||
if (!currentPolicy.enabled || !currentPolicy.data) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!enforcedOptions) {
|
||||
enforcedOptions = new MasterPasswordPolicyOptions();
|
||||
}
|
||||
|
||||
if (
|
||||
currentPolicy.data.minComplexity != null &&
|
||||
currentPolicy.data.minComplexity > enforcedOptions.minComplexity
|
||||
) {
|
||||
enforcedOptions.minComplexity = currentPolicy.data.minComplexity;
|
||||
}
|
||||
|
||||
if (
|
||||
currentPolicy.data.minLength != null &&
|
||||
currentPolicy.data.minLength > enforcedOptions.minLength
|
||||
) {
|
||||
enforcedOptions.minLength = currentPolicy.data.minLength;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireUpper) {
|
||||
enforcedOptions.requireUpper = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireLower) {
|
||||
enforcedOptions.requireLower = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireNumbers) {
|
||||
enforcedOptions.requireNumbers = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.requireSpecial) {
|
||||
enforcedOptions.requireSpecial = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.enforceOnLogin) {
|
||||
enforcedOptions.enforceOnLogin = true;
|
||||
}
|
||||
this.mergeMasterPasswordPolicyOptions(enforcedOptions, currentPolicy.data);
|
||||
});
|
||||
|
||||
return enforcedOptions;
|
||||
}
|
||||
|
||||
combineMasterPasswordPolicyOptions(
|
||||
...policies: MasterPasswordPolicyOptions[]
|
||||
): MasterPasswordPolicyOptions | undefined {
|
||||
let combinedOptions: MasterPasswordPolicyOptions | undefined = undefined;
|
||||
|
||||
policies.forEach((currentOptions) => {
|
||||
if (!combinedOptions) {
|
||||
combinedOptions = new MasterPasswordPolicyOptions();
|
||||
}
|
||||
|
||||
this.mergeMasterPasswordPolicyOptions(combinedOptions, currentOptions);
|
||||
});
|
||||
|
||||
return combinedOptions;
|
||||
}
|
||||
|
||||
evaluateMasterPassword(
|
||||
passwordStrength: number,
|
||||
newPassword: string,
|
||||
@@ -240,4 +228,26 @@ export class DefaultPolicyService implements PolicyService {
|
||||
return organization.canManagePolicies;
|
||||
}
|
||||
}
|
||||
|
||||
private mergeMasterPasswordPolicyOptions(
|
||||
target: MasterPasswordPolicyOptions | undefined,
|
||||
source: MasterPasswordPolicyOptions | undefined,
|
||||
) {
|
||||
if (!target) {
|
||||
target = new MasterPasswordPolicyOptions();
|
||||
}
|
||||
|
||||
if (source) {
|
||||
target.minComplexity = Math.max(
|
||||
target.minComplexity,
|
||||
source.minComplexity ?? target.minComplexity,
|
||||
);
|
||||
target.minLength = Math.max(target.minLength, source.minLength ?? target.minLength);
|
||||
target.requireUpper = target.requireUpper || source.requireUpper;
|
||||
target.requireLower = target.requireLower || source.requireLower;
|
||||
target.requireNumbers = target.requireNumbers || source.requireNumbers;
|
||||
target.requireSpecial = target.requireSpecial || source.requireSpecial;
|
||||
target.enforceOnLogin = target.enforceOnLogin || source.enforceOnLogin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { OrganizationInvite } from "@bitwarden/common/auth/services/organization-invite/organization-invite";
|
||||
import { ORGANIZATION_INVITE } from "@bitwarden/common/auth/services/organization-invite/organization-invite-state";
|
||||
import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||
|
||||
export class OrganizationInviteService {
|
||||
private organizationInvitationState: GlobalState<OrganizationInvite | null>;
|
||||
|
||||
constructor(private readonly globalStateProvider: GlobalStateProvider) {
|
||||
this.organizationInvitationState = this.globalStateProvider.get(ORGANIZATION_INVITE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently stored organization invite
|
||||
*/
|
||||
async getOrganizationInvite(): Promise<OrganizationInvite | null> {
|
||||
return await firstValueFrom(this.organizationInvitationState.state$);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a new organization invite
|
||||
* @param invite an organization invite
|
||||
* @throws if the invite is nullish
|
||||
*/
|
||||
async setOrganizationInvitation(invite: OrganizationInvite): Promise<void> {
|
||||
if (invite == null) {
|
||||
throw new Error("Invite cannot be null. Use clearOrganizationInvitation instead.");
|
||||
}
|
||||
await this.organizationInvitationState.update(() => invite);
|
||||
}
|
||||
|
||||
/** Clears the currently stored organization invite */
|
||||
async clearOrganizationInvitation(): Promise<void> {
|
||||
await this.organizationInvitationState.update(() => null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { OrganizationInvite } from "@bitwarden/common/auth/services/organization-invite/organization-invite";
|
||||
import { KeyDefinition, ORGANIZATION_INVITE_DISK } from "@bitwarden/common/platform/state";
|
||||
|
||||
// We're storing the organization invite for 2 reasons:
|
||||
// 1. If the org requires a MP policy check, we need to keep track that the user has already been redirected when they return.
|
||||
// 2. The MP policy check happens on login/register flows, we need to store the token to retrieve the policies then.
|
||||
export const ORGANIZATION_INVITE = new KeyDefinition<OrganizationInvite | null>(
|
||||
ORGANIZATION_INVITE_DISK,
|
||||
"organizationInvite",
|
||||
{
|
||||
deserializer: (invite) => (invite ? OrganizationInvite.fromJSON(invite) : null),
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
export class OrganizationInvite {
|
||||
email?: string;
|
||||
initOrganization?: boolean;
|
||||
orgSsoIdentifier?: string;
|
||||
orgUserHasExistingUser?: boolean;
|
||||
organizationId?: string;
|
||||
organizationName?: string;
|
||||
organizationUserId?: string;
|
||||
token?: string;
|
||||
|
||||
static fromJSON(json: Jsonify<OrganizationInvite>): OrganizationInvite | null {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new OrganizationInvite(), json);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user