mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 07:13:32 +00:00
feat(change-password): [PM-18720] (#5319) Change Password Implementation for Non Dialog Cases (#15319)
* feat(change-password-component): Change Password Update [18720] - Very close to complete. * fix(policy-enforcement): [PM-21085] Fix Bug with Policy Enforcement - Removed temp code to force the state I need to verify correctness. * fix(policy-enforcement): [PM-21085] Fix Bug with Policy Enforcement - Recover account working with change password component. * fix(policy-enforcement): [PM-21085] Fix Bug with Policy Enforcement - Made code more dry. * fix(change-password-component): Change Password Update [18720] - Updates to routing and the extension. Extension is still a wip. * fix(change-password-component): Change Password Update [18720] - Extension routing changes. * feat(change-password-component): Change Password Update [18720] - More extension work * feat(change-password-component): Change Password Update [18720] - Pausing work for now while we wait for product to hear back. * feat(change-password-component): Change Password Update [18720] - Removed duplicated anon layouts. * feat(change-password-component): Change Password Update [18720] - Tidied up code. * feat(change-password-component): Change Password Update [18720] - Small fixes to the styling * feat(change-password-component): Change Password Update [18720] - Adding more content for the routing. * feat(change-password-component): Change Password Update [18720] - Removed circular loop for now. * feat(change-password-component): Change Password Update [18720] - Made comments regarding the change password routing complexities with change-password and auth guard. * feat(change-password-component): Change Password Update [18720] - Undid some changes because they will be conflicts later on. * feat(change-password-component): Change Password Update [18720] - Small directive change. * feat(change-password-component): Change Password Update [18720] - Small changes and added some clarification on where I'm blocked * feat(change-password-component): Change Password Update [18720] - Org invite is seemingly working, found one bug to iron out. * refactor(change-password-component): Change Password Update [18720] - Fixed up policy service to be made more clear. * docs(change-password-component): Change Password Update [18720] - Updated documentation. * refactor(change-password-component): Change Password Update [18720] - Routing changes and policy service changes. * fix(change-password-component): Change Password Update [18720] - Wrapping up changes. * feat(change-password-component): Change Password Update [18720] - Should be working fully * feat(change-password-component): Change Password Update [18720] - Found a bug, working on password policy being present on login. * feat(change-password-component): Change Password Update [18720] - Turned on auth guard on other clients for change-password route. * feat(change-password-component): Change Password Update [18720] - Committing intermediate changes. * feat(change-password-component): Change Password Update [18720] - The master password policy endpoint has been added! Should be working. Testing now. * feat(change-password-component): Change Password Update [18720] - Minor fixes. * feat(change-password-component): Change Password Update [18720] - Undid naming change. * feat(change-password-component): Change Password Update [18720] - Removed comment. * feat(change-password-component): Change Password Update [18720] - Removed unneeded code. * fix(change-password-component): Change Password Update [18720] - Took org invite state out of service and made it accessible. * fix(change-password-component): Change Password Update [18720] - Small changes. * fix(change-password-component): Change Password Update [18720] - Split up org invite service into client specific implementations and have them injected into clients properly * feat(change-password-component): Change Password Update [18720] - Stopping work and going to switch to a new branch to pare down some of the solutions that were made to get this over the finish line * feat(change-password-component): Change Password Update [18720] - Started to remove functionality in the login.component and the password login strategy. * feat(change-password-component): Change Password Update [18720] - Removed more unneded changes. * feat(change-password-component): Change Password Update [18720] - Change password clearing state working properly. * fix(change-password-component): Change Password Update [18720] - Added docs and moved web implementation. * comments(change-password-component): Change Password Update [18720] - Added more notes. * test(change-password-component): Change Password Update [18720] - Added in tests for policy service. * comment(change-password-component): Change Password Update [18720] - Updated doc with correct ticket number. * comment(change-password-component): Change Password Update [18720] - Fixed doc. * test(change-password-component): Change Password Update [18720] - Fixed tests. * test(change-password-component): Change Password Update [18720] - Fixed linting errors. Have more tests to fix. * test(change-password-component): Change Password Update [18720] - Added back in ignore for typesafety. * fix(change-password-component): Change Password Update [18720] - Fixed other type issues. * test(change-password-component): Change Password Update [18720] - Fixed tests. * test(change-password-component): Change Password Update [18720] - Fixed more tests. * test(change-password-component): Change Password Update [18720] - Fixed tiny duplicate code. * fix(change-password-component): Change Password Update [18720] - Fixed desktop component. * fix(change-password-component): Change Password Update [18720] - Removed unused code * fix(change-password-component): Change Password Update [18720] - Fixed locales. * fix(change-password-component): Change Password Update [18720] - Removed tracing. * fix(change-password-component): Change Password Update [18720] - Removed duplicative services module entry. * fix(change-password-component): Change Password Update [18720] - Added comment. * fix(change-password-component): Change Password Update [18720] - Fixed unneeded call in two factor to get user id. * fix(change-password-component): Change Password Update [18720] - Fixed a couple of tiny things. * fix(change-password-component): Change Password Update [18720] - Added comment for later fix. * fix(change-password-component): Change Password Update [18720] - Fixed linting error. * PM-18720 - AuthGuard - move call to get isChangePasswordFlagOn down after other conditions for efficiency. * PM-18720 - PasswordLoginStrategy tests - test new feature flagged combine org invite policies logic for weak password evaluation. * PM-18720 - CLI - fix dep issue * PM-18720 - ChangePasswordComp - extract change password warning up out of input password component * PM-18720 - InputPassword - remove unused dependency. * PM-18720 - ChangePasswordComp - add callout dep * PM-18720 - Revert all anon-layout changes * PM-18720 - Anon Layout - finish reverting changes. * PM-18720 - WIP move of change password out of libs/auth * PM-18720 - Clean up remaining imports from moving change password out of libs/auth * PM-18720 - Add change-password barrel file for better import grouping * PM-18720 - Change Password comp - restore maxWidth * PM-18720 - After merge, fix errors * PM-18720 - Desktop - fix api service import * PM-18720 - NDV - fix routing. * PM-18720 - Change Password Comp - add logout service todo * PM-18720 - PasswordSettings - per feedback, component is already feature flagged behind PM16117_ChangeExistingPasswordRefactor so we can just delete the replaced callout (new text is in change-password comp) * PM-18720 - Routing Modules - properly flag new component behind feature flag. * PM-18720 - SSO Login Strategy - fix config service import since it is now in shared deps from main merge. * PM-18720 - Fix SSO login strategy tests * PM-18720 - Default Policy Service - address AC PR feedback --------- Co-authored-by: Jared Snider <jsnider@bitwarden.com> Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
ec015bd253
commit
1f60bcdcc0
@@ -1173,6 +1173,12 @@
|
||||
"message": "Oh no! We couldn't save this. Try entering the details manually.",
|
||||
"description": "Detailed error message shown when saving login details fails."
|
||||
},
|
||||
"changePasswordWarning": {
|
||||
"message": "After changing your password, you will need to log in with your new password. Active sessions on other devices will be logged out within one hour."
|
||||
},
|
||||
"accountRecoveryUpdateMasterPasswordSubtitle": {
|
||||
"message": "Change your master password to complete account recovery."
|
||||
},
|
||||
"enableChangedPasswordNotification": {
|
||||
"message": "Ask to update existing login"
|
||||
},
|
||||
@@ -3454,6 +3460,9 @@
|
||||
"logInRequestSent": {
|
||||
"message": "Request sent"
|
||||
},
|
||||
"masterPasswordChanged": {
|
||||
"message": "Master password saved"
|
||||
},
|
||||
"exposedMasterPassword": {
|
||||
"message": "Exposed Master Password"
|
||||
},
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
tdeDecryptionRequiredGuard,
|
||||
unauthGuardFn,
|
||||
} from "@bitwarden/angular/auth/guards";
|
||||
import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password";
|
||||
import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component";
|
||||
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||
import {
|
||||
@@ -331,7 +332,15 @@ const routes: Routes = [
|
||||
{
|
||||
path: "update-temp-password",
|
||||
component: UpdateTempPasswordComponent,
|
||||
canActivate: [authGuard],
|
||||
canActivate: [
|
||||
canAccessFeature(
|
||||
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
|
||||
false,
|
||||
`/change-password`,
|
||||
false,
|
||||
),
|
||||
authGuard,
|
||||
],
|
||||
data: { elevation: 1 } satisfies RouteDataProperties,
|
||||
},
|
||||
{
|
||||
@@ -555,6 +564,23 @@ const routes: Routes = [
|
||||
showBackButton: true,
|
||||
} satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData,
|
||||
},
|
||||
{
|
||||
path: "change-password",
|
||||
data: {
|
||||
elevation: 1,
|
||||
hideFooter: true,
|
||||
} satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: ChangePasswordComponent,
|
||||
},
|
||||
],
|
||||
canActivate: [
|
||||
canAccessFeature(FeatureFlag.PM16117_ChangeExistingPasswordRefactor),
|
||||
authGuard,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
tdeDecryptionRequiredGuard,
|
||||
unauthGuardFn,
|
||||
} from "@bitwarden/angular/auth/guards";
|
||||
import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password";
|
||||
import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component";
|
||||
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||
import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route";
|
||||
@@ -119,7 +120,15 @@ const routes: Routes = [
|
||||
{
|
||||
path: "update-temp-password",
|
||||
component: UpdateTempPasswordComponent,
|
||||
canActivate: [authGuard],
|
||||
canActivate: [
|
||||
canAccessFeature(
|
||||
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
|
||||
false,
|
||||
`/change-password`,
|
||||
false,
|
||||
),
|
||||
authGuard,
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "remove-password",
|
||||
@@ -340,6 +349,14 @@ const routes: Routes = [
|
||||
},
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
},
|
||||
{
|
||||
path: "change-password",
|
||||
component: ChangePasswordComponent,
|
||||
canActivate: [
|
||||
canAccessFeature(FeatureFlag.PM16117_ChangeExistingPasswordRefactor),
|
||||
authGuard,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -382,12 +382,11 @@ const safeProviders: SafeProvider[] = [
|
||||
provide: SetPasswordJitService,
|
||||
useClass: DesktopSetPasswordJitService,
|
||||
deps: [
|
||||
ApiService,
|
||||
MasterPasswordApiService,
|
||||
KeyService,
|
||||
EncryptService,
|
||||
I18nServiceAbstraction,
|
||||
KdfConfigService,
|
||||
KeyService,
|
||||
MasterPasswordApiService,
|
||||
InternalMasterPasswordServiceAbstraction,
|
||||
OrganizationApiServiceAbstraction,
|
||||
OrganizationUserApiService,
|
||||
|
||||
@@ -84,7 +84,7 @@ export class DesktopLoginComponentService
|
||||
} catch (err) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccured"),
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("ssoError"),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
|
||||
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";
|
||||
@@ -32,52 +31,50 @@ const BroadcasterSubscriptionId = "SetPasswordComponent";
|
||||
})
|
||||
export class SetPasswordComponent extends BaseSetPasswordComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
accountService: AccountService,
|
||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
apiService: ApiService,
|
||||
i18nService: I18nService,
|
||||
keyService: KeyService,
|
||||
messagingService: MessagingService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
policyApiService: PolicyApiServiceAbstraction,
|
||||
policyService: PolicyService,
|
||||
router: Router,
|
||||
masterPasswordApiService: MasterPasswordApiService,
|
||||
syncService: SyncService,
|
||||
route: ActivatedRoute,
|
||||
protected accountService: AccountService,
|
||||
protected dialogService: DialogService,
|
||||
protected encryptService: EncryptService,
|
||||
protected i18nService: I18nService,
|
||||
protected kdfConfigService: KdfConfigService,
|
||||
protected keyService: KeyService,
|
||||
protected masterPasswordApiService: MasterPasswordApiService,
|
||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
protected messagingService: MessagingService,
|
||||
protected organizationApiService: OrganizationApiServiceAbstraction,
|
||||
protected organizationUserApiService: OrganizationUserApiService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected policyApiService: PolicyApiServiceAbstraction,
|
||||
protected policyService: PolicyService,
|
||||
protected route: ActivatedRoute,
|
||||
protected router: Router,
|
||||
protected ssoLoginService: SsoLoginServiceAbstraction,
|
||||
protected syncService: SyncService,
|
||||
protected toastService: ToastService,
|
||||
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
organizationApiService: OrganizationApiServiceAbstraction,
|
||||
organizationUserApiService: OrganizationUserApiService,
|
||||
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
dialogService: DialogService,
|
||||
kdfConfigService: KdfConfigService,
|
||||
encryptService: EncryptService,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
accountService,
|
||||
masterPasswordService,
|
||||
dialogService,
|
||||
encryptService,
|
||||
i18nService,
|
||||
kdfConfigService,
|
||||
keyService,
|
||||
masterPasswordApiService,
|
||||
masterPasswordService,
|
||||
messagingService,
|
||||
organizationApiService,
|
||||
organizationUserApiService,
|
||||
platformUtilsService,
|
||||
policyApiService,
|
||||
policyService,
|
||||
router,
|
||||
masterPasswordApiService,
|
||||
apiService,
|
||||
syncService,
|
||||
route,
|
||||
organizationApiService,
|
||||
organizationUserApiService,
|
||||
userDecryptionOptionsService,
|
||||
router,
|
||||
ssoLoginService,
|
||||
dialogService,
|
||||
kdfConfigService,
|
||||
encryptService,
|
||||
syncService,
|
||||
toastService,
|
||||
userDecryptionOptionsService,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2407,6 +2407,15 @@
|
||||
"updateWeakMasterPasswordWarning": {
|
||||
"message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour."
|
||||
},
|
||||
"changePasswordWarning": {
|
||||
"message": "After changing your password, you will need to log in with your new password. Active sessions on other devices will be logged out within one hour."
|
||||
},
|
||||
"accountRecoveryUpdateMasterPasswordSubtitle": {
|
||||
"message": "Change your master password to complete account recovery."
|
||||
},
|
||||
"updateMasterPasswordSubtitle": {
|
||||
"message": "Your master password does not meet this organization’s requirements. Change your master password to continue."
|
||||
},
|
||||
"tdeDisabledMasterPasswordRequired": {
|
||||
"message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault."
|
||||
},
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { ChangePasswordService, DefaultChangePasswordService } from "@bitwarden/auth/angular";
|
||||
import {
|
||||
ChangePasswordService,
|
||||
DefaultChangePasswordService,
|
||||
} from "@bitwarden/angular/auth/password-management/change-password";
|
||||
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
import { RouterService } from "@bitwarden/web-vault/app/core";
|
||||
import { UserKeyRotationService } from "@bitwarden/web-vault/app/key-management/key-rotation/user-key-rotation.service";
|
||||
|
||||
export class WebChangePasswordService
|
||||
@@ -14,6 +18,7 @@ export class WebChangePasswordService
|
||||
protected masterPasswordApiService: MasterPasswordApiService,
|
||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
private userKeyRotationService: UserKeyRotationService,
|
||||
private routerService: RouterService,
|
||||
) {
|
||||
super(keyService, masterPasswordApiService, masterPasswordService);
|
||||
}
|
||||
@@ -31,4 +36,8 @@ export class WebChangePasswordService
|
||||
newPasswordHint,
|
||||
);
|
||||
}
|
||||
|
||||
async clearDeeplinkState() {
|
||||
await this.routerService.getAndClearLoginRedirectUrl();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ 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 { 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";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -22,7 +24,6 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legac
|
||||
// FIXME: remove `src` and fix import
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { RouterService } from "../../../../../../../../apps/web/src/app/core";
|
||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
||||
|
||||
import { WebLoginComponentService } from "./web-login-component.service";
|
||||
|
||||
@@ -32,7 +33,7 @@ jest.mock("../../../../../utils/flags", () => ({
|
||||
|
||||
describe("WebLoginComponentService", () => {
|
||||
let service: WebLoginComponentService;
|
||||
let acceptOrganizationInviteService: MockProxy<AcceptOrganizationInviteService>;
|
||||
let organizationInviteService: MockProxy<OrganizationInviteService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let policyApiService: MockProxy<PolicyApiServiceAbstraction>;
|
||||
let internalPolicyService: MockProxy<InternalPolicyService>;
|
||||
@@ -44,9 +45,10 @@ describe("WebLoginComponentService", () => {
|
||||
let ssoLoginService: MockProxy<SsoLoginServiceAbstraction>;
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
let accountService: FakeAccountService;
|
||||
let configService: MockProxy<ConfigService>;
|
||||
|
||||
beforeEach(() => {
|
||||
acceptOrganizationInviteService = mock<AcceptOrganizationInviteService>();
|
||||
organizationInviteService = mock<OrganizationInviteService>();
|
||||
logService = mock<LogService>();
|
||||
policyApiService = mock<PolicyApiServiceAbstraction>();
|
||||
internalPolicyService = mock<InternalPolicyService>();
|
||||
@@ -57,12 +59,13 @@ describe("WebLoginComponentService", () => {
|
||||
platformUtilsService = mock<PlatformUtilsService>();
|
||||
ssoLoginService = mock<SsoLoginServiceAbstraction>();
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
configService = mock<ConfigService>();
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
WebLoginComponentService,
|
||||
{ provide: DefaultLoginComponentService, useClass: WebLoginComponentService },
|
||||
{ provide: AcceptOrganizationInviteService, useValue: acceptOrganizationInviteService },
|
||||
{ provide: OrganizationInviteService, useValue: organizationInviteService },
|
||||
{ provide: LogService, useValue: logService },
|
||||
{ provide: PolicyApiServiceAbstraction, useValue: policyApiService },
|
||||
{ provide: InternalPolicyService, useValue: internalPolicyService },
|
||||
@@ -73,6 +76,7 @@ describe("WebLoginComponentService", () => {
|
||||
{ provide: PlatformUtilsService, useValue: platformUtilsService },
|
||||
{ provide: SsoLoginServiceAbstraction, useValue: ssoLoginService },
|
||||
{ provide: AccountService, useValue: accountService },
|
||||
{ provide: ConfigService, useValue: configService },
|
||||
],
|
||||
});
|
||||
service = TestBed.inject(WebLoginComponentService);
|
||||
@@ -84,14 +88,14 @@ describe("WebLoginComponentService", () => {
|
||||
|
||||
describe("getOrgPoliciesFromOrgInvite", () => {
|
||||
it("returns undefined if organization invite is null", async () => {
|
||||
acceptOrganizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
const result = await service.getOrgPoliciesFromOrgInvite();
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("logs an error if getPoliciesByToken throws an error", async () => {
|
||||
const error = new Error("Test error");
|
||||
acceptOrganizationInviteService.getOrganizationInvite.mockResolvedValue({
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue({
|
||||
organizationId: "org-id",
|
||||
token: "token",
|
||||
email: "email",
|
||||
@@ -117,7 +121,7 @@ describe("WebLoginComponentService", () => {
|
||||
const resetPasswordPolicyOptions = new ResetPasswordPolicyOptions();
|
||||
resetPasswordPolicyOptions.autoEnrollEnabled = autoEnrollEnabled;
|
||||
|
||||
acceptOrganizationInviteService.getOrganizationInvite.mockResolvedValue({
|
||||
organizationInviteService.getOrganizationInvite.mockResolvedValue({
|
||||
organizationId: "org-id",
|
||||
token: "token",
|
||||
email: "email",
|
||||
|
||||
@@ -11,18 +11,21 @@ import {
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||
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";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
|
||||
import { RouterService } from "../../../../core/router.service";
|
||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
||||
|
||||
@Injectable()
|
||||
export class WebLoginComponentService
|
||||
@@ -30,7 +33,7 @@ export class WebLoginComponentService
|
||||
implements LoginComponentService
|
||||
{
|
||||
constructor(
|
||||
protected acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
||||
protected organizationInviteService: OrganizationInviteService,
|
||||
protected logService: LogService,
|
||||
protected policyApiService: PolicyApiServiceAbstraction,
|
||||
protected policyService: InternalPolicyService,
|
||||
@@ -42,6 +45,7 @@ export class WebLoginComponentService
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
private router: Router,
|
||||
private accountService: AccountService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
super(
|
||||
cryptoFunctionService,
|
||||
@@ -66,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[];
|
||||
@@ -84,7 +88,7 @@ export class WebLoginComponentService
|
||||
}
|
||||
|
||||
if (policies == null) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions(
|
||||
@@ -95,12 +99,23 @@ export class WebLoginComponentService
|
||||
const isPolicyAndAutoEnrollEnabled =
|
||||
resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled;
|
||||
|
||||
const enforcedPasswordPolicyOptions = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId, policies)),
|
||||
),
|
||||
);
|
||||
let enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions;
|
||||
|
||||
if (
|
||||
await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)
|
||||
) {
|
||||
enforcedPasswordPolicyOptions =
|
||||
this.policyService.combinePoliciesIntoMasterPasswordPolicyOptions(policies);
|
||||
} else {
|
||||
enforcedPasswordPolicyOptions = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) =>
|
||||
this.policyService.masterPasswordPolicyOptions$(userId, policies),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
policies,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
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 { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
|
||||
import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||
|
||||
export class WebOrganizationInviteService implements 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);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ 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 { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
|
||||
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 { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
||||
@@ -25,7 +26,6 @@ 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, KdfConfigService, KeyService } from "@bitwarden/key-management";
|
||||
import { AcceptOrganizationInviteService } from "@bitwarden/web-vault/app/auth/organization-invite/accept-organization.service";
|
||||
import { RouterService } from "@bitwarden/web-vault/app/core";
|
||||
|
||||
import { WebSetInitialPasswordService } from "./web-set-initial-password.service";
|
||||
@@ -43,7 +43,7 @@ describe("WebSetInitialPasswordService", () => {
|
||||
let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>;
|
||||
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
|
||||
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||
let acceptOrganizationInviteService: MockProxy<AcceptOrganizationInviteService>;
|
||||
let organizationInviteService: MockProxy<OrganizationInviteService>;
|
||||
let routerService: MockProxy<RouterService>;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -57,7 +57,7 @@ describe("WebSetInitialPasswordService", () => {
|
||||
organizationApiService = mock<OrganizationApiServiceAbstraction>();
|
||||
organizationUserApiService = mock<OrganizationUserApiService>();
|
||||
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||
acceptOrganizationInviteService = mock<AcceptOrganizationInviteService>();
|
||||
organizationInviteService = mock<OrganizationInviteService>();
|
||||
routerService = mock<RouterService>();
|
||||
|
||||
sut = new WebSetInitialPasswordService(
|
||||
@@ -71,7 +71,7 @@ describe("WebSetInitialPasswordService", () => {
|
||||
organizationApiService,
|
||||
organizationUserApiService,
|
||||
userDecryptionOptionsService,
|
||||
acceptOrganizationInviteService,
|
||||
organizationInviteService,
|
||||
routerService,
|
||||
);
|
||||
});
|
||||
@@ -169,9 +169,7 @@ describe("WebSetInitialPasswordService", () => {
|
||||
|
||||
// Assert
|
||||
expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest);
|
||||
expect(acceptOrganizationInviteService.clearOrganizationInvitation).toHaveBeenCalledTimes(
|
||||
1,
|
||||
);
|
||||
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -201,7 +199,7 @@ describe("WebSetInitialPasswordService", () => {
|
||||
// Assert
|
||||
await expect(promise).rejects.toThrow();
|
||||
expect(masterPasswordApiService.setPassword).not.toHaveBeenCalled();
|
||||
expect(acceptOrganizationInviteService.clearOrganizationInvitation).not.toHaveBeenCalled();
|
||||
expect(organizationInviteService.clearOrganizationInvitation).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,12 +9,12 @@ import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth
|
||||
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 { 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 { UserId } from "@bitwarden/common/types/guid";
|
||||
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
|
||||
import { AcceptOrganizationInviteService } from "@bitwarden/web-vault/app/auth/organization-invite/accept-organization.service";
|
||||
import { RouterService } from "@bitwarden/web-vault/app/core";
|
||||
|
||||
export class WebSetInitialPasswordService
|
||||
@@ -32,7 +32,7 @@ export class WebSetInitialPasswordService
|
||||
protected organizationApiService: OrganizationApiServiceAbstraction,
|
||||
protected organizationUserApiService: OrganizationUserApiService,
|
||||
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
||||
private organizationInviteService: OrganizationInviteService,
|
||||
private routerService: RouterService,
|
||||
) {
|
||||
super(
|
||||
@@ -78,6 +78,6 @@ export class WebSetInitialPasswordService
|
||||
* as clear the org invite itself that was originally set in state by the AcceptOrganizationComponent.
|
||||
*/
|
||||
await this.routerService.getAndClearLoginRedirectUrl();
|
||||
await this.acceptOrganizationInviteService.clearOrganizationInvitation();
|
||||
await this.organizationInviteService.clearOrganizationInvitation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,20 +9,16 @@ 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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.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", () => {
|
||||
@@ -30,32 +26,28 @@ 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>;
|
||||
let configService: MockProxy<ConfigService>;
|
||||
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);
|
||||
configService = mock<ConfigService>();
|
||||
|
||||
service = new WebRegistrationFinishService(
|
||||
keyService,
|
||||
accountApiService,
|
||||
acceptOrgInviteService,
|
||||
organizationInviteService,
|
||||
policyApiService,
|
||||
logService,
|
||||
policyService,
|
||||
accountService,
|
||||
configService,
|
||||
);
|
||||
});
|
||||
@@ -76,21 +68,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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -106,22 +98,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,
|
||||
@@ -131,13 +123,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,
|
||||
@@ -151,14 +143,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,
|
||||
@@ -225,7 +217,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);
|
||||
|
||||
@@ -261,7 +253,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);
|
||||
|
||||
@@ -297,7 +289,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,
|
||||
@@ -338,7 +330,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,
|
||||
@@ -381,7 +373,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,16 +12,14 @@ 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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.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
|
||||
@@ -29,18 +27,17 @@ 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,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
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;
|
||||
}
|
||||
@@ -50,7 +47,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;
|
||||
@@ -115,7 +112,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;
|
||||
|
||||
@@ -5,16 +5,16 @@ import {
|
||||
SetPasswordCredentials,
|
||||
SetPasswordJitService,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
|
||||
|
||||
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);
|
||||
organizationInviteService = inject(OrganizationInviteService);
|
||||
|
||||
override async setPassword(credentials: SetPasswordCredentials) {
|
||||
await super.setPassword(credentials);
|
||||
@@ -22,6 +22,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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { FakeGlobalStateProvider } from "@bitwarden/common/../spec/fake-state-provider";
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
@@ -15,22 +14,18 @@ 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 { 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 +38,8 @@ 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 +52,8 @@ 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 +67,7 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
organizationApiService,
|
||||
organizationUserApiService,
|
||||
i18nService,
|
||||
globalStateProvider,
|
||||
dialogService,
|
||||
organizationInviteService,
|
||||
accountService,
|
||||
);
|
||||
});
|
||||
@@ -103,8 +93,10 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
expect(result).toBe(true);
|
||||
expect(organizationUserApiService.postOrganizationUserAcceptInit).toHaveBeenCalled();
|
||||
expect(apiService.refreshIdentityToken).toHaveBeenCalled();
|
||||
expect(globalState.nextMock).toHaveBeenCalledWith(null);
|
||||
expect(organizationUserApiService.postOrganizationUserAccept).not.toHaveBeenCalled();
|
||||
expect(organizationInviteService.getOrganizationInvite).not.toHaveBeenCalled();
|
||||
expect(organizationInviteService.setOrganizationInvitation).not.toHaveBeenCalled();
|
||||
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalled();
|
||||
expect(authService.logOut).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -121,13 +113,16 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(authService.logOut).toHaveBeenCalled();
|
||||
expect(globalState.nextMock).toHaveBeenCalledWith(invite);
|
||||
expect(organizationInviteService.setOrganizationInvitation).toHaveBeenCalledWith(invite);
|
||||
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("clears the stored invite when a master password policy check is required but the stored invite doesn't match the provided one", async () => {
|
||||
const storedInvite = createOrgInvite({ email: "wrongemail@example.com" });
|
||||
const providedInvite = createOrgInvite();
|
||||
await globalState.update(() => storedInvite);
|
||||
organizationInviteService.getOrganizationInvite.mockReturnValueOnce(
|
||||
Promise.resolve(storedInvite),
|
||||
);
|
||||
policyApiService.getPoliciesByToken.mockResolvedValue([
|
||||
{
|
||||
type: PolicyType.MasterPassword,
|
||||
@@ -139,7 +134,11 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(authService.logOut).toHaveBeenCalled();
|
||||
expect(globalState.nextMock).toHaveBeenCalledWith(providedInvite);
|
||||
expect(organizationInviteService.setOrganizationInvitation).toHaveBeenCalledWith(
|
||||
providedInvite,
|
||||
);
|
||||
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalledWith();
|
||||
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("accepts the invitation request when the organization doesn't have a master password policy", async () => {
|
||||
@@ -151,8 +150,10 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
expect(result).toBe(true);
|
||||
expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled();
|
||||
expect(apiService.refreshIdentityToken).toHaveBeenCalled();
|
||||
expect(globalState.nextMock).toHaveBeenCalledWith(null);
|
||||
expect(organizationUserApiService.postOrganizationUserAcceptInit).not.toHaveBeenCalled();
|
||||
expect(organizationInviteService.setOrganizationInvitation).not.toHaveBeenCalled();
|
||||
expect(organizationInviteService.getOrganizationInvite).not.toHaveBeenCalled();
|
||||
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalled();
|
||||
expect(authService.logOut).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -165,7 +166,7 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
} as Policy,
|
||||
]);
|
||||
// an existing invite means the user has already passed the master password policy
|
||||
await globalState.update(() => invite);
|
||||
organizationInviteService.getOrganizationInvite.mockReturnValueOnce(Promise.resolve(invite));
|
||||
|
||||
policyService.getResetPasswordPolicyOptions.mockReturnValue([
|
||||
{
|
||||
@@ -179,6 +180,8 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
expect(result).toBe(true);
|
||||
expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled();
|
||||
expect(organizationUserApiService.postOrganizationUserAcceptInit).not.toHaveBeenCalled();
|
||||
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalledWith();
|
||||
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalled();
|
||||
expect(authService.logOut).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -202,7 +205,7 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
encryptedString: "encryptedString",
|
||||
} as EncString);
|
||||
|
||||
await globalState.update(() => invite);
|
||||
organizationInviteService.getOrganizationInvite.mockReturnValueOnce(Promise.resolve(invite));
|
||||
|
||||
policyService.getResetPasswordPolicyOptions.mockReturnValue([
|
||||
{
|
||||
@@ -220,6 +223,9 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
);
|
||||
expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled();
|
||||
expect(organizationUserApiService.postOrganizationUserAcceptInit).not.toHaveBeenCalled();
|
||||
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalledTimes(1);
|
||||
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalledWith();
|
||||
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalled();
|
||||
expect(authService.logOut).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -244,8 +244,7 @@ export class ChangePasswordComponent
|
||||
await this.masterPasswordApiService.postPassword(request);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: this.i18nService.t("masterPasswordChanged"),
|
||||
message: this.i18nService.t("masterPasswordChangedDesc"),
|
||||
message: this.i18nService.t("masterPasswordChanged"),
|
||||
});
|
||||
this.messagingService.send("logout");
|
||||
} catch {
|
||||
|
||||
@@ -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,7 +1,6 @@
|
||||
<h1 class="tw-mt-6 tw-mb-2 tw-pb-2.5">{{ "changeMasterPassword" | i18n }}</h1>
|
||||
|
||||
<div class="tw-max-w-lg tw-mb-12">
|
||||
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
|
||||
<auth-change-password [inputPasswordFlow]="inputPasswordFlow"></auth-change-password>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import { Component, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ChangePasswordComponent, InputPasswordFlow } from "@bitwarden/auth/angular";
|
||||
import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password";
|
||||
import { InputPasswordFlow } from "@bitwarden/auth/angular";
|
||||
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { CalloutModule } from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
@@ -16,6 +17,7 @@ import { WebauthnLoginSettingsModule } from "../../webauthn-login-settings";
|
||||
})
|
||||
export class PasswordSettingsComponent implements OnInit {
|
||||
inputPasswordFlow = InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation;
|
||||
changePasswordFeatureFlag = false;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
|
||||
@@ -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,
|
||||
@@ -115,7 +115,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,
|
||||
@@ -174,7 +174,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) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
OrganizationUserApiService,
|
||||
CollectionService,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { ChangePasswordService } from "@bitwarden/angular/auth/password-management/change-password";
|
||||
import { SetInitialPasswordService } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.service.abstraction";
|
||||
import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
|
||||
import {
|
||||
@@ -34,7 +35,6 @@ import {
|
||||
SsoComponentService,
|
||||
LoginDecryptionOptionsService,
|
||||
TwoFactorAuthDuoComponentService,
|
||||
ChangePasswordService,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import {
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
@@ -52,6 +52,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";
|
||||
@@ -108,6 +109,7 @@ import {
|
||||
} from "@bitwarden/key-management";
|
||||
import { LockComponentService } from "@bitwarden/key-management-ui";
|
||||
import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarden/vault";
|
||||
import { WebOrganizationInviteService } from "@bitwarden/web-vault/app/auth/core/services/organization-invite/web-organization-invite.service";
|
||||
|
||||
import { flagEnabled } from "../../utils/flags";
|
||||
import { PolicyListService } from "../admin-console/core/policy-list.service";
|
||||
@@ -122,7 +124,6 @@ import {
|
||||
WebSetInitialPasswordService,
|
||||
} 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";
|
||||
@@ -246,17 +247,21 @@ const safeProviders: SafeProvider[] = [
|
||||
provide: CLIENT_TYPE,
|
||||
useValue: ClientType.Web,
|
||||
}),
|
||||
safeProvider({
|
||||
provide: OrganizationInviteService,
|
||||
useClass: WebOrganizationInviteService,
|
||||
deps: [GlobalStateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: RegistrationFinishServiceAbstraction,
|
||||
useClass: WebRegistrationFinishService,
|
||||
deps: [
|
||||
KeyServiceAbstraction,
|
||||
AccountApiServiceAbstraction,
|
||||
AcceptOrganizationInviteService,
|
||||
OrganizationInviteService,
|
||||
PolicyApiServiceAbstraction,
|
||||
LogService,
|
||||
PolicyService,
|
||||
AccountService,
|
||||
ConfigService,
|
||||
],
|
||||
}),
|
||||
@@ -275,12 +280,11 @@ const safeProviders: SafeProvider[] = [
|
||||
provide: SetPasswordJitService,
|
||||
useClass: WebSetPasswordJitService,
|
||||
deps: [
|
||||
ApiService,
|
||||
MasterPasswordApiService,
|
||||
KeyServiceAbstraction,
|
||||
EncryptService,
|
||||
I18nServiceAbstraction,
|
||||
KdfConfigService,
|
||||
KeyServiceAbstraction,
|
||||
MasterPasswordApiService,
|
||||
InternalMasterPasswordServiceAbstraction,
|
||||
OrganizationApiServiceAbstraction,
|
||||
OrganizationUserApiService,
|
||||
@@ -301,7 +305,7 @@ const safeProviders: SafeProvider[] = [
|
||||
OrganizationApiServiceAbstraction,
|
||||
OrganizationUserApiService,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
AcceptOrganizationInviteService,
|
||||
OrganizationInviteService,
|
||||
RouterService,
|
||||
],
|
||||
}),
|
||||
@@ -314,7 +318,7 @@ const safeProviders: SafeProvider[] = [
|
||||
provide: LoginComponentService,
|
||||
useClass: WebLoginComponentService,
|
||||
deps: [
|
||||
AcceptOrganizationInviteService,
|
||||
OrganizationInviteService,
|
||||
LogService,
|
||||
PolicyApiServiceAbstraction,
|
||||
InternalPolicyService,
|
||||
@@ -326,6 +330,7 @@ const safeProviders: SafeProvider[] = [
|
||||
SsoLoginServiceAbstraction,
|
||||
Router,
|
||||
AccountService,
|
||||
ConfigService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
@@ -378,7 +383,7 @@ const safeProviders: SafeProvider[] = [
|
||||
safeProvider({
|
||||
provide: LoginDecryptionOptionsService,
|
||||
useClass: WebLoginDecryptionOptionsService,
|
||||
deps: [MessagingService, RouterService, AcceptOrganizationInviteService],
|
||||
deps: [MessagingService, RouterService, OrganizationInviteService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: IpcService,
|
||||
@@ -398,6 +403,7 @@ const safeProviders: SafeProvider[] = [
|
||||
MasterPasswordApiService,
|
||||
InternalMasterPasswordServiceAbstraction,
|
||||
UserKeyRotationService,
|
||||
RouterService,
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
unauthGuardFn,
|
||||
activeAuthGuard,
|
||||
} from "@bitwarden/angular/auth/guards";
|
||||
import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password";
|
||||
import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component";
|
||||
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||
import {
|
||||
@@ -144,13 +145,29 @@ const routes: Routes = [
|
||||
{
|
||||
path: "update-temp-password",
|
||||
component: UpdateTempPasswordComponent,
|
||||
canActivate: [authGuard],
|
||||
canActivate: [
|
||||
canAccessFeature(
|
||||
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
|
||||
false,
|
||||
"change-password",
|
||||
false,
|
||||
),
|
||||
authGuard,
|
||||
],
|
||||
data: { titleId: "updateTempPassword" } satisfies RouteDataProperties,
|
||||
},
|
||||
{
|
||||
path: "update-password",
|
||||
component: UpdatePasswordComponent,
|
||||
canActivate: [authGuard],
|
||||
canActivate: [
|
||||
canAccessFeature(
|
||||
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
|
||||
false,
|
||||
"change-password",
|
||||
false,
|
||||
),
|
||||
authGuard,
|
||||
],
|
||||
data: { titleId: "updatePassword" } satisfies RouteDataProperties,
|
||||
},
|
||||
],
|
||||
@@ -580,6 +597,14 @@ const routes: Routes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "change-password",
|
||||
component: ChangePasswordComponent,
|
||||
canActivate: [
|
||||
canAccessFeature(FeatureFlag.PM16117_ChangeExistingPasswordRefactor),
|
||||
authGuard,
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "setup-extension",
|
||||
data: {
|
||||
|
||||
@@ -1785,6 +1785,9 @@
|
||||
"loggedOutWarning": {
|
||||
"message": "Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour."
|
||||
},
|
||||
"changePasswordWarning": {
|
||||
"message": "After changing your password, you will need to log in with your new password. Active sessions on other devices will be logged out within one hour."
|
||||
},
|
||||
"emailChanged": {
|
||||
"message": "Email saved"
|
||||
},
|
||||
@@ -6077,6 +6080,12 @@
|
||||
"updateMasterPassword": {
|
||||
"message": "Update master password"
|
||||
},
|
||||
"accountRecoveryUpdateMasterPasswordSubtitle": {
|
||||
"message": "Change your master password to complete account recovery."
|
||||
},
|
||||
"updateMasterPasswordSubtitle": {
|
||||
"message": "Your master password does not meet this organization’s requirements. Change your master password to continue."
|
||||
},
|
||||
"updateMasterPasswordWarning": {
|
||||
"message": "Your master password was recently changed by an administrator in your organization. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour."
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user