mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +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.",
|
"message": "Oh no! We couldn't save this. Try entering the details manually.",
|
||||||
"description": "Detailed error message shown when saving login details fails."
|
"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": {
|
"enableChangedPasswordNotification": {
|
||||||
"message": "Ask to update existing login"
|
"message": "Ask to update existing login"
|
||||||
},
|
},
|
||||||
@@ -3454,6 +3460,9 @@
|
|||||||
"logInRequestSent": {
|
"logInRequestSent": {
|
||||||
"message": "Request sent"
|
"message": "Request sent"
|
||||||
},
|
},
|
||||||
|
"masterPasswordChanged": {
|
||||||
|
"message": "Master password saved"
|
||||||
|
},
|
||||||
"exposedMasterPassword": {
|
"exposedMasterPassword": {
|
||||||
"message": "Exposed Master Password"
|
"message": "Exposed Master Password"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
tdeDecryptionRequiredGuard,
|
tdeDecryptionRequiredGuard,
|
||||||
unauthGuardFn,
|
unauthGuardFn,
|
||||||
} from "@bitwarden/angular/auth/guards";
|
} 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 { 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 { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||||
import {
|
import {
|
||||||
@@ -331,7 +332,15 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "update-temp-password",
|
path: "update-temp-password",
|
||||||
component: UpdateTempPasswordComponent,
|
component: UpdateTempPasswordComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [
|
||||||
|
canAccessFeature(
|
||||||
|
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
|
||||||
|
false,
|
||||||
|
`/change-password`,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
authGuard,
|
||||||
|
],
|
||||||
data: { elevation: 1 } satisfies RouteDataProperties,
|
data: { elevation: 1 } satisfies RouteDataProperties,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -555,6 +564,23 @@ const routes: Routes = [
|
|||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
} satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData,
|
} 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,
|
tdeDecryptionRequiredGuard,
|
||||||
unauthGuardFn,
|
unauthGuardFn,
|
||||||
} from "@bitwarden/angular/auth/guards";
|
} 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 { 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 { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||||
import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route";
|
import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route";
|
||||||
@@ -119,7 +120,15 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "update-temp-password",
|
path: "update-temp-password",
|
||||||
component: UpdateTempPasswordComponent,
|
component: UpdateTempPasswordComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [
|
||||||
|
canAccessFeature(
|
||||||
|
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
|
||||||
|
false,
|
||||||
|
`/change-password`,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
authGuard,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "remove-password",
|
path: "remove-password",
|
||||||
@@ -340,6 +349,14 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "change-password",
|
||||||
|
component: ChangePasswordComponent,
|
||||||
|
canActivate: [
|
||||||
|
canAccessFeature(FeatureFlag.PM16117_ChangeExistingPasswordRefactor),
|
||||||
|
authGuard,
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -382,12 +382,11 @@ const safeProviders: SafeProvider[] = [
|
|||||||
provide: SetPasswordJitService,
|
provide: SetPasswordJitService,
|
||||||
useClass: DesktopSetPasswordJitService,
|
useClass: DesktopSetPasswordJitService,
|
||||||
deps: [
|
deps: [
|
||||||
ApiService,
|
|
||||||
MasterPasswordApiService,
|
|
||||||
KeyService,
|
|
||||||
EncryptService,
|
EncryptService,
|
||||||
I18nServiceAbstraction,
|
I18nServiceAbstraction,
|
||||||
KdfConfigService,
|
KdfConfigService,
|
||||||
|
KeyService,
|
||||||
|
MasterPasswordApiService,
|
||||||
InternalMasterPasswordServiceAbstraction,
|
InternalMasterPasswordServiceAbstraction,
|
||||||
OrganizationApiServiceAbstraction,
|
OrganizationApiServiceAbstraction,
|
||||||
OrganizationUserApiService,
|
OrganizationUserApiService,
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export class DesktopLoginComponentService
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
title: this.i18nService.t("errorOccured"),
|
title: this.i18nService.t("errorOccurred"),
|
||||||
message: this.i18nService.t("ssoError"),
|
message: this.i18nService.t("ssoError"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { ActivatedRoute, Router } from "@angular/router";
|
|||||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||||
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
|
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
|
||||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
import { 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 { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { 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 {
|
export class SetPasswordComponent extends BaseSetPasswordComponent implements OnInit, OnDestroy {
|
||||||
constructor(
|
constructor(
|
||||||
accountService: AccountService,
|
protected accountService: AccountService,
|
||||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
protected dialogService: DialogService,
|
||||||
apiService: ApiService,
|
protected encryptService: EncryptService,
|
||||||
i18nService: I18nService,
|
protected i18nService: I18nService,
|
||||||
keyService: KeyService,
|
protected kdfConfigService: KdfConfigService,
|
||||||
messagingService: MessagingService,
|
protected keyService: KeyService,
|
||||||
platformUtilsService: PlatformUtilsService,
|
protected masterPasswordApiService: MasterPasswordApiService,
|
||||||
policyApiService: PolicyApiServiceAbstraction,
|
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||||
policyService: PolicyService,
|
protected messagingService: MessagingService,
|
||||||
router: Router,
|
protected organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
masterPasswordApiService: MasterPasswordApiService,
|
protected organizationUserApiService: OrganizationUserApiService,
|
||||||
syncService: SyncService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
route: ActivatedRoute,
|
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 broadcasterService: BroadcasterService,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
organizationApiService: OrganizationApiServiceAbstraction,
|
|
||||||
organizationUserApiService: OrganizationUserApiService,
|
|
||||||
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
|
||||||
ssoLoginService: SsoLoginServiceAbstraction,
|
|
||||||
dialogService: DialogService,
|
|
||||||
kdfConfigService: KdfConfigService,
|
|
||||||
encryptService: EncryptService,
|
|
||||||
toastService: ToastService,
|
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
accountService,
|
accountService,
|
||||||
masterPasswordService,
|
dialogService,
|
||||||
|
encryptService,
|
||||||
i18nService,
|
i18nService,
|
||||||
|
kdfConfigService,
|
||||||
keyService,
|
keyService,
|
||||||
|
masterPasswordApiService,
|
||||||
|
masterPasswordService,
|
||||||
messagingService,
|
messagingService,
|
||||||
|
organizationApiService,
|
||||||
|
organizationUserApiService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
policyApiService,
|
policyApiService,
|
||||||
policyService,
|
policyService,
|
||||||
router,
|
|
||||||
masterPasswordApiService,
|
|
||||||
apiService,
|
|
||||||
syncService,
|
|
||||||
route,
|
route,
|
||||||
organizationApiService,
|
router,
|
||||||
organizationUserApiService,
|
|
||||||
userDecryptionOptionsService,
|
|
||||||
ssoLoginService,
|
ssoLoginService,
|
||||||
dialogService,
|
syncService,
|
||||||
kdfConfigService,
|
|
||||||
encryptService,
|
|
||||||
toastService,
|
toastService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2407,6 +2407,15 @@
|
|||||||
"updateWeakMasterPasswordWarning": {
|
"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."
|
"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": {
|
"tdeDisabledMasterPasswordRequired": {
|
||||||
"message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault."
|
"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 { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
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 { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
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";
|
import { UserKeyRotationService } from "@bitwarden/web-vault/app/key-management/key-rotation/user-key-rotation.service";
|
||||||
|
|
||||||
export class WebChangePasswordService
|
export class WebChangePasswordService
|
||||||
@@ -14,6 +18,7 @@ export class WebChangePasswordService
|
|||||||
protected masterPasswordApiService: MasterPasswordApiService,
|
protected masterPasswordApiService: MasterPasswordApiService,
|
||||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||||
private userKeyRotationService: UserKeyRotationService,
|
private userKeyRotationService: UserKeyRotationService,
|
||||||
|
private routerService: RouterService,
|
||||||
) {
|
) {
|
||||||
super(keyService, masterPasswordApiService, masterPasswordService);
|
super(keyService, masterPasswordApiService, masterPasswordService);
|
||||||
}
|
}
|
||||||
@@ -31,4 +36,8 @@ export class WebChangePasswordService
|
|||||||
newPasswordHint,
|
newPasswordHint,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async clearDeeplinkState() {
|
||||||
|
await this.routerService.getAndClearLoginRedirectUrl();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import {
|
|||||||
LoginDecryptionOptionsService,
|
LoginDecryptionOptionsService,
|
||||||
DefaultLoginDecryptionOptionsService,
|
DefaultLoginDecryptionOptionsService,
|
||||||
} from "@bitwarden/auth/angular";
|
} 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 { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
|
||||||
import { RouterService } from "../../../../core/router.service";
|
import { RouterService } from "../../../../core/router.service";
|
||||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
|
||||||
|
|
||||||
export class WebLoginDecryptionOptionsService
|
export class WebLoginDecryptionOptionsService
|
||||||
extends DefaultLoginDecryptionOptionsService
|
extends DefaultLoginDecryptionOptionsService
|
||||||
@@ -16,7 +16,7 @@ export class WebLoginDecryptionOptionsService
|
|||||||
constructor(
|
constructor(
|
||||||
protected messagingService: MessagingService,
|
protected messagingService: MessagingService,
|
||||||
private routerService: RouterService,
|
private routerService: RouterService,
|
||||||
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
private organizationInviteService: OrganizationInviteService,
|
||||||
) {
|
) {
|
||||||
super(messagingService);
|
super(messagingService);
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ export class WebLoginDecryptionOptionsService
|
|||||||
// accepted while being enrolled in admin recovery. So we need to clear
|
// accepted while being enrolled in admin recovery. So we need to clear
|
||||||
// the redirect and stored org invite.
|
// the redirect and stored org invite.
|
||||||
await this.routerService.getAndClearLoginRedirectUrl();
|
await this.routerService.getAndClearLoginRedirectUrl();
|
||||||
await this.acceptOrganizationInviteService.clearOrganizationInvitation();
|
await this.organizationInviteService.clearOrganizationInvitation();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(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 { ResetPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/reset-password-policy-options";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.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 { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.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 { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.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
|
// FIXME: remove `src` and fix import
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { RouterService } from "../../../../../../../../apps/web/src/app/core";
|
import { RouterService } from "../../../../../../../../apps/web/src/app/core";
|
||||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
|
||||||
|
|
||||||
import { WebLoginComponentService } from "./web-login-component.service";
|
import { WebLoginComponentService } from "./web-login-component.service";
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ jest.mock("../../../../../utils/flags", () => ({
|
|||||||
|
|
||||||
describe("WebLoginComponentService", () => {
|
describe("WebLoginComponentService", () => {
|
||||||
let service: WebLoginComponentService;
|
let service: WebLoginComponentService;
|
||||||
let acceptOrganizationInviteService: MockProxy<AcceptOrganizationInviteService>;
|
let organizationInviteService: MockProxy<OrganizationInviteService>;
|
||||||
let logService: MockProxy<LogService>;
|
let logService: MockProxy<LogService>;
|
||||||
let policyApiService: MockProxy<PolicyApiServiceAbstraction>;
|
let policyApiService: MockProxy<PolicyApiServiceAbstraction>;
|
||||||
let internalPolicyService: MockProxy<InternalPolicyService>;
|
let internalPolicyService: MockProxy<InternalPolicyService>;
|
||||||
@@ -44,9 +45,10 @@ describe("WebLoginComponentService", () => {
|
|||||||
let ssoLoginService: MockProxy<SsoLoginServiceAbstraction>;
|
let ssoLoginService: MockProxy<SsoLoginServiceAbstraction>;
|
||||||
const mockUserId = Utils.newGuid() as UserId;
|
const mockUserId = Utils.newGuid() as UserId;
|
||||||
let accountService: FakeAccountService;
|
let accountService: FakeAccountService;
|
||||||
|
let configService: MockProxy<ConfigService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
acceptOrganizationInviteService = mock<AcceptOrganizationInviteService>();
|
organizationInviteService = mock<OrganizationInviteService>();
|
||||||
logService = mock<LogService>();
|
logService = mock<LogService>();
|
||||||
policyApiService = mock<PolicyApiServiceAbstraction>();
|
policyApiService = mock<PolicyApiServiceAbstraction>();
|
||||||
internalPolicyService = mock<InternalPolicyService>();
|
internalPolicyService = mock<InternalPolicyService>();
|
||||||
@@ -57,12 +59,13 @@ describe("WebLoginComponentService", () => {
|
|||||||
platformUtilsService = mock<PlatformUtilsService>();
|
platformUtilsService = mock<PlatformUtilsService>();
|
||||||
ssoLoginService = mock<SsoLoginServiceAbstraction>();
|
ssoLoginService = mock<SsoLoginServiceAbstraction>();
|
||||||
accountService = mockAccountServiceWith(mockUserId);
|
accountService = mockAccountServiceWith(mockUserId);
|
||||||
|
configService = mock<ConfigService>();
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
WebLoginComponentService,
|
WebLoginComponentService,
|
||||||
{ provide: DefaultLoginComponentService, useClass: WebLoginComponentService },
|
{ provide: DefaultLoginComponentService, useClass: WebLoginComponentService },
|
||||||
{ provide: AcceptOrganizationInviteService, useValue: acceptOrganizationInviteService },
|
{ provide: OrganizationInviteService, useValue: organizationInviteService },
|
||||||
{ provide: LogService, useValue: logService },
|
{ provide: LogService, useValue: logService },
|
||||||
{ provide: PolicyApiServiceAbstraction, useValue: policyApiService },
|
{ provide: PolicyApiServiceAbstraction, useValue: policyApiService },
|
||||||
{ provide: InternalPolicyService, useValue: internalPolicyService },
|
{ provide: InternalPolicyService, useValue: internalPolicyService },
|
||||||
@@ -73,6 +76,7 @@ describe("WebLoginComponentService", () => {
|
|||||||
{ provide: PlatformUtilsService, useValue: platformUtilsService },
|
{ provide: PlatformUtilsService, useValue: platformUtilsService },
|
||||||
{ provide: SsoLoginServiceAbstraction, useValue: ssoLoginService },
|
{ provide: SsoLoginServiceAbstraction, useValue: ssoLoginService },
|
||||||
{ provide: AccountService, useValue: accountService },
|
{ provide: AccountService, useValue: accountService },
|
||||||
|
{ provide: ConfigService, useValue: configService },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
service = TestBed.inject(WebLoginComponentService);
|
service = TestBed.inject(WebLoginComponentService);
|
||||||
@@ -84,14 +88,14 @@ describe("WebLoginComponentService", () => {
|
|||||||
|
|
||||||
describe("getOrgPoliciesFromOrgInvite", () => {
|
describe("getOrgPoliciesFromOrgInvite", () => {
|
||||||
it("returns undefined if organization invite is null", async () => {
|
it("returns undefined if organization invite is null", async () => {
|
||||||
acceptOrganizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||||
const result = await service.getOrgPoliciesFromOrgInvite();
|
const result = await service.getOrgPoliciesFromOrgInvite();
|
||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("logs an error if getPoliciesByToken throws an error", async () => {
|
it("logs an error if getPoliciesByToken throws an error", async () => {
|
||||||
const error = new Error("Test error");
|
const error = new Error("Test error");
|
||||||
acceptOrganizationInviteService.getOrganizationInvite.mockResolvedValue({
|
organizationInviteService.getOrganizationInvite.mockResolvedValue({
|
||||||
organizationId: "org-id",
|
organizationId: "org-id",
|
||||||
token: "token",
|
token: "token",
|
||||||
email: "email",
|
email: "email",
|
||||||
@@ -117,7 +121,7 @@ describe("WebLoginComponentService", () => {
|
|||||||
const resetPasswordPolicyOptions = new ResetPasswordPolicyOptions();
|
const resetPasswordPolicyOptions = new ResetPasswordPolicyOptions();
|
||||||
resetPasswordPolicyOptions.autoEnrollEnabled = autoEnrollEnabled;
|
resetPasswordPolicyOptions.autoEnrollEnabled = autoEnrollEnabled;
|
||||||
|
|
||||||
acceptOrganizationInviteService.getOrganizationInvite.mockResolvedValue({
|
organizationInviteService.getOrganizationInvite.mockResolvedValue({
|
||||||
organizationId: "org-id",
|
organizationId: "org-id",
|
||||||
token: "token",
|
token: "token",
|
||||||
email: "email",
|
email: "email",
|
||||||
|
|||||||
@@ -11,18 +11,21 @@ import {
|
|||||||
} from "@bitwarden/auth/angular";
|
} from "@bitwarden/auth/angular";
|
||||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
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 { 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 { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
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 { 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 { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||||
|
|
||||||
import { RouterService } from "../../../../core/router.service";
|
import { RouterService } from "../../../../core/router.service";
|
||||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WebLoginComponentService
|
export class WebLoginComponentService
|
||||||
@@ -30,7 +33,7 @@ export class WebLoginComponentService
|
|||||||
implements LoginComponentService
|
implements LoginComponentService
|
||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
protected acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
protected organizationInviteService: OrganizationInviteService,
|
||||||
protected logService: LogService,
|
protected logService: LogService,
|
||||||
protected policyApiService: PolicyApiServiceAbstraction,
|
protected policyApiService: PolicyApiServiceAbstraction,
|
||||||
protected policyService: InternalPolicyService,
|
protected policyService: InternalPolicyService,
|
||||||
@@ -42,6 +45,7 @@ export class WebLoginComponentService
|
|||||||
ssoLoginService: SsoLoginServiceAbstraction,
|
ssoLoginService: SsoLoginServiceAbstraction,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
|
private configService: ConfigService,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
cryptoFunctionService,
|
cryptoFunctionService,
|
||||||
@@ -66,8 +70,8 @@ export class WebLoginComponentService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrgPoliciesFromOrgInvite(): Promise<PasswordPolicies | null> {
|
async getOrgPoliciesFromOrgInvite(): Promise<PasswordPolicies | undefined> {
|
||||||
const orgInvite = await this.acceptOrganizationInviteService.getOrganizationInvite();
|
const orgInvite = await this.organizationInviteService.getOrganizationInvite();
|
||||||
|
|
||||||
if (orgInvite != null) {
|
if (orgInvite != null) {
|
||||||
let policies: Policy[];
|
let policies: Policy[];
|
||||||
@@ -84,7 +88,7 @@ export class WebLoginComponentService
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (policies == null) {
|
if (policies == null) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions(
|
const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions(
|
||||||
@@ -95,12 +99,23 @@ export class WebLoginComponentService
|
|||||||
const isPolicyAndAutoEnrollEnabled =
|
const isPolicyAndAutoEnrollEnabled =
|
||||||
resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled;
|
resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled;
|
||||||
|
|
||||||
const enforcedPasswordPolicyOptions = await firstValueFrom(
|
let enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions;
|
||||||
|
|
||||||
|
if (
|
||||||
|
await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)
|
||||||
|
) {
|
||||||
|
enforcedPasswordPolicyOptions =
|
||||||
|
this.policyService.combinePoliciesIntoMasterPasswordPolicyOptions(policies);
|
||||||
|
} else {
|
||||||
|
enforcedPasswordPolicyOptions = await firstValueFrom(
|
||||||
this.accountService.activeAccount$.pipe(
|
this.accountService.activeAccount$.pipe(
|
||||||
getUserId,
|
getUserId,
|
||||||
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId, policies)),
|
switchMap((userId) =>
|
||||||
|
this.policyService.masterPasswordPolicyOptions$(userId, policies),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
policies,
|
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 { 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 { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||||
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
|
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 { 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 { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||||
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
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 { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||||
import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management";
|
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 { RouterService } from "@bitwarden/web-vault/app/core";
|
||||||
|
|
||||||
import { WebSetInitialPasswordService } from "./web-set-initial-password.service";
|
import { WebSetInitialPasswordService } from "./web-set-initial-password.service";
|
||||||
@@ -43,7 +43,7 @@ describe("WebSetInitialPasswordService", () => {
|
|||||||
let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>;
|
let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>;
|
||||||
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
|
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
|
||||||
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||||
let acceptOrganizationInviteService: MockProxy<AcceptOrganizationInviteService>;
|
let organizationInviteService: MockProxy<OrganizationInviteService>;
|
||||||
let routerService: MockProxy<RouterService>;
|
let routerService: MockProxy<RouterService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -57,7 +57,7 @@ describe("WebSetInitialPasswordService", () => {
|
|||||||
organizationApiService = mock<OrganizationApiServiceAbstraction>();
|
organizationApiService = mock<OrganizationApiServiceAbstraction>();
|
||||||
organizationUserApiService = mock<OrganizationUserApiService>();
|
organizationUserApiService = mock<OrganizationUserApiService>();
|
||||||
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||||
acceptOrganizationInviteService = mock<AcceptOrganizationInviteService>();
|
organizationInviteService = mock<OrganizationInviteService>();
|
||||||
routerService = mock<RouterService>();
|
routerService = mock<RouterService>();
|
||||||
|
|
||||||
sut = new WebSetInitialPasswordService(
|
sut = new WebSetInitialPasswordService(
|
||||||
@@ -71,7 +71,7 @@ describe("WebSetInitialPasswordService", () => {
|
|||||||
organizationApiService,
|
organizationApiService,
|
||||||
organizationUserApiService,
|
organizationUserApiService,
|
||||||
userDecryptionOptionsService,
|
userDecryptionOptionsService,
|
||||||
acceptOrganizationInviteService,
|
organizationInviteService,
|
||||||
routerService,
|
routerService,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -169,9 +169,7 @@ describe("WebSetInitialPasswordService", () => {
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest);
|
expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest);
|
||||||
expect(acceptOrganizationInviteService.clearOrganizationInvitation).toHaveBeenCalledTimes(
|
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalledTimes(1);
|
||||||
1,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -201,7 +199,7 @@ describe("WebSetInitialPasswordService", () => {
|
|||||||
// Assert
|
// Assert
|
||||||
await expect(promise).rejects.toThrow();
|
await expect(promise).rejects.toThrow();
|
||||||
expect(masterPasswordApiService.setPassword).not.toHaveBeenCalled();
|
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 { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
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 { 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 { 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 { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
|
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";
|
import { RouterService } from "@bitwarden/web-vault/app/core";
|
||||||
|
|
||||||
export class WebSetInitialPasswordService
|
export class WebSetInitialPasswordService
|
||||||
@@ -32,7 +32,7 @@ export class WebSetInitialPasswordService
|
|||||||
protected organizationApiService: OrganizationApiServiceAbstraction,
|
protected organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
protected organizationUserApiService: OrganizationUserApiService,
|
protected organizationUserApiService: OrganizationUserApiService,
|
||||||
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
private organizationInviteService: OrganizationInviteService,
|
||||||
private routerService: RouterService,
|
private routerService: RouterService,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
@@ -78,6 +78,6 @@ export class WebSetInitialPasswordService
|
|||||||
* as clear the org invite itself that was originally set in state by the AcceptOrganizationComponent.
|
* as clear the org invite itself that was originally set in state by the AcceptOrganizationComponent.
|
||||||
*/
|
*/
|
||||||
await this.routerService.getAndClearLoginRedirectUrl();
|
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 { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||||
import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service";
|
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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.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 { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
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 { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
|
||||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||||
import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management";
|
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";
|
import { WebRegistrationFinishService } from "./web-registration-finish.service";
|
||||||
|
|
||||||
describe("WebRegistrationFinishService", () => {
|
describe("WebRegistrationFinishService", () => {
|
||||||
@@ -30,32 +26,28 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
|
|
||||||
let keyService: MockProxy<KeyService>;
|
let keyService: MockProxy<KeyService>;
|
||||||
let accountApiService: MockProxy<AccountApiService>;
|
let accountApiService: MockProxy<AccountApiService>;
|
||||||
let acceptOrgInviteService: MockProxy<AcceptOrganizationInviteService>;
|
let organizationInviteService: MockProxy<OrganizationInviteService>;
|
||||||
let policyApiService: MockProxy<PolicyApiServiceAbstraction>;
|
let policyApiService: MockProxy<PolicyApiServiceAbstraction>;
|
||||||
let logService: MockProxy<LogService>;
|
let logService: MockProxy<LogService>;
|
||||||
let policyService: MockProxy<PolicyService>;
|
let policyService: MockProxy<PolicyService>;
|
||||||
let configService: MockProxy<ConfigService>;
|
let configService: MockProxy<ConfigService>;
|
||||||
const mockUserId = Utils.newGuid() as UserId;
|
|
||||||
let accountService: FakeAccountService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
keyService = mock<KeyService>();
|
keyService = mock<KeyService>();
|
||||||
accountApiService = mock<AccountApiService>();
|
accountApiService = mock<AccountApiService>();
|
||||||
acceptOrgInviteService = mock<AcceptOrganizationInviteService>();
|
organizationInviteService = mock<OrganizationInviteService>();
|
||||||
policyApiService = mock<PolicyApiServiceAbstraction>();
|
policyApiService = mock<PolicyApiServiceAbstraction>();
|
||||||
logService = mock<LogService>();
|
logService = mock<LogService>();
|
||||||
policyService = mock<PolicyService>();
|
policyService = mock<PolicyService>();
|
||||||
accountService = mockAccountServiceWith(mockUserId);
|
|
||||||
configService = mock<ConfigService>();
|
configService = mock<ConfigService>();
|
||||||
|
|
||||||
service = new WebRegistrationFinishService(
|
service = new WebRegistrationFinishService(
|
||||||
keyService,
|
keyService,
|
||||||
accountApiService,
|
accountApiService,
|
||||||
acceptOrgInviteService,
|
organizationInviteService,
|
||||||
policyApiService,
|
policyApiService,
|
||||||
logService,
|
logService,
|
||||||
policyService,
|
policyService,
|
||||||
accountService,
|
|
||||||
configService,
|
configService,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -76,21 +68,21 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns null when the org invite is null", async () => {
|
it("returns null when the org invite is null", async () => {
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||||
|
|
||||||
const result = await service.getOrgNameFromOrgInvite();
|
const result = await service.getOrgNameFromOrgInvite();
|
||||||
|
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns the organization name from the organization invite when it exists", async () => {
|
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();
|
const result = await service.getOrgNameFromOrgInvite();
|
||||||
|
|
||||||
expect(result).toEqual(orgInvite.organizationName);
|
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 () => {
|
it("returns null when the org invite is null", async () => {
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||||
|
|
||||||
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
||||||
|
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns null when the policies are null", async () => {
|
it("returns null when the policies are null", async () => {
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||||
policyApiService.getPoliciesByToken.mockResolvedValue(null);
|
policyApiService.getPoliciesByToken.mockResolvedValue(null);
|
||||||
|
|
||||||
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
||||||
|
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||||
expect(policyApiService.getPoliciesByToken).toHaveBeenCalledWith(
|
expect(policyApiService.getPoliciesByToken).toHaveBeenCalledWith(
|
||||||
orgInvite.organizationId,
|
orgInvite.organizationId,
|
||||||
orgInvite.token,
|
orgInvite.token,
|
||||||
@@ -131,13 +123,13 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("logs an error and returns null when policies cannot be fetched", async () => {
|
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"));
|
policyApiService.getPoliciesByToken.mockRejectedValue(new Error("error"));
|
||||||
|
|
||||||
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
||||||
|
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||||
expect(policyApiService.getPoliciesByToken).toHaveBeenCalledWith(
|
expect(policyApiService.getPoliciesByToken).toHaveBeenCalledWith(
|
||||||
orgInvite.organizationId,
|
orgInvite.organizationId,
|
||||||
orgInvite.token,
|
orgInvite.token,
|
||||||
@@ -151,14 +143,14 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
const masterPasswordPolicies = [new Policy()];
|
const masterPasswordPolicies = [new Policy()];
|
||||||
const masterPasswordPolicyOptions = new MasterPasswordPolicyOptions();
|
const masterPasswordPolicyOptions = new MasterPasswordPolicyOptions();
|
||||||
|
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||||
policyApiService.getPoliciesByToken.mockResolvedValue(masterPasswordPolicies);
|
policyApiService.getPoliciesByToken.mockResolvedValue(masterPasswordPolicies);
|
||||||
policyService.masterPasswordPolicyOptions$.mockReturnValue(of(masterPasswordPolicyOptions));
|
policyService.masterPasswordPolicyOptions$.mockReturnValue(of(masterPasswordPolicyOptions));
|
||||||
|
|
||||||
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
||||||
|
|
||||||
expect(result).toEqual(masterPasswordPolicyOptions);
|
expect(result).toEqual(masterPasswordPolicyOptions);
|
||||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||||
expect(policyApiService.getPoliciesByToken).toHaveBeenCalledWith(
|
expect(policyApiService.getPoliciesByToken).toHaveBeenCalledWith(
|
||||||
orgInvite.organizationId,
|
orgInvite.organizationId,
|
||||||
orgInvite.token,
|
orgInvite.token,
|
||||||
@@ -225,7 +217,7 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||||
accountApiService.registerFinish.mockResolvedValue();
|
accountApiService.registerFinish.mockResolvedValue();
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||||
|
|
||||||
await service.finishRegistration(email, passwordInputResult, emailVerificationToken);
|
await service.finishRegistration(email, passwordInputResult, emailVerificationToken);
|
||||||
|
|
||||||
@@ -261,7 +253,7 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||||
accountApiService.registerFinish.mockResolvedValue();
|
accountApiService.registerFinish.mockResolvedValue();
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||||
|
|
||||||
await service.finishRegistration(email, passwordInputResult);
|
await service.finishRegistration(email, passwordInputResult);
|
||||||
|
|
||||||
@@ -297,7 +289,7 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||||
accountApiService.registerFinish.mockResolvedValue();
|
accountApiService.registerFinish.mockResolvedValue();
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||||
|
|
||||||
await service.finishRegistration(
|
await service.finishRegistration(
|
||||||
email,
|
email,
|
||||||
@@ -338,7 +330,7 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||||
accountApiService.registerFinish.mockResolvedValue();
|
accountApiService.registerFinish.mockResolvedValue();
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||||
|
|
||||||
await service.finishRegistration(
|
await service.finishRegistration(
|
||||||
email,
|
email,
|
||||||
@@ -381,7 +373,7 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||||
accountApiService.registerFinish.mockResolvedValue();
|
accountApiService.registerFinish.mockResolvedValue();
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
organizationInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||||
|
|
||||||
await service.finishRegistration(
|
await service.finishRegistration(
|
||||||
email,
|
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 { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||||
import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service";
|
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 { 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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
|
||||||
|
|
||||||
export class WebRegistrationFinishService
|
export class WebRegistrationFinishService
|
||||||
extends DefaultRegistrationFinishService
|
extends DefaultRegistrationFinishService
|
||||||
implements RegistrationFinishService
|
implements RegistrationFinishService
|
||||||
@@ -29,18 +27,17 @@ export class WebRegistrationFinishService
|
|||||||
constructor(
|
constructor(
|
||||||
protected keyService: KeyService,
|
protected keyService: KeyService,
|
||||||
protected accountApiService: AccountApiService,
|
protected accountApiService: AccountApiService,
|
||||||
private acceptOrgInviteService: AcceptOrganizationInviteService,
|
private organizationInviteService: OrganizationInviteService,
|
||||||
private policyApiService: PolicyApiServiceAbstraction,
|
private policyApiService: PolicyApiServiceAbstraction,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private policyService: PolicyService,
|
private policyService: PolicyService,
|
||||||
private accountService: AccountService,
|
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
) {
|
) {
|
||||||
super(keyService, accountApiService);
|
super(keyService, accountApiService);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async getOrgNameFromOrgInvite(): Promise<string | null> {
|
override async getOrgNameFromOrgInvite(): Promise<string | null> {
|
||||||
const orgInvite = await this.acceptOrgInviteService.getOrganizationInvite();
|
const orgInvite = await this.organizationInviteService.getOrganizationInvite();
|
||||||
if (orgInvite == null) {
|
if (orgInvite == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -50,7 +47,7 @@ export class WebRegistrationFinishService
|
|||||||
|
|
||||||
override async getMasterPasswordPolicyOptsFromOrgInvite(): Promise<MasterPasswordPolicyOptions | null> {
|
override async getMasterPasswordPolicyOptsFromOrgInvite(): Promise<MasterPasswordPolicyOptions | null> {
|
||||||
// If there's a deep linked org invite, use it to get the password policies
|
// 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) {
|
if (orgInvite == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -115,7 +112,7 @@ export class WebRegistrationFinishService
|
|||||||
// web specific logic
|
// web specific logic
|
||||||
// Org invites are deep linked. Non-existent accounts are redirected to the register page.
|
// 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.
|
// 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) {
|
if (orgInvite != null) {
|
||||||
registerRequest.organizationUserId = orgInvite.organizationUserId;
|
registerRequest.organizationUserId = orgInvite.organizationUserId;
|
||||||
registerRequest.orgInviteToken = orgInvite.token;
|
registerRequest.orgInviteToken = orgInvite.token;
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ import {
|
|||||||
SetPasswordCredentials,
|
SetPasswordCredentials,
|
||||||
SetPasswordJitService,
|
SetPasswordJitService,
|
||||||
} from "@bitwarden/auth/angular";
|
} from "@bitwarden/auth/angular";
|
||||||
|
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
|
||||||
|
|
||||||
import { RouterService } from "../../../../core/router.service";
|
import { RouterService } from "../../../../core/router.service";
|
||||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
|
||||||
|
|
||||||
export class WebSetPasswordJitService
|
export class WebSetPasswordJitService
|
||||||
extends DefaultSetPasswordJitService
|
extends DefaultSetPasswordJitService
|
||||||
implements SetPasswordJitService
|
implements SetPasswordJitService
|
||||||
{
|
{
|
||||||
routerService = inject(RouterService);
|
routerService = inject(RouterService);
|
||||||
acceptOrganizationInviteService = inject(AcceptOrganizationInviteService);
|
organizationInviteService = inject(OrganizationInviteService);
|
||||||
|
|
||||||
override async setPassword(credentials: SetPasswordCredentials) {
|
override async setPassword(credentials: SetPasswordCredentials) {
|
||||||
await super.setPassword(credentials);
|
await super.setPassword(credentials);
|
||||||
@@ -22,6 +22,6 @@ export class WebSetPasswordJitService
|
|||||||
// SSO JIT accepts org invites when setting their MP, meaning
|
// SSO JIT accepts org invites when setting their MP, meaning
|
||||||
// we can clear the deep linked url for accepting it.
|
// we can clear the deep linked url for accepting it.
|
||||||
await this.routerService.getAndClearLoginRedirectUrl();
|
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 { ActivatedRoute, Params, Router } from "@angular/router";
|
||||||
|
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
|
||||||
import { BaseAcceptComponent } from "../../common/base.accept.component";
|
import { BaseAcceptComponent } from "../../common/base.accept.component";
|
||||||
|
|
||||||
import { AcceptOrganizationInviteService } from "./accept-organization.service";
|
import { AcceptOrganizationInviteService } from "./accept-organization.service";
|
||||||
import { OrganizationInvite } from "./organization-invite";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "accept-organization.component.html",
|
templateUrl: "accept-organization.component.html",
|
||||||
@@ -21,18 +22,19 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
|
|||||||
protected requiredParameters: string[] = ["organizationId", "organizationUserId", "token"];
|
protected requiredParameters: string[] = ["organizationId", "organizationUserId", "token"];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
router: Router,
|
protected router: Router,
|
||||||
platformUtilsService: PlatformUtilsService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
i18nService: I18nService,
|
protected i18nService: I18nService,
|
||||||
route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
authService: AuthService,
|
protected authService: AuthService,
|
||||||
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
||||||
|
private organizationInviteService: OrganizationInviteService,
|
||||||
) {
|
) {
|
||||||
super(router, platformUtilsService, i18nService, route, authService);
|
super(router, platformUtilsService, i18nService, route, authService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async authedHandler(qParams: Params): Promise<void> {
|
async authedHandler(qParams: Params): Promise<void> {
|
||||||
const invite = OrganizationInvite.fromParams(qParams);
|
const invite = this.fromParams(qParams);
|
||||||
const success = await this.acceptOrganizationInviteService.validateAndAcceptInvite(invite);
|
const success = await this.acceptOrganizationInviteService.validateAndAcceptInvite(invite);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
@@ -52,9 +54,9 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async unauthedHandler(qParams: Params): Promise<void> {
|
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);
|
await this.navigateInviteAcceptance(invite);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,4 +96,21 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
|
|||||||
});
|
});
|
||||||
return;
|
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
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { FakeGlobalStateProvider } from "@bitwarden/common/../spec/fake-state-provider";
|
|
||||||
import { MockProxy, mock } from "jest-mock-extended";
|
import { MockProxy, mock } from "jest-mock-extended";
|
||||||
import { BehaviorSubject } from "rxjs";
|
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 { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.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 { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
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 { OrgKey } from "@bitwarden/common/types/key";
|
||||||
import { DialogService } from "@bitwarden/components";
|
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { I18nService } from "../../core/i18n.service";
|
import { I18nService } from "../../core/i18n.service";
|
||||||
|
|
||||||
import {
|
import { AcceptOrganizationInviteService } from "./accept-organization.service";
|
||||||
AcceptOrganizationInviteService,
|
|
||||||
ORGANIZATION_INVITE,
|
|
||||||
} from "./accept-organization.service";
|
|
||||||
import { OrganizationInvite } from "./organization-invite";
|
|
||||||
|
|
||||||
describe("AcceptOrganizationInviteService", () => {
|
describe("AcceptOrganizationInviteService", () => {
|
||||||
let sut: AcceptOrganizationInviteService;
|
let sut: AcceptOrganizationInviteService;
|
||||||
@@ -43,10 +38,8 @@ describe("AcceptOrganizationInviteService", () => {
|
|||||||
let logService: MockProxy<LogService>;
|
let logService: MockProxy<LogService>;
|
||||||
let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>;
|
let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>;
|
||||||
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
|
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
|
||||||
|
let organizationInviteService: MockProxy<OrganizationInviteService>;
|
||||||
let i18nService: MockProxy<I18nService>;
|
let i18nService: MockProxy<I18nService>;
|
||||||
let globalStateProvider: FakeGlobalStateProvider;
|
|
||||||
let globalState: FakeGlobalState<OrganizationInvite>;
|
|
||||||
let dialogService: MockProxy<DialogService>;
|
|
||||||
let accountService: MockProxy<AccountService>;
|
let accountService: MockProxy<AccountService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -59,10 +52,8 @@ describe("AcceptOrganizationInviteService", () => {
|
|||||||
logService = mock();
|
logService = mock();
|
||||||
organizationApiService = mock();
|
organizationApiService = mock();
|
||||||
organizationUserApiService = mock();
|
organizationUserApiService = mock();
|
||||||
|
organizationInviteService = mock();
|
||||||
i18nService = mock();
|
i18nService = mock();
|
||||||
globalStateProvider = new FakeGlobalStateProvider();
|
|
||||||
globalState = globalStateProvider.getFake(ORGANIZATION_INVITE);
|
|
||||||
dialogService = mock();
|
|
||||||
accountService = mock();
|
accountService = mock();
|
||||||
|
|
||||||
sut = new AcceptOrganizationInviteService(
|
sut = new AcceptOrganizationInviteService(
|
||||||
@@ -76,8 +67,7 @@ describe("AcceptOrganizationInviteService", () => {
|
|||||||
organizationApiService,
|
organizationApiService,
|
||||||
organizationUserApiService,
|
organizationUserApiService,
|
||||||
i18nService,
|
i18nService,
|
||||||
globalStateProvider,
|
organizationInviteService,
|
||||||
dialogService,
|
|
||||||
accountService,
|
accountService,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -103,8 +93,10 @@ describe("AcceptOrganizationInviteService", () => {
|
|||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
expect(organizationUserApiService.postOrganizationUserAcceptInit).toHaveBeenCalled();
|
expect(organizationUserApiService.postOrganizationUserAcceptInit).toHaveBeenCalled();
|
||||||
expect(apiService.refreshIdentityToken).toHaveBeenCalled();
|
expect(apiService.refreshIdentityToken).toHaveBeenCalled();
|
||||||
expect(globalState.nextMock).toHaveBeenCalledWith(null);
|
|
||||||
expect(organizationUserApiService.postOrganizationUserAccept).not.toHaveBeenCalled();
|
expect(organizationUserApiService.postOrganizationUserAccept).not.toHaveBeenCalled();
|
||||||
|
expect(organizationInviteService.getOrganizationInvite).not.toHaveBeenCalled();
|
||||||
|
expect(organizationInviteService.setOrganizationInvitation).not.toHaveBeenCalled();
|
||||||
|
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalled();
|
||||||
expect(authService.logOut).not.toHaveBeenCalled();
|
expect(authService.logOut).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -121,13 +113,16 @@ describe("AcceptOrganizationInviteService", () => {
|
|||||||
|
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
expect(authService.logOut).toHaveBeenCalled();
|
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 () => {
|
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 storedInvite = createOrgInvite({ email: "wrongemail@example.com" });
|
||||||
const providedInvite = createOrgInvite();
|
const providedInvite = createOrgInvite();
|
||||||
await globalState.update(() => storedInvite);
|
organizationInviteService.getOrganizationInvite.mockReturnValueOnce(
|
||||||
|
Promise.resolve(storedInvite),
|
||||||
|
);
|
||||||
policyApiService.getPoliciesByToken.mockResolvedValue([
|
policyApiService.getPoliciesByToken.mockResolvedValue([
|
||||||
{
|
{
|
||||||
type: PolicyType.MasterPassword,
|
type: PolicyType.MasterPassword,
|
||||||
@@ -139,7 +134,11 @@ describe("AcceptOrganizationInviteService", () => {
|
|||||||
|
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
expect(authService.logOut).toHaveBeenCalled();
|
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 () => {
|
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(result).toBe(true);
|
||||||
expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled();
|
expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled();
|
||||||
expect(apiService.refreshIdentityToken).toHaveBeenCalled();
|
expect(apiService.refreshIdentityToken).toHaveBeenCalled();
|
||||||
expect(globalState.nextMock).toHaveBeenCalledWith(null);
|
|
||||||
expect(organizationUserApiService.postOrganizationUserAcceptInit).not.toHaveBeenCalled();
|
expect(organizationUserApiService.postOrganizationUserAcceptInit).not.toHaveBeenCalled();
|
||||||
|
expect(organizationInviteService.setOrganizationInvitation).not.toHaveBeenCalled();
|
||||||
|
expect(organizationInviteService.getOrganizationInvite).not.toHaveBeenCalled();
|
||||||
|
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalled();
|
||||||
expect(authService.logOut).not.toHaveBeenCalled();
|
expect(authService.logOut).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -165,7 +166,7 @@ describe("AcceptOrganizationInviteService", () => {
|
|||||||
} as Policy,
|
} as Policy,
|
||||||
]);
|
]);
|
||||||
// an existing invite means the user has already passed the master password 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([
|
policyService.getResetPasswordPolicyOptions.mockReturnValue([
|
||||||
{
|
{
|
||||||
@@ -179,6 +180,8 @@ describe("AcceptOrganizationInviteService", () => {
|
|||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled();
|
expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled();
|
||||||
expect(organizationUserApiService.postOrganizationUserAcceptInit).not.toHaveBeenCalled();
|
expect(organizationUserApiService.postOrganizationUserAcceptInit).not.toHaveBeenCalled();
|
||||||
|
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalledWith();
|
||||||
|
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalled();
|
||||||
expect(authService.logOut).not.toHaveBeenCalled();
|
expect(authService.logOut).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -202,7 +205,7 @@ describe("AcceptOrganizationInviteService", () => {
|
|||||||
encryptedString: "encryptedString",
|
encryptedString: "encryptedString",
|
||||||
} as EncString);
|
} as EncString);
|
||||||
|
|
||||||
await globalState.update(() => invite);
|
organizationInviteService.getOrganizationInvite.mockReturnValueOnce(Promise.resolve(invite));
|
||||||
|
|
||||||
policyService.getResetPasswordPolicyOptions.mockReturnValue([
|
policyService.getResetPasswordPolicyOptions.mockReturnValue([
|
||||||
{
|
{
|
||||||
@@ -220,6 +223,9 @@ describe("AcceptOrganizationInviteService", () => {
|
|||||||
);
|
);
|
||||||
expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled();
|
expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled();
|
||||||
expect(organizationUserApiService.postOrganizationUserAcceptInit).not.toHaveBeenCalled();
|
expect(organizationUserApiService.postOrganizationUserAcceptInit).not.toHaveBeenCalled();
|
||||||
|
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalledTimes(1);
|
||||||
|
expect(organizationInviteService.getOrganizationInvite).toHaveBeenCalledWith();
|
||||||
|
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalled();
|
||||||
expect(authService.logOut).not.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 { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.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 { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
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 { OrgKey } from "@bitwarden/common/types/key";
|
||||||
import { DialogService } from "@bitwarden/components";
|
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
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()
|
@Injectable()
|
||||||
export class AcceptOrganizationInviteService {
|
export class AcceptOrganizationInviteService {
|
||||||
private organizationInvitationState: GlobalState<OrganizationInvite | null>;
|
|
||||||
private orgNameSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);
|
private orgNameSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);
|
||||||
private policyCache: Policy[];
|
private policyCache: Policy[];
|
||||||
|
|
||||||
@@ -64,34 +45,9 @@ export class AcceptOrganizationInviteService {
|
|||||||
private readonly organizationApiService: OrganizationApiServiceAbstraction,
|
private readonly organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
private readonly organizationUserApiService: OrganizationUserApiService,
|
private readonly organizationUserApiService: OrganizationUserApiService,
|
||||||
private readonly i18nService: I18nService,
|
private readonly i18nService: I18nService,
|
||||||
private readonly globalStateProvider: GlobalStateProvider,
|
private readonly organizationInviteService: OrganizationInviteService,
|
||||||
private readonly dialogService: DialogService,
|
|
||||||
private readonly accountService: AccountService,
|
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.
|
* Validates and accepts the organization invitation if possible.
|
||||||
@@ -113,7 +69,7 @@ export class AcceptOrganizationInviteService {
|
|||||||
|
|
||||||
// Accepting an org invite from existing org
|
// Accepting an org invite from existing org
|
||||||
if (await this.masterPasswordPolicyCheckRequired(invite)) {
|
if (await this.masterPasswordPolicyCheckRequired(invite)) {
|
||||||
await this.setOrganizationInvitation(invite);
|
await this.organizationInviteService.setOrganizationInvitation(invite);
|
||||||
this.authService.logOut(() => {
|
this.authService.logOut(() => {
|
||||||
/* Do nothing */
|
/* Do nothing */
|
||||||
});
|
});
|
||||||
@@ -134,7 +90,7 @@ export class AcceptOrganizationInviteService {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
await this.apiService.refreshIdentityToken();
|
await this.apiService.refreshIdentityToken();
|
||||||
await this.clearOrganizationInvitation();
|
await this.organizationInviteService.clearOrganizationInvitation();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async prepareAcceptAndInitRequest(
|
private async prepareAcceptAndInitRequest(
|
||||||
@@ -170,7 +126,7 @@ export class AcceptOrganizationInviteService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await this.apiService.refreshIdentityToken();
|
await this.apiService.refreshIdentityToken();
|
||||||
await this.clearOrganizationInvitation();
|
await this.organizationInviteService.clearOrganizationInvitation();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async prepareAcceptRequest(
|
private async prepareAcceptRequest(
|
||||||
@@ -224,10 +180,10 @@ export class AcceptOrganizationInviteService {
|
|||||||
(p) => p.type === PolicyType.MasterPassword && p.enabled,
|
(p) => p.type === PolicyType.MasterPassword && p.enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
let storedInvite = await this.getOrganizationInvite();
|
let storedInvite = await this.organizationInviteService.getOrganizationInvite();
|
||||||
if (storedInvite?.email !== invite.email) {
|
if (storedInvite?.email !== invite.email) {
|
||||||
// clear stored invites if the email doesn't match
|
// clear stored invites if the email doesn't match
|
||||||
await this.clearOrganizationInvitation();
|
await this.organizationInviteService.clearOrganizationInvitation();
|
||||||
storedInvite = null;
|
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
|
// 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 { Component, inject } from "@angular/core";
|
||||||
|
|
||||||
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
|
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 { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||||
|
|
||||||
import { RouterService } from "../core";
|
import { RouterService } from "../core";
|
||||||
|
|
||||||
import { AcceptOrganizationInviteService } from "./organization-invite/accept-organization.service";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-set-password",
|
selector: "app-set-password",
|
||||||
templateUrl: "set-password.component.html",
|
templateUrl: "set-password.component.html",
|
||||||
@@ -15,7 +14,7 @@ import { AcceptOrganizationInviteService } from "./organization-invite/accept-or
|
|||||||
})
|
})
|
||||||
export class SetPasswordComponent extends BaseSetPasswordComponent {
|
export class SetPasswordComponent extends BaseSetPasswordComponent {
|
||||||
routerService = inject(RouterService);
|
routerService = inject(RouterService);
|
||||||
acceptOrganizationInviteService = inject(AcceptOrganizationInviteService);
|
organizationInviteService = inject(OrganizationInviteService);
|
||||||
|
|
||||||
protected override async onSetPasswordSuccess(
|
protected override async onSetPasswordSuccess(
|
||||||
masterKey: MasterKey,
|
masterKey: MasterKey,
|
||||||
@@ -26,6 +25,6 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
|
|||||||
// SSO JIT accepts org invites when setting their MP, meaning
|
// SSO JIT accepts org invites when setting their MP, meaning
|
||||||
// we can clear the deep linked url for accepting it.
|
// we can clear the deep linked url for accepting it.
|
||||||
await this.routerService.getAndClearLoginRedirectUrl();
|
await this.routerService.getAndClearLoginRedirectUrl();
|
||||||
await this.acceptOrganizationInviteService.clearOrganizationInvitation();
|
await this.organizationInviteService.clearOrganizationInvitation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,34 +43,34 @@ export class ChangePasswordComponent
|
|||||||
characterMinimumMessage = "";
|
characterMinimumMessage = "";
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
i18nService: I18nService,
|
|
||||||
keyService: KeyService,
|
|
||||||
messagingService: MessagingService,
|
|
||||||
platformUtilsService: PlatformUtilsService,
|
|
||||||
policyService: PolicyService,
|
|
||||||
private auditService: AuditService,
|
private auditService: AuditService,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private syncService: SyncService,
|
private keyRotationService: UserKeyRotationService,
|
||||||
private masterPasswordApiService: MasterPasswordApiService,
|
private masterPasswordApiService: MasterPasswordApiService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
dialogService: DialogService,
|
private syncService: SyncService,
|
||||||
private userVerificationService: UserVerificationService,
|
private userVerificationService: UserVerificationService,
|
||||||
private keyRotationService: UserKeyRotationService,
|
protected accountService: AccountService,
|
||||||
kdfConfigService: KdfConfigService,
|
protected dialogService: DialogService,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
protected kdfConfigService: KdfConfigService,
|
||||||
|
protected keyService: KeyService,
|
||||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||||
accountService: AccountService,
|
protected messagingService: MessagingService,
|
||||||
toastService: ToastService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
protected policyService: PolicyService,
|
||||||
|
protected toastService: ToastService,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
|
accountService,
|
||||||
|
dialogService,
|
||||||
i18nService,
|
i18nService,
|
||||||
|
kdfConfigService,
|
||||||
keyService,
|
keyService,
|
||||||
|
masterPasswordService,
|
||||||
messagingService,
|
messagingService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
policyService,
|
policyService,
|
||||||
dialogService,
|
|
||||||
kdfConfigService,
|
|
||||||
masterPasswordService,
|
|
||||||
accountService,
|
|
||||||
toastService,
|
toastService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -244,8 +244,7 @@ export class ChangePasswordComponent
|
|||||||
await this.masterPasswordApiService.postPassword(request);
|
await this.masterPasswordApiService.postPassword(request);
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
title: this.i18nService.t("masterPasswordChanged"),
|
message: this.i18nService.t("masterPasswordChanged"),
|
||||||
message: this.i18nService.t("masterPasswordChangedDesc"),
|
|
||||||
});
|
});
|
||||||
this.messagingService.send("logout");
|
this.messagingService.send("logout");
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -71,15 +71,15 @@ export class EmergencyAccessTakeoverComponent
|
|||||||
protected toastService: ToastService,
|
protected toastService: ToastService,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
|
accountService,
|
||||||
|
dialogService,
|
||||||
i18nService,
|
i18nService,
|
||||||
|
kdfConfigService,
|
||||||
keyService,
|
keyService,
|
||||||
|
masterPasswordService,
|
||||||
messagingService,
|
messagingService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
policyService,
|
policyService,
|
||||||
dialogService,
|
|
||||||
kdfConfigService,
|
|
||||||
masterPasswordService,
|
|
||||||
accountService,
|
|
||||||
toastService,
|
toastService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<h1 class="tw-mt-6 tw-mb-2 tw-pb-2.5">{{ "changeMasterPassword" | i18n }}</h1>
|
<h1 class="tw-mt-6 tw-mb-2 tw-pb-2.5">{{ "changeMasterPassword" | i18n }}</h1>
|
||||||
|
|
||||||
<div class="tw-max-w-lg tw-mb-12">
|
<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>
|
<auth-change-password [inputPasswordFlow]="inputPasswordFlow"></auth-change-password>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { Component, OnInit } from "@angular/core";
|
|||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
import { firstValueFrom } from "rxjs";
|
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 { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { CalloutModule } from "@bitwarden/components";
|
import { CalloutModule } from "@bitwarden/components";
|
||||||
import { I18nPipe } from "@bitwarden/ui-common";
|
import { I18nPipe } from "@bitwarden/ui-common";
|
||||||
@@ -16,6 +17,7 @@ import { WebauthnLoginSettingsModule } from "../../webauthn-login-settings";
|
|||||||
})
|
})
|
||||||
export class PasswordSettingsComponent implements OnInit {
|
export class PasswordSettingsComponent implements OnInit {
|
||||||
inputPasswordFlow = InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation;
|
inputPasswordFlow = InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation;
|
||||||
|
changePasswordFeatureFlag = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Component, inject } from "@angular/core";
|
import { Component, inject } from "@angular/core";
|
||||||
|
|
||||||
import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "@bitwarden/angular/auth/components/update-password.component";
|
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 { RouterService } from "../core";
|
||||||
|
|
||||||
import { AcceptOrganizationInviteService } from "./organization-invite/accept-organization.service";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-update-password",
|
selector: "app-update-password",
|
||||||
templateUrl: "update-password.component.html",
|
templateUrl: "update-password.component.html",
|
||||||
@@ -13,13 +12,13 @@ import { AcceptOrganizationInviteService } from "./organization-invite/accept-or
|
|||||||
})
|
})
|
||||||
export class UpdatePasswordComponent extends BaseUpdatePasswordComponent {
|
export class UpdatePasswordComponent extends BaseUpdatePasswordComponent {
|
||||||
private routerService = inject(RouterService);
|
private routerService = inject(RouterService);
|
||||||
private acceptOrganizationInviteService = inject(AcceptOrganizationInviteService);
|
private organizationInviteService = inject(OrganizationInviteService);
|
||||||
|
|
||||||
override async cancel() {
|
override async cancel() {
|
||||||
// clearing the login redirect url so that the user
|
// clearing the login redirect url so that the user
|
||||||
// does not join the organization if they cancel
|
// does not join the organization if they cancel
|
||||||
await this.routerService.getAndClearLoginRedirectUrl();
|
await this.routerService.getAndClearLoginRedirectUrl();
|
||||||
await this.acceptOrganizationInviteService.clearOrganizationInvitation();
|
await this.organizationInviteService.clearOrganizationInvitation();
|
||||||
await super.cancel();
|
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 { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
|
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
|
||||||
import {
|
import {
|
||||||
OrganizationBillingServiceAbstraction as OrganizationBillingService,
|
OrganizationBillingServiceAbstraction as OrganizationBillingService,
|
||||||
OrganizationInformation,
|
OrganizationInformation,
|
||||||
@@ -31,7 +32,6 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
|||||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
import { ToastService } from "@bitwarden/components";
|
import { ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { AcceptOrganizationInviteService } from "../../../auth/organization-invite/accept-organization.service";
|
|
||||||
import {
|
import {
|
||||||
OrganizationCreatedEvent,
|
OrganizationCreatedEvent,
|
||||||
SubscriptionProduct,
|
SubscriptionProduct,
|
||||||
@@ -115,7 +115,7 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy {
|
|||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private routerService: RouterService,
|
private routerService: RouterService,
|
||||||
private organizationBillingService: OrganizationBillingService,
|
private organizationBillingService: OrganizationBillingService,
|
||||||
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
private organizationInviteService: OrganizationInviteService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private registrationFinishService: RegistrationFinishService,
|
private registrationFinishService: RegistrationFinishService,
|
||||||
private validationService: ValidationService,
|
private validationService: ValidationService,
|
||||||
@@ -174,7 +174,7 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy {
|
|||||||
this.setupFamilySponsorship(qParams.sponsorshipToken);
|
this.setupFamilySponsorship(qParams.sponsorshipToken);
|
||||||
});
|
});
|
||||||
|
|
||||||
const invite = await this.acceptOrganizationInviteService.getOrganizationInvite();
|
const invite = await this.organizationInviteService.getOrganizationInvite();
|
||||||
let policies: Policy[] | null = null;
|
let policies: Policy[] | null = null;
|
||||||
|
|
||||||
if (invite != null) {
|
if (invite != null) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
OrganizationUserApiService,
|
OrganizationUserApiService,
|
||||||
CollectionService,
|
CollectionService,
|
||||||
} from "@bitwarden/admin-console/common";
|
} 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 { 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 { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
|
||||||
import {
|
import {
|
||||||
@@ -34,7 +35,6 @@ import {
|
|||||||
SsoComponentService,
|
SsoComponentService,
|
||||||
LoginDecryptionOptionsService,
|
LoginDecryptionOptionsService,
|
||||||
TwoFactorAuthDuoComponentService,
|
TwoFactorAuthDuoComponentService,
|
||||||
ChangePasswordService,
|
|
||||||
} from "@bitwarden/auth/angular";
|
} from "@bitwarden/auth/angular";
|
||||||
import {
|
import {
|
||||||
InternalUserDecryptionOptionsServiceAbstraction,
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
@@ -52,6 +52,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
|||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.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 { ClientType } from "@bitwarden/common/enums";
|
||||||
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||||
@@ -108,6 +109,7 @@ import {
|
|||||||
} from "@bitwarden/key-management";
|
} from "@bitwarden/key-management";
|
||||||
import { LockComponentService } from "@bitwarden/key-management-ui";
|
import { LockComponentService } from "@bitwarden/key-management-ui";
|
||||||
import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarden/vault";
|
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 { flagEnabled } from "../../utils/flags";
|
||||||
import { PolicyListService } from "../admin-console/core/policy-list.service";
|
import { PolicyListService } from "../admin-console/core/policy-list.service";
|
||||||
@@ -122,7 +124,6 @@ import {
|
|||||||
WebSetInitialPasswordService,
|
WebSetInitialPasswordService,
|
||||||
} from "../auth";
|
} from "../auth";
|
||||||
import { WebSsoComponentService } from "../auth/core/services/login/web-sso-component.service";
|
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 { HtmlStorageService } from "../core/html-storage.service";
|
||||||
import { I18nService } from "../core/i18n.service";
|
import { I18nService } from "../core/i18n.service";
|
||||||
import { WebFileDownloadService } from "../core/web-file-download.service";
|
import { WebFileDownloadService } from "../core/web-file-download.service";
|
||||||
@@ -246,17 +247,21 @@ const safeProviders: SafeProvider[] = [
|
|||||||
provide: CLIENT_TYPE,
|
provide: CLIENT_TYPE,
|
||||||
useValue: ClientType.Web,
|
useValue: ClientType.Web,
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: OrganizationInviteService,
|
||||||
|
useClass: WebOrganizationInviteService,
|
||||||
|
deps: [GlobalStateProvider],
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: RegistrationFinishServiceAbstraction,
|
provide: RegistrationFinishServiceAbstraction,
|
||||||
useClass: WebRegistrationFinishService,
|
useClass: WebRegistrationFinishService,
|
||||||
deps: [
|
deps: [
|
||||||
KeyServiceAbstraction,
|
KeyServiceAbstraction,
|
||||||
AccountApiServiceAbstraction,
|
AccountApiServiceAbstraction,
|
||||||
AcceptOrganizationInviteService,
|
OrganizationInviteService,
|
||||||
PolicyApiServiceAbstraction,
|
PolicyApiServiceAbstraction,
|
||||||
LogService,
|
LogService,
|
||||||
PolicyService,
|
PolicyService,
|
||||||
AccountService,
|
|
||||||
ConfigService,
|
ConfigService,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -275,12 +280,11 @@ const safeProviders: SafeProvider[] = [
|
|||||||
provide: SetPasswordJitService,
|
provide: SetPasswordJitService,
|
||||||
useClass: WebSetPasswordJitService,
|
useClass: WebSetPasswordJitService,
|
||||||
deps: [
|
deps: [
|
||||||
ApiService,
|
|
||||||
MasterPasswordApiService,
|
|
||||||
KeyServiceAbstraction,
|
|
||||||
EncryptService,
|
EncryptService,
|
||||||
I18nServiceAbstraction,
|
I18nServiceAbstraction,
|
||||||
KdfConfigService,
|
KdfConfigService,
|
||||||
|
KeyServiceAbstraction,
|
||||||
|
MasterPasswordApiService,
|
||||||
InternalMasterPasswordServiceAbstraction,
|
InternalMasterPasswordServiceAbstraction,
|
||||||
OrganizationApiServiceAbstraction,
|
OrganizationApiServiceAbstraction,
|
||||||
OrganizationUserApiService,
|
OrganizationUserApiService,
|
||||||
@@ -301,7 +305,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
OrganizationApiServiceAbstraction,
|
OrganizationApiServiceAbstraction,
|
||||||
OrganizationUserApiService,
|
OrganizationUserApiService,
|
||||||
InternalUserDecryptionOptionsServiceAbstraction,
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
AcceptOrganizationInviteService,
|
OrganizationInviteService,
|
||||||
RouterService,
|
RouterService,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -314,7 +318,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
provide: LoginComponentService,
|
provide: LoginComponentService,
|
||||||
useClass: WebLoginComponentService,
|
useClass: WebLoginComponentService,
|
||||||
deps: [
|
deps: [
|
||||||
AcceptOrganizationInviteService,
|
OrganizationInviteService,
|
||||||
LogService,
|
LogService,
|
||||||
PolicyApiServiceAbstraction,
|
PolicyApiServiceAbstraction,
|
||||||
InternalPolicyService,
|
InternalPolicyService,
|
||||||
@@ -326,6 +330,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
SsoLoginServiceAbstraction,
|
SsoLoginServiceAbstraction,
|
||||||
Router,
|
Router,
|
||||||
AccountService,
|
AccountService,
|
||||||
|
ConfigService,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
@@ -378,7 +383,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
safeProvider({
|
safeProvider({
|
||||||
provide: LoginDecryptionOptionsService,
|
provide: LoginDecryptionOptionsService,
|
||||||
useClass: WebLoginDecryptionOptionsService,
|
useClass: WebLoginDecryptionOptionsService,
|
||||||
deps: [MessagingService, RouterService, AcceptOrganizationInviteService],
|
deps: [MessagingService, RouterService, OrganizationInviteService],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: IpcService,
|
provide: IpcService,
|
||||||
@@ -398,6 +403,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
MasterPasswordApiService,
|
MasterPasswordApiService,
|
||||||
InternalMasterPasswordServiceAbstraction,
|
InternalMasterPasswordServiceAbstraction,
|
||||||
UserKeyRotationService,
|
UserKeyRotationService,
|
||||||
|
RouterService,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
unauthGuardFn,
|
unauthGuardFn,
|
||||||
activeAuthGuard,
|
activeAuthGuard,
|
||||||
} from "@bitwarden/angular/auth/guards";
|
} 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 { 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 { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||||
import {
|
import {
|
||||||
@@ -144,13 +145,29 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "update-temp-password",
|
path: "update-temp-password",
|
||||||
component: UpdateTempPasswordComponent,
|
component: UpdateTempPasswordComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [
|
||||||
|
canAccessFeature(
|
||||||
|
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
|
||||||
|
false,
|
||||||
|
"change-password",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
authGuard,
|
||||||
|
],
|
||||||
data: { titleId: "updateTempPassword" } satisfies RouteDataProperties,
|
data: { titleId: "updateTempPassword" } satisfies RouteDataProperties,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "update-password",
|
path: "update-password",
|
||||||
component: UpdatePasswordComponent,
|
component: UpdatePasswordComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [
|
||||||
|
canAccessFeature(
|
||||||
|
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
|
||||||
|
false,
|
||||||
|
"change-password",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
authGuard,
|
||||||
|
],
|
||||||
data: { titleId: "updatePassword" } satisfies RouteDataProperties,
|
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",
|
path: "setup-extension",
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -1785,6 +1785,9 @@
|
|||||||
"loggedOutWarning": {
|
"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."
|
"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": {
|
"emailChanged": {
|
||||||
"message": "Email saved"
|
"message": "Email saved"
|
||||||
},
|
},
|
||||||
@@ -6077,6 +6080,12 @@
|
|||||||
"updateMasterPassword": {
|
"updateMasterPassword": {
|
||||||
"message": "Update master password"
|
"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": {
|
"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."
|
"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."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,15 +37,15 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
|
|||||||
protected destroy$ = new Subject<void>();
|
protected destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
protected accountService: AccountService,
|
||||||
|
protected dialogService: DialogService,
|
||||||
protected i18nService: I18nService,
|
protected i18nService: I18nService,
|
||||||
|
protected kdfConfigService: KdfConfigService,
|
||||||
protected keyService: KeyService,
|
protected keyService: KeyService,
|
||||||
|
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||||
protected messagingService: MessagingService,
|
protected messagingService: MessagingService,
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
protected policyService: PolicyService,
|
protected policyService: PolicyService,
|
||||||
protected dialogService: DialogService,
|
|
||||||
protected kdfConfigService: KdfConfigService,
|
|
||||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
|
||||||
protected accountService: AccountService,
|
|
||||||
protected toastService: ToastService,
|
protected toastService: ToastService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
import { 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 { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
@@ -58,38 +57,37 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
|
|||||||
ForceSetPasswordReason = ForceSetPasswordReason;
|
ForceSetPasswordReason = ForceSetPasswordReason;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
accountService: AccountService,
|
protected accountService: AccountService,
|
||||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
protected dialogService: DialogService,
|
||||||
i18nService: I18nService,
|
protected encryptService: EncryptService,
|
||||||
keyService: KeyService,
|
protected i18nService: I18nService,
|
||||||
messagingService: MessagingService,
|
protected kdfConfigService: KdfConfigService,
|
||||||
platformUtilsService: PlatformUtilsService,
|
protected keyService: KeyService,
|
||||||
private policyApiService: PolicyApiServiceAbstraction,
|
protected masterPasswordApiService: MasterPasswordApiService,
|
||||||
policyService: PolicyService,
|
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 router: Router,
|
||||||
private masterPasswordApiService: MasterPasswordApiService,
|
protected ssoLoginService: SsoLoginServiceAbstraction,
|
||||||
private apiService: ApiService,
|
protected syncService: SyncService,
|
||||||
private syncService: SyncService,
|
|
||||||
private route: ActivatedRoute,
|
|
||||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
|
||||||
private organizationUserApiService: OrganizationUserApiService,
|
|
||||||
private userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
|
||||||
private ssoLoginService: SsoLoginServiceAbstraction,
|
|
||||||
dialogService: DialogService,
|
|
||||||
kdfConfigService: KdfConfigService,
|
|
||||||
private encryptService: EncryptService,
|
|
||||||
protected toastService: ToastService,
|
protected toastService: ToastService,
|
||||||
|
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
|
accountService,
|
||||||
|
dialogService,
|
||||||
i18nService,
|
i18nService,
|
||||||
|
kdfConfigService,
|
||||||
keyService,
|
keyService,
|
||||||
|
masterPasswordService,
|
||||||
messagingService,
|
messagingService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
policyService,
|
policyService,
|
||||||
dialogService,
|
|
||||||
kdfConfigService,
|
|
||||||
masterPasswordService,
|
|
||||||
accountService,
|
|
||||||
toastService,
|
toastService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,15 +52,15 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
|
|||||||
toastService: ToastService,
|
toastService: ToastService,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
|
accountService,
|
||||||
|
dialogService,
|
||||||
i18nService,
|
i18nService,
|
||||||
|
kdfConfigService,
|
||||||
keyService,
|
keyService,
|
||||||
|
masterPasswordService,
|
||||||
messagingService,
|
messagingService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
policyService,
|
policyService,
|
||||||
dialogService,
|
|
||||||
kdfConfigService,
|
|
||||||
masterPasswordService,
|
|
||||||
accountService,
|
|
||||||
toastService,
|
toastService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,15 +64,15 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent imp
|
|||||||
toastService: ToastService,
|
toastService: ToastService,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
|
accountService,
|
||||||
|
dialogService,
|
||||||
i18nService,
|
i18nService,
|
||||||
|
kdfConfigService,
|
||||||
keyService,
|
keyService,
|
||||||
|
masterPasswordService,
|
||||||
messagingService,
|
messagingService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
policyService,
|
policyService,
|
||||||
dialogService,
|
|
||||||
kdfConfigService,
|
|
||||||
masterPasswordService,
|
|
||||||
accountService,
|
|
||||||
toastService,
|
toastService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,9 +47,6 @@ export const authGuard: CanActivateFn = async (
|
|||||||
const isSetInitialPasswordFlagOn = await configService.getFeatureFlag(
|
const isSetInitialPasswordFlagOn = await configService.getFeatureFlag(
|
||||||
FeatureFlag.PM16117_SetInitialPasswordRefactor,
|
FeatureFlag.PM16117_SetInitialPasswordRefactor,
|
||||||
);
|
);
|
||||||
const isChangePasswordFlagOn = await configService.getFeatureFlag(
|
|
||||||
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
|
|
||||||
);
|
|
||||||
|
|
||||||
// User JIT provisioned into a master-password-encryption org
|
// User JIT provisioned into a master-password-encryption org
|
||||||
if (
|
if (
|
||||||
@@ -114,6 +111,10 @@ export const authGuard: CanActivateFn = async (
|
|||||||
return router.createUrlTree([route]);
|
return router.createUrlTree([route]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isChangePasswordFlagOn = await configService.getFeatureFlag(
|
||||||
|
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
|
||||||
|
);
|
||||||
|
|
||||||
// Post- Account Recovery or Weak Password on login
|
// Post- Account Recovery or Weak Password on login
|
||||||
if (
|
if (
|
||||||
(forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset ||
|
(forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset ||
|
||||||
|
|||||||
@@ -6,6 +6,12 @@
|
|||||||
></i>
|
></i>
|
||||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||||
} @else {
|
} @else {
|
||||||
|
<bit-callout
|
||||||
|
*ngIf="this.forceSetPasswordReason !== ForceSetPasswordReason.AdminForcePasswordReset"
|
||||||
|
type="warning"
|
||||||
|
>{{ "changePasswordWarning" | i18n }}</bit-callout
|
||||||
|
>
|
||||||
|
|
||||||
<auth-input-password
|
<auth-input-password
|
||||||
[flow]="inputPasswordFlow"
|
[flow]="inputPasswordFlow"
|
||||||
[email]="email"
|
[email]="email"
|
||||||
@@ -15,6 +21,8 @@
|
|||||||
[inlineButtons]="true"
|
[inlineButtons]="true"
|
||||||
[primaryButtonText]="{ key: 'changeMasterPassword' }"
|
[primaryButtonText]="{ key: 'changeMasterPassword' }"
|
||||||
(onPasswordFormSubmit)="handlePasswordFormSubmit($event)"
|
(onPasswordFormSubmit)="handlePasswordFormSubmit($event)"
|
||||||
|
[secondaryButtonText]="secondaryButtonText()"
|
||||||
|
(onSecondaryButtonClick)="logOut()"
|
||||||
>
|
>
|
||||||
</auth-input-password>
|
</auth-input-password>
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import { Component, Input, OnInit } from "@angular/core";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import {
|
||||||
|
InputPasswordComponent,
|
||||||
|
InputPasswordFlow,
|
||||||
|
PasswordInputResult,
|
||||||
|
} from "@bitwarden/auth/angular";
|
||||||
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
|
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||||
|
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||||
|
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.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 { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
import {
|
||||||
|
AnonLayoutWrapperDataService,
|
||||||
|
DialogService,
|
||||||
|
ToastService,
|
||||||
|
Icons,
|
||||||
|
CalloutComponent,
|
||||||
|
} from "@bitwarden/components";
|
||||||
|
import { I18nPipe } from "@bitwarden/ui-common";
|
||||||
|
|
||||||
|
import { ChangePasswordService } from "./change-password.service.abstraction";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change Password Component
|
||||||
|
*
|
||||||
|
* NOTE: The change password component uses the input-password component which will show the
|
||||||
|
* current password input form in some flows, although it could be left off. This is intentional
|
||||||
|
* and by design to maintain a strong security posture as some flows could have the user
|
||||||
|
* end up at a change password without having one before.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: "auth-change-password",
|
||||||
|
templateUrl: "change-password.component.html",
|
||||||
|
imports: [InputPasswordComponent, I18nPipe, CalloutComponent],
|
||||||
|
})
|
||||||
|
export class ChangePasswordComponent implements OnInit {
|
||||||
|
@Input() inputPasswordFlow: InputPasswordFlow = InputPasswordFlow.ChangePassword;
|
||||||
|
|
||||||
|
activeAccount: Account | null = null;
|
||||||
|
email?: string;
|
||||||
|
userId?: UserId;
|
||||||
|
masterPasswordPolicyOptions?: MasterPasswordPolicyOptions;
|
||||||
|
initializing = true;
|
||||||
|
submitting = false;
|
||||||
|
formPromise?: Promise<any>;
|
||||||
|
forceSetPasswordReason: ForceSetPasswordReason = ForceSetPasswordReason.None;
|
||||||
|
|
||||||
|
protected readonly ForceSetPasswordReason = ForceSetPasswordReason;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private accountService: AccountService,
|
||||||
|
private changePasswordService: ChangePasswordService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||||
|
private anonLayoutWrapperDataService: AnonLayoutWrapperDataService,
|
||||||
|
private organizationInviteService: OrganizationInviteService,
|
||||||
|
private messagingService: MessagingService,
|
||||||
|
private policyService: PolicyService,
|
||||||
|
private toastService: ToastService,
|
||||||
|
private syncService: SyncService,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private logService: LogService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||||
|
|
||||||
|
if (!this.activeAccount) {
|
||||||
|
throw new Error("No active active account found while trying to change passwords.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.userId = this.activeAccount.id;
|
||||||
|
this.email = this.activeAccount.email;
|
||||||
|
|
||||||
|
if (!this.userId) {
|
||||||
|
throw new Error("userId not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.masterPasswordPolicyOptions = await firstValueFrom(
|
||||||
|
this.policyService.masterPasswordPolicyOptions$(this.userId),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.forceSetPasswordReason = await firstValueFrom(
|
||||||
|
this.masterPasswordService.forceSetPasswordReason$(this.userId),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset) {
|
||||||
|
this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({
|
||||||
|
pageIcon: Icons.LockIcon,
|
||||||
|
pageTitle: { key: "updateMasterPassword" },
|
||||||
|
pageSubtitle: { key: "accountRecoveryUpdateMasterPasswordSubtitle" },
|
||||||
|
});
|
||||||
|
} else if (this.forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword) {
|
||||||
|
this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({
|
||||||
|
pageIcon: Icons.LockIcon,
|
||||||
|
pageTitle: { key: "updateMasterPassword" },
|
||||||
|
pageSubtitle: { key: "updateMasterPasswordSubtitle" },
|
||||||
|
maxWidth: "lg",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initializing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async logOut() {
|
||||||
|
const confirmed = await this.dialogService.openSimpleDialog({
|
||||||
|
title: { key: "logOut" },
|
||||||
|
content: { key: "logOutConfirmation" },
|
||||||
|
acceptButtonText: { key: "logOut" },
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
|
await this.organizationInviteService.clearOrganizationInvitation();
|
||||||
|
|
||||||
|
if (this.changePasswordService.clearDeeplinkState) {
|
||||||
|
await this.changePasswordService.clearDeeplinkState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: PM-23515 eventually use the logout service instead of messaging service once it is available without circular dependencies
|
||||||
|
this.messagingService.send("logout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) {
|
||||||
|
this.submitting = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (passwordInputResult.rotateUserKey) {
|
||||||
|
if (this.activeAccount == null) {
|
||||||
|
throw new Error("activeAccount not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
passwordInputResult.currentPassword == null ||
|
||||||
|
passwordInputResult.newPasswordHint == null
|
||||||
|
) {
|
||||||
|
throw new Error("currentPassword or newPasswordHint not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.syncService.fullSync(true);
|
||||||
|
|
||||||
|
await this.changePasswordService.rotateUserKeyMasterPasswordAndEncryptedData(
|
||||||
|
passwordInputResult.currentPassword,
|
||||||
|
passwordInputResult.newPassword,
|
||||||
|
this.activeAccount,
|
||||||
|
passwordInputResult.newPasswordHint,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (!this.userId) {
|
||||||
|
throw new Error("userId not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset) {
|
||||||
|
await this.changePasswordService.changePasswordForAccountRecovery(
|
||||||
|
passwordInputResult,
|
||||||
|
this.userId,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await this.changePasswordService.changePassword(passwordInputResult, this.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toastService.showToast({
|
||||||
|
variant: "success",
|
||||||
|
message: this.i18nService.t("masterPasswordChanged"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: PM-23515 eventually use the logout service instead of messaging service once it is available without circular dependencies
|
||||||
|
this.messagingService.send("logout");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logService.error(error);
|
||||||
|
this.toastService.showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: "",
|
||||||
|
message: this.i18nService.t("errorOccurred"),
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
this.submitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the logout button in the case of admin force reset password or weak password upon login.
|
||||||
|
*/
|
||||||
|
protected secondaryButtonText(): { key: string } | undefined {
|
||||||
|
return this.forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset ||
|
||||||
|
this.forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword
|
||||||
|
? { key: "logOut" }
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { PasswordInputResult } from "@bitwarden/auth/angular";
|
import { PasswordInputResult } from "@bitwarden/auth/angular";
|
||||||
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
@@ -32,5 +34,29 @@ export abstract class ChangePasswordService {
|
|||||||
* @param userId the `userId`
|
* @param userId the `userId`
|
||||||
* @throws if the `userId`, `currentMasterKey`, or `currentServerMasterKeyHash` is not found
|
* @throws if the `userId`, `currentMasterKey`, or `currentServerMasterKeyHash` is not found
|
||||||
*/
|
*/
|
||||||
abstract changePassword(passwordInputResult: PasswordInputResult, userId: UserId): Promise<void>;
|
abstract changePassword(
|
||||||
|
passwordInputResult: PasswordInputResult,
|
||||||
|
userId: UserId | null,
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the user's password and re-encrypts the user key with the `newMasterKey`.
|
||||||
|
* - Specifically, this method uses credentials from the `passwordInputResult` to:
|
||||||
|
* 1. Decrypt the user key with the `currentMasterKey`
|
||||||
|
* 2. Re-encrypt that user key with the `newMasterKey`, resulting in a `newMasterKeyEncryptedUserKey`
|
||||||
|
* 3. Build a `PasswordRequest` object that gets PUTed to `"/accounts/update-temp-password"` so that the
|
||||||
|
* ForcePasswordReset gets set to false.
|
||||||
|
* @param passwordInputResult
|
||||||
|
* @param userId
|
||||||
|
*/
|
||||||
|
abstract changePasswordForAccountRecovery(
|
||||||
|
passwordInputResult: PasswordInputResult,
|
||||||
|
userId: UserId,
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional method that will clear up any deep link state.
|
||||||
|
* - Currently only used on the web change password service.
|
||||||
|
*/
|
||||||
|
clearDeeplinkState?: () => Promise<void>;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { PasswordInputResult } from "@bitwarden/auth/angular";
|
||||||
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
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 { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||||
@@ -9,8 +12,6 @@ import { UserId } from "@bitwarden/common/types/guid";
|
|||||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||||
import { KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management";
|
import { KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { PasswordInputResult } from "../input-password/password-input-result";
|
|
||||||
|
|
||||||
import { ChangePasswordService } from "./change-password.service.abstraction";
|
import { ChangePasswordService } from "./change-password.service.abstraction";
|
||||||
import { DefaultChangePasswordService } from "./default-change-password.service";
|
import { DefaultChangePasswordService } from "./default-change-password.service";
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ describe("DefaultChangePasswordService", () => {
|
|||||||
it("should throw if a currentMasterKey was not found", async () => {
|
it("should throw if a currentMasterKey was not found", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const incorrectPasswordInputResult = { ...passwordInputResult };
|
const incorrectPasswordInputResult = { ...passwordInputResult };
|
||||||
incorrectPasswordInputResult.currentMasterKey = null;
|
incorrectPasswordInputResult.currentMasterKey = undefined;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const testFn = sut.changePassword(incorrectPasswordInputResult, userId);
|
const testFn = sut.changePassword(incorrectPasswordInputResult, userId);
|
||||||
@@ -123,7 +124,7 @@ describe("DefaultChangePasswordService", () => {
|
|||||||
it("should throw if a currentServerMasterKeyHash was not found", async () => {
|
it("should throw if a currentServerMasterKeyHash was not found", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const incorrectPasswordInputResult = { ...passwordInputResult };
|
const incorrectPasswordInputResult = { ...passwordInputResult };
|
||||||
incorrectPasswordInputResult.currentServerMasterKeyHash = null;
|
incorrectPasswordInputResult.currentServerMasterKeyHash = undefined;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const testFn = sut.changePassword(incorrectPasswordInputResult, userId);
|
const testFn = sut.changePassword(incorrectPasswordInputResult, userId);
|
||||||
@@ -174,4 +175,43 @@ describe("DefaultChangePasswordService", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("changePasswordForAccountRecovery()", () => {
|
||||||
|
it("should call the putUpdateTempPassword() API method with the correct UpdateTempPasswordRequest credentials", async () => {
|
||||||
|
// Act
|
||||||
|
await sut.changePasswordForAccountRecovery(passwordInputResult, userId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(masterPasswordApiService.putUpdateTempPassword).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
newMasterPasswordHash: passwordInputResult.newServerMasterKeyHash,
|
||||||
|
masterPasswordHint: passwordInputResult.newPasswordHint,
|
||||||
|
key: newMasterKeyEncryptedUserKey[1].encryptedString,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error if user key decryption fails", async () => {
|
||||||
|
// Arrange
|
||||||
|
masterPasswordService.decryptUserKeyWithMasterKey.mockResolvedValue(null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const testFn = sut.changePasswordForAccountRecovery(passwordInputResult, userId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await expect(testFn).rejects.toThrow("Could not decrypt user key");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error if putUpdateTempPassword() fails", async () => {
|
||||||
|
// Arrange
|
||||||
|
masterPasswordApiService.putUpdateTempPassword.mockRejectedValueOnce(new Error("error"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const testFn = sut.changePasswordForAccountRecovery(passwordInputResult, userId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await expect(testFn).rejects.toThrow("Could not change password");
|
||||||
|
expect(masterPasswordApiService.putUpdateTempPassword).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -1,11 +1,18 @@
|
|||||||
import { PasswordInputResult, ChangePasswordService } from "@bitwarden/auth/angular";
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { PasswordInputResult } from "@bitwarden/auth/angular";
|
||||||
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||||
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
||||||
|
import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request";
|
||||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||||
|
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
import { UserKey } from "@bitwarden/common/types/key";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
|
import { ChangePasswordService } from "./change-password.service.abstraction";
|
||||||
|
|
||||||
export class DefaultChangePasswordService implements ChangePasswordService {
|
export class DefaultChangePasswordService implements ChangePasswordService {
|
||||||
constructor(
|
constructor(
|
||||||
protected keyService: KeyService,
|
protected keyService: KeyService,
|
||||||
@@ -22,7 +29,11 @@ export class DefaultChangePasswordService implements ChangePasswordService {
|
|||||||
throw new Error("rotateUserKeyMasterPasswordAndEncryptedData() is only implemented in Web");
|
throw new Error("rotateUserKeyMasterPasswordAndEncryptedData() is only implemented in Web");
|
||||||
}
|
}
|
||||||
|
|
||||||
async changePassword(passwordInputResult: PasswordInputResult, userId: UserId) {
|
private async preparePasswordChange(
|
||||||
|
passwordInputResult: PasswordInputResult,
|
||||||
|
userId: UserId | null,
|
||||||
|
request: PasswordRequest | UpdateTempPasswordRequest,
|
||||||
|
): Promise<[UserKey, EncString]> {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw new Error("userId not found");
|
throw new Error("userId not found");
|
||||||
}
|
}
|
||||||
@@ -45,15 +56,32 @@ export class DefaultChangePasswordService implements ChangePasswordService {
|
|||||||
throw new Error("Could not decrypt user key");
|
throw new Error("Could not decrypt user key");
|
||||||
}
|
}
|
||||||
|
|
||||||
const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey(
|
const newKeyValue = await this.keyService.encryptUserKeyWithMasterKey(
|
||||||
passwordInputResult.newMasterKey,
|
passwordInputResult.newMasterKey,
|
||||||
decryptedUserKey,
|
decryptedUserKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
const request = new PasswordRequest();
|
if (request instanceof PasswordRequest) {
|
||||||
request.masterPasswordHash = passwordInputResult.currentServerMasterKeyHash;
|
request.masterPasswordHash = passwordInputResult.currentServerMasterKeyHash;
|
||||||
request.newMasterPasswordHash = passwordInputResult.newServerMasterKeyHash;
|
request.newMasterPasswordHash = passwordInputResult.newServerMasterKeyHash;
|
||||||
request.masterPasswordHint = passwordInputResult.newPasswordHint;
|
request.masterPasswordHint = passwordInputResult.newPasswordHint;
|
||||||
|
} else if (request instanceof UpdateTempPasswordRequest) {
|
||||||
|
request.newMasterPasswordHash = passwordInputResult.newServerMasterKeyHash;
|
||||||
|
request.masterPasswordHint = passwordInputResult.newPasswordHint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newKeyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
async changePassword(passwordInputResult: PasswordInputResult, userId: UserId | null) {
|
||||||
|
const request = new PasswordRequest();
|
||||||
|
|
||||||
|
const newMasterKeyEncryptedUserKey = await this.preparePasswordChange(
|
||||||
|
passwordInputResult,
|
||||||
|
userId,
|
||||||
|
request,
|
||||||
|
);
|
||||||
|
|
||||||
request.key = newMasterKeyEncryptedUserKey[1].encryptedString as string;
|
request.key = newMasterKeyEncryptedUserKey[1].encryptedString as string;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -62,4 +90,23 @@ export class DefaultChangePasswordService implements ChangePasswordService {
|
|||||||
throw new Error("Could not change password");
|
throw new Error("Could not change password");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async changePasswordForAccountRecovery(passwordInputResult: PasswordInputResult, userId: UserId) {
|
||||||
|
const request = new UpdateTempPasswordRequest();
|
||||||
|
|
||||||
|
const newMasterKeyEncryptedUserKey = await this.preparePasswordChange(
|
||||||
|
passwordInputResult,
|
||||||
|
userId,
|
||||||
|
request,
|
||||||
|
);
|
||||||
|
|
||||||
|
request.key = newMasterKeyEncryptedUserKey[1].encryptedString as string;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO: PM-23047 will look to consolidate this into the change password endpoint.
|
||||||
|
await this.masterPasswordApiService.putUpdateTempPassword(request);
|
||||||
|
} catch {
|
||||||
|
throw new Error("Could not change password");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./change-password.component";
|
||||||
|
export * from "./change-password.service.abstraction";
|
||||||
|
export * from "./default-change-password.service";
|
||||||
@@ -11,6 +11,10 @@ import {
|
|||||||
DefaultOrganizationUserApiService,
|
DefaultOrganizationUserApiService,
|
||||||
OrganizationUserApiService,
|
OrganizationUserApiService,
|
||||||
} from "@bitwarden/admin-console/common";
|
} from "@bitwarden/admin-console/common";
|
||||||
|
import {
|
||||||
|
ChangePasswordService,
|
||||||
|
DefaultChangePasswordService,
|
||||||
|
} from "@bitwarden/angular/auth/password-management/change-password";
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import {
|
import {
|
||||||
@@ -29,8 +33,6 @@ import {
|
|||||||
TwoFactorAuthComponentService,
|
TwoFactorAuthComponentService,
|
||||||
TwoFactorAuthEmailComponentService,
|
TwoFactorAuthEmailComponentService,
|
||||||
TwoFactorAuthWebAuthnComponentService,
|
TwoFactorAuthWebAuthnComponentService,
|
||||||
ChangePasswordService,
|
|
||||||
DefaultChangePasswordService,
|
|
||||||
} from "@bitwarden/auth/angular";
|
} from "@bitwarden/auth/angular";
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
@@ -115,6 +117,8 @@ import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
|
|||||||
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";
|
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";
|
||||||
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
|
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
|
||||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/services/master-password/master-password-api.service.implementation";
|
import { MasterPasswordApiService } from "@bitwarden/common/auth/services/master-password/master-password-api.service.implementation";
|
||||||
|
import { DefaultOrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/default-organization-invite.service";
|
||||||
|
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
|
||||||
import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation";
|
import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation";
|
||||||
import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service";
|
import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service";
|
||||||
import { TokenService } from "@bitwarden/common/auth/services/token.service";
|
import { TokenService } from "@bitwarden/common/auth/services/token.service";
|
||||||
@@ -1406,16 +1410,20 @@ const safeProviders: SafeProvider[] = [
|
|||||||
useClass: DefaultKdfConfigService,
|
useClass: DefaultKdfConfigService,
|
||||||
deps: [StateProvider],
|
deps: [StateProvider],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: OrganizationInviteService,
|
||||||
|
useClass: DefaultOrganizationInviteService,
|
||||||
|
deps: [],
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: SetPasswordJitService,
|
provide: SetPasswordJitService,
|
||||||
useClass: DefaultSetPasswordJitService,
|
useClass: DefaultSetPasswordJitService,
|
||||||
deps: [
|
deps: [
|
||||||
ApiServiceAbstraction,
|
|
||||||
MasterPasswordApiServiceAbstraction,
|
|
||||||
KeyService,
|
|
||||||
EncryptService,
|
EncryptService,
|
||||||
I18nServiceAbstraction,
|
I18nServiceAbstraction,
|
||||||
KdfConfigService,
|
KdfConfigService,
|
||||||
|
KeyService,
|
||||||
|
MasterPasswordApiServiceAbstraction,
|
||||||
InternalMasterPasswordServiceAbstraction,
|
InternalMasterPasswordServiceAbstraction,
|
||||||
OrganizationApiServiceAbstraction,
|
OrganizationApiServiceAbstraction,
|
||||||
OrganizationUserApiService,
|
OrganizationUserApiService,
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
import { Component, Input, OnInit } from "@angular/core";
|
|
||||||
import { firstValueFrom } from "rxjs";
|
|
||||||
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
|
||||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
|
||||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
|
||||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
|
||||||
// eslint-disable-next-line no-restricted-imports
|
|
||||||
import { ToastService } from "@bitwarden/components";
|
|
||||||
import { I18nPipe } from "@bitwarden/ui-common";
|
|
||||||
|
|
||||||
import {
|
|
||||||
InputPasswordComponent,
|
|
||||||
InputPasswordFlow,
|
|
||||||
} from "../input-password/input-password.component";
|
|
||||||
import { PasswordInputResult } from "../input-password/password-input-result";
|
|
||||||
|
|
||||||
import { ChangePasswordService } from "./change-password.service.abstraction";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "auth-change-password",
|
|
||||||
templateUrl: "change-password.component.html",
|
|
||||||
imports: [InputPasswordComponent, I18nPipe],
|
|
||||||
})
|
|
||||||
export class ChangePasswordComponent implements OnInit {
|
|
||||||
@Input() inputPasswordFlow: InputPasswordFlow = InputPasswordFlow.ChangePassword;
|
|
||||||
|
|
||||||
activeAccount: Account | null = null;
|
|
||||||
email?: string;
|
|
||||||
userId?: UserId;
|
|
||||||
masterPasswordPolicyOptions?: MasterPasswordPolicyOptions;
|
|
||||||
initializing = true;
|
|
||||||
submitting = false;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private accountService: AccountService,
|
|
||||||
private changePasswordService: ChangePasswordService,
|
|
||||||
private i18nService: I18nService,
|
|
||||||
private messagingService: MessagingService,
|
|
||||||
private policyService: PolicyService,
|
|
||||||
private toastService: ToastService,
|
|
||||||
private syncService: SyncService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
|
||||||
this.activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
|
||||||
this.userId = this.activeAccount?.id;
|
|
||||||
this.email = this.activeAccount?.email;
|
|
||||||
|
|
||||||
if (!this.userId) {
|
|
||||||
throw new Error("userId not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.masterPasswordPolicyOptions = await firstValueFrom(
|
|
||||||
this.policyService.masterPasswordPolicyOptions$(this.userId),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.initializing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) {
|
|
||||||
this.submitting = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (passwordInputResult.rotateUserKey) {
|
|
||||||
if (this.activeAccount == null) {
|
|
||||||
throw new Error("activeAccount not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
passwordInputResult.currentPassword == null ||
|
|
||||||
passwordInputResult.newPasswordHint == null
|
|
||||||
) {
|
|
||||||
throw new Error("currentPassword or newPasswordHint not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.syncService.fullSync(true);
|
|
||||||
|
|
||||||
await this.changePasswordService.rotateUserKeyMasterPasswordAndEncryptedData(
|
|
||||||
passwordInputResult.currentPassword,
|
|
||||||
passwordInputResult.newPassword,
|
|
||||||
this.activeAccount,
|
|
||||||
passwordInputResult.newPasswordHint,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (!this.userId) {
|
|
||||||
throw new Error("userId not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.changePasswordService.changePassword(passwordInputResult, this.userId);
|
|
||||||
|
|
||||||
this.toastService.showToast({
|
|
||||||
variant: "success",
|
|
||||||
title: this.i18nService.t("masterPasswordChanged"),
|
|
||||||
message: this.i18nService.t("masterPasswordChangedDesc"),
|
|
||||||
});
|
|
||||||
|
|
||||||
this.messagingService.send("logout");
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
this.toastService.showToast({
|
|
||||||
variant: "error",
|
|
||||||
title: "",
|
|
||||||
message: this.i18nService.t("errorOccurred"),
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
this.submitting = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* This barrel file should only contain Angular exports
|
* This barrel file should only contain Angular exports
|
||||||
*/
|
*/
|
||||||
// change password
|
|
||||||
export * from "./change-password/change-password.component";
|
|
||||||
export * from "./change-password/change-password.service.abstraction";
|
|
||||||
export * from "./change-password/default-change-password.service";
|
|
||||||
|
|
||||||
// fingerprint dialog
|
// fingerprint dialog
|
||||||
export * from "./fingerprint-dialog/fingerprint-dialog.component";
|
export * from "./fingerprint-dialog/fingerprint-dialog.component";
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
|
|
||||||
<div class="tw-mb-6">
|
<div class="tw-mb-6">
|
||||||
<bit-form-field>
|
<bit-form-field [disableMargin]="true">
|
||||||
<bit-label>{{ "newMasterPass" | i18n }}</bit-label>
|
<bit-label>{{ "newMasterPass" | i18n }}</bit-label>
|
||||||
<input
|
<input
|
||||||
id="input-password-form_new-password"
|
id="input-password-form_new-password"
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export class InputPasswordComponent implements OnInit {
|
|||||||
@Input({ transform: (val: string) => val?.trim().toLowerCase() }) email?: string;
|
@Input({ transform: (val: string) => val?.trim().toLowerCase() }) email?: string;
|
||||||
@Input() userId?: UserId;
|
@Input() userId?: UserId;
|
||||||
@Input() loading = false;
|
@Input() loading = false;
|
||||||
@Input() masterPasswordPolicyOptions: MasterPasswordPolicyOptions | null = null;
|
@Input() masterPasswordPolicyOptions?: MasterPasswordPolicyOptions;
|
||||||
|
|
||||||
@Input() inlineButtons = false;
|
@Input() inlineButtons = false;
|
||||||
@Input() primaryButtonText?: Translation;
|
@Input() primaryButtonText?: Translation;
|
||||||
@@ -169,7 +169,7 @@ export class InputPasswordComponent implements OnInit {
|
|||||||
|
|
||||||
protected get minPasswordLengthMsg() {
|
protected get minPasswordLengthMsg() {
|
||||||
if (
|
if (
|
||||||
this.masterPasswordPolicyOptions != null &&
|
this.masterPasswordPolicyOptions != undefined &&
|
||||||
this.masterPasswordPolicyOptions.minLength > 0
|
this.masterPasswordPolicyOptions.minLength > 0
|
||||||
) {
|
) {
|
||||||
return this.i18nService.t("characterMinimum", this.masterPasswordPolicyOptions.minLength);
|
return this.i18nService.t("characterMinimum", this.masterPasswordPolicyOptions.minLength);
|
||||||
@@ -463,7 +463,7 @@ export class InputPasswordComponent implements OnInit {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns `true` if the current password is correct (it can be used to successfully decrypt
|
* Returns `true` if the current password is correct (it can be used to successfully decrypt
|
||||||
* the masterKeyEncrypedUserKey), `false` otherwise
|
* the masterKeyEncryptedUserKey), `false` otherwise
|
||||||
*/
|
*/
|
||||||
private async verifyCurrentPassword(
|
private async verifyCurrentPassword(
|
||||||
currentPassword: string,
|
currentPassword: string,
|
||||||
|
|||||||
@@ -18,9 +18,12 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
|||||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
|
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
|
||||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||||
import { ClientType, HttpStatusCode } from "@bitwarden/common/enums";
|
import { ClientType, HttpStatusCode } from "@bitwarden/common/enums";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
@@ -122,6 +125,8 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private validationService: ValidationService,
|
private validationService: ValidationService,
|
||||||
private loginSuccessHandlerService: LoginSuccessHandlerService,
|
private loginSuccessHandlerService: LoginSuccessHandlerService,
|
||||||
|
private masterPasswordService: MasterPasswordServiceAbstraction,
|
||||||
|
private configService: ConfigService,
|
||||||
) {
|
) {
|
||||||
this.clientType = this.platformUtilsService.getClientType();
|
this.clientType = this.platformUtilsService.getClientType();
|
||||||
}
|
}
|
||||||
@@ -225,7 +230,29 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = new PasswordLoginCredentials(email, masterPassword);
|
let credentials: PasswordLoginCredentials;
|
||||||
|
|
||||||
|
if (
|
||||||
|
await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)
|
||||||
|
) {
|
||||||
|
// Try to retrieve any org policies from an org invite now so we can send it to the
|
||||||
|
// login strategies. Since it is optional and we only want to be doing this on the
|
||||||
|
// web we will only send in content in the right context.
|
||||||
|
const orgPoliciesFromInvite = this.loginComponentService.getOrgPoliciesFromOrgInvite
|
||||||
|
? await this.loginComponentService.getOrgPoliciesFromOrgInvite()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const orgMasterPasswordPolicyOptions = orgPoliciesFromInvite?.enforcedPasswordPolicyOptions;
|
||||||
|
|
||||||
|
credentials = new PasswordLoginCredentials(
|
||||||
|
email,
|
||||||
|
masterPassword,
|
||||||
|
undefined,
|
||||||
|
orgMasterPasswordPolicyOptions,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
credentials = new PasswordLoginCredentials(email, masterPassword);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await this.loginStrategyService.logIn(credentials);
|
const authResult = await this.loginStrategyService.logIn(credentials);
|
||||||
@@ -284,7 +311,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|||||||
This is now unsupported and requires a downgraded client */
|
This is now unsupported and requires a downgraded client */
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
title: this.i18nService.t("errorOccured"),
|
title: this.i18nService.t("errorOccurred"),
|
||||||
message: this.i18nService.t("legacyEncryptionUnsupported"),
|
message: this.i18nService.t("legacyEncryptionUnsupported"),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -325,7 +352,13 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|||||||
orgPolicies.enforcedPasswordPolicyOptions,
|
orgPolicies.enforcedPasswordPolicyOptions,
|
||||||
);
|
);
|
||||||
if (isPasswordChangeRequired) {
|
if (isPasswordChangeRequired) {
|
||||||
await this.router.navigate(["update-password"]);
|
const changePasswordFeatureFlagOn = await this.configService.getFeatureFlag(
|
||||||
|
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.router.navigate(
|
||||||
|
changePasswordFeatureFlagOn ? ["change-password"] : ["update-password"],
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,9 +370,15 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|||||||
await this.router.navigate(["vault"]);
|
await this.router.navigate(["vault"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the master password meets the enforced policy requirements
|
* Checks if the master password meets the enforced policy requirements
|
||||||
* and if the user is required to change their password.
|
* and if the user is required to change their password.
|
||||||
|
*
|
||||||
|
* TODO: This is duplicate checking that we want to only do in the password login strategy.
|
||||||
|
* Once we no longer need the policies state being set to reference later in change password
|
||||||
|
* via using the Admin Console's new policy endpoint changes we can remove this. Consult
|
||||||
|
* PM-23001 for details.
|
||||||
*/
|
*/
|
||||||
private async isPasswordChangeRequiredByOrgPolicy(
|
private async isPasswordChangeRequiredByOrgPolicy(
|
||||||
enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions,
|
enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions,
|
||||||
|
|||||||
@@ -2,11 +2,17 @@ import { CommonModule } from "@angular/common";
|
|||||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
|
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
import { Subject, takeUntil } from "rxjs";
|
import { firstValueFrom, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { LoginSuccessHandlerService } from "@bitwarden/auth/common";
|
import { LoginSuccessHandlerService } from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
@@ -61,6 +67,9 @@ export class NewDeviceVerificationComponent implements OnInit, OnDestroy {
|
|||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private loginSuccessHandlerService: LoginSuccessHandlerService,
|
private loginSuccessHandlerService: LoginSuccessHandlerService,
|
||||||
|
private configService: ConfigService,
|
||||||
|
private accountService: AccountService,
|
||||||
|
private masterPasswordService: MasterPasswordServiceAbstraction,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@@ -141,8 +150,29 @@ export class NewDeviceVerificationComponent implements OnInit, OnDestroy {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.loginSuccessHandlerService.run(authResult.userId);
|
this.loginSuccessHandlerService.run(authResult.userId);
|
||||||
|
|
||||||
// If verification succeeds, navigate to vault
|
// TODO: PM-22663 use the new service to handle routing.
|
||||||
|
if (
|
||||||
|
await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)
|
||||||
|
) {
|
||||||
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getUserId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const forceSetPasswordReason = await firstValueFrom(
|
||||||
|
this.masterPasswordService.forceSetPasswordReason$(activeUserId),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword ||
|
||||||
|
forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset
|
||||||
|
) {
|
||||||
|
await this.router.navigate(["/change-password"]);
|
||||||
|
} else {
|
||||||
await this.router.navigate(["/vault"]);
|
await this.router.navigate(["/vault"]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await this.router.navigate(["/vault"]);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
let errorMessage =
|
let errorMessage =
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
FakeUserDecryptionOptions as UserDecryptionOptions,
|
FakeUserDecryptionOptions as UserDecryptionOptions,
|
||||||
InternalUserDecryptionOptionsServiceAbstraction,
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
} from "@bitwarden/auth/common";
|
} 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 { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||||
import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response";
|
import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response";
|
||||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||||
@@ -33,7 +32,6 @@ import { SetPasswordCredentials } from "./set-password-jit.service.abstraction";
|
|||||||
describe("DefaultSetPasswordJitService", () => {
|
describe("DefaultSetPasswordJitService", () => {
|
||||||
let sut: DefaultSetPasswordJitService;
|
let sut: DefaultSetPasswordJitService;
|
||||||
|
|
||||||
let apiService: MockProxy<ApiService>;
|
|
||||||
let masterPasswordApiService: MockProxy<MasterPasswordApiService>;
|
let masterPasswordApiService: MockProxy<MasterPasswordApiService>;
|
||||||
let keyService: MockProxy<KeyService>;
|
let keyService: MockProxy<KeyService>;
|
||||||
let encryptService: MockProxy<EncryptService>;
|
let encryptService: MockProxy<EncryptService>;
|
||||||
@@ -45,7 +43,6 @@ describe("DefaultSetPasswordJitService", () => {
|
|||||||
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
apiService = mock<ApiService>();
|
|
||||||
masterPasswordApiService = mock<MasterPasswordApiService>();
|
masterPasswordApiService = mock<MasterPasswordApiService>();
|
||||||
keyService = mock<KeyService>();
|
keyService = mock<KeyService>();
|
||||||
encryptService = mock<EncryptService>();
|
encryptService = mock<EncryptService>();
|
||||||
@@ -57,12 +54,11 @@ describe("DefaultSetPasswordJitService", () => {
|
|||||||
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||||
|
|
||||||
sut = new DefaultSetPasswordJitService(
|
sut = new DefaultSetPasswordJitService(
|
||||||
apiService,
|
|
||||||
masterPasswordApiService,
|
|
||||||
keyService,
|
|
||||||
encryptService,
|
encryptService,
|
||||||
i18nService,
|
i18nService,
|
||||||
kdfConfigService,
|
kdfConfigService,
|
||||||
|
keyService,
|
||||||
|
masterPasswordApiService,
|
||||||
masterPasswordService,
|
masterPasswordService,
|
||||||
organizationApiService,
|
organizationApiService,
|
||||||
organizationUserApiService,
|
organizationUserApiService,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
OrganizationUserResetPasswordEnrollmentRequest,
|
OrganizationUserResetPasswordEnrollmentRequest,
|
||||||
} from "@bitwarden/admin-console/common";
|
} from "@bitwarden/admin-console/common";
|
||||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
import { 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 { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||||
@@ -31,12 +30,11 @@ import {
|
|||||||
|
|
||||||
export class DefaultSetPasswordJitService implements SetPasswordJitService {
|
export class DefaultSetPasswordJitService implements SetPasswordJitService {
|
||||||
constructor(
|
constructor(
|
||||||
protected apiService: ApiService,
|
|
||||||
protected masterPasswordApiService: MasterPasswordApiService,
|
|
||||||
protected keyService: KeyService,
|
|
||||||
protected encryptService: EncryptService,
|
protected encryptService: EncryptService,
|
||||||
protected i18nService: I18nService,
|
protected i18nService: I18nService,
|
||||||
protected kdfConfigService: KdfConfigService,
|
protected kdfConfigService: KdfConfigService,
|
||||||
|
protected keyService: KeyService,
|
||||||
|
protected masterPasswordApiService: MasterPasswordApiService,
|
||||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||||
protected organizationApiService: OrganizationApiServiceAbstraction,
|
protected organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
protected organizationUserApiService: OrganizationUserApiService,
|
protected organizationUserApiService: OrganizationUserApiService,
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
title: this.i18nService.t("errorOccured"),
|
title: this.i18nService.t("errorOccurred"),
|
||||||
message: this.i18nService.t("legacyEncryptionUnsupported"),
|
message: this.i18nService.t("legacyEncryptionUnsupported"),
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
@@ -494,7 +494,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultSuccessRoute = await this.determineDefaultSuccessRoute();
|
const defaultSuccessRoute = await this.determineDefaultSuccessRoute(authResult.userId);
|
||||||
|
|
||||||
await this.router.navigate([defaultSuccessRoute], {
|
await this.router.navigate([defaultSuccessRoute], {
|
||||||
queryParams: {
|
queryParams: {
|
||||||
@@ -503,12 +503,28 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async determineDefaultSuccessRoute(): Promise<string> {
|
private async determineDefaultSuccessRoute(userId: UserId): Promise<string> {
|
||||||
const activeAccountStatus = await firstValueFrom(this.authService.activeAccountStatus$);
|
const activeAccountStatus = await firstValueFrom(this.authService.activeAccountStatus$);
|
||||||
if (activeAccountStatus === AuthenticationStatus.Locked) {
|
if (activeAccountStatus === AuthenticationStatus.Locked) {
|
||||||
return "lock";
|
return "lock";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: PM-22663 use the new service to handle routing.
|
||||||
|
if (
|
||||||
|
await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)
|
||||||
|
) {
|
||||||
|
const forceSetPasswordReason = await firstValueFrom(
|
||||||
|
this.masterPasswordService.forceSetPasswordReason$(userId),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword ||
|
||||||
|
forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset
|
||||||
|
) {
|
||||||
|
return "change-password";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return "vault";
|
return "vault";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
VaultTimeoutSettingsService,
|
VaultTimeoutSettingsService,
|
||||||
} from "@bitwarden/common/key-management/vault-timeout";
|
} from "@bitwarden/common/key-management/vault-timeout";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
@@ -55,6 +56,7 @@ describe("AuthRequestLoginStrategy", () => {
|
|||||||
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
||||||
let kdfConfigService: MockProxy<KdfConfigService>;
|
let kdfConfigService: MockProxy<KdfConfigService>;
|
||||||
let environmentService: MockProxy<EnvironmentService>;
|
let environmentService: MockProxy<EnvironmentService>;
|
||||||
|
let configService: MockProxy<ConfigService>;
|
||||||
|
|
||||||
const mockUserId = Utils.newGuid() as UserId;
|
const mockUserId = Utils.newGuid() as UserId;
|
||||||
let accountService: FakeAccountService;
|
let accountService: FakeAccountService;
|
||||||
@@ -91,6 +93,7 @@ describe("AuthRequestLoginStrategy", () => {
|
|||||||
vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
||||||
kdfConfigService = mock<KdfConfigService>();
|
kdfConfigService = mock<KdfConfigService>();
|
||||||
environmentService = mock<EnvironmentService>();
|
environmentService = mock<EnvironmentService>();
|
||||||
|
configService = mock<ConfigService>();
|
||||||
|
|
||||||
accountService = mockAccountServiceWith(mockUserId);
|
accountService = mockAccountServiceWith(mockUserId);
|
||||||
masterPasswordService = new FakeMasterPasswordService();
|
masterPasswordService = new FakeMasterPasswordService();
|
||||||
@@ -121,6 +124,7 @@ describe("AuthRequestLoginStrategy", () => {
|
|||||||
vaultTimeoutSettingsService,
|
vaultTimeoutSettingsService,
|
||||||
kdfConfigService,
|
kdfConfigService,
|
||||||
environmentService,
|
environmentService,
|
||||||
|
configService,
|
||||||
);
|
);
|
||||||
|
|
||||||
tokenResponse = identityTokenResponseFactory();
|
tokenResponse = identityTokenResponseFactory();
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
VaultTimeoutSettingsService,
|
VaultTimeoutSettingsService,
|
||||||
} from "@bitwarden/common/key-management/vault-timeout";
|
} from "@bitwarden/common/key-management/vault-timeout";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
@@ -123,6 +124,7 @@ describe("LoginStrategy", () => {
|
|||||||
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
||||||
let kdfConfigService: MockProxy<KdfConfigService>;
|
let kdfConfigService: MockProxy<KdfConfigService>;
|
||||||
let environmentService: MockProxy<EnvironmentService>;
|
let environmentService: MockProxy<EnvironmentService>;
|
||||||
|
let configService: MockProxy<ConfigService>;
|
||||||
|
|
||||||
let passwordLoginStrategy: PasswordLoginStrategy;
|
let passwordLoginStrategy: PasswordLoginStrategy;
|
||||||
let credentials: PasswordLoginCredentials;
|
let credentials: PasswordLoginCredentials;
|
||||||
@@ -148,6 +150,7 @@ describe("LoginStrategy", () => {
|
|||||||
passwordStrengthService = mock<PasswordStrengthService>();
|
passwordStrengthService = mock<PasswordStrengthService>();
|
||||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||||
environmentService = mock<EnvironmentService>();
|
environmentService = mock<EnvironmentService>();
|
||||||
|
configService = mock<ConfigService>();
|
||||||
|
|
||||||
vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
||||||
|
|
||||||
@@ -177,6 +180,7 @@ describe("LoginStrategy", () => {
|
|||||||
vaultTimeoutSettingsService,
|
vaultTimeoutSettingsService,
|
||||||
kdfConfigService,
|
kdfConfigService,
|
||||||
environmentService,
|
environmentService,
|
||||||
|
configService,
|
||||||
);
|
);
|
||||||
credentials = new PasswordLoginCredentials(email, masterPassword);
|
credentials = new PasswordLoginCredentials(email, masterPassword);
|
||||||
});
|
});
|
||||||
@@ -491,6 +495,7 @@ describe("LoginStrategy", () => {
|
|||||||
vaultTimeoutSettingsService,
|
vaultTimeoutSettingsService,
|
||||||
kdfConfigService,
|
kdfConfigService,
|
||||||
environmentService,
|
environmentService,
|
||||||
|
configService,
|
||||||
);
|
);
|
||||||
|
|
||||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||||
@@ -551,6 +556,7 @@ describe("LoginStrategy", () => {
|
|||||||
vaultTimeoutSettingsService,
|
vaultTimeoutSettingsService,
|
||||||
kdfConfigService,
|
kdfConfigService,
|
||||||
environmentService,
|
environmentService,
|
||||||
|
configService,
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = await passwordLoginStrategy.logIn(credentials);
|
const result = await passwordLoginStrategy.logIn(credentials);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
} from "@bitwarden/common/key-management/vault-timeout";
|
} from "@bitwarden/common/key-management/vault-timeout";
|
||||||
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
@@ -91,6 +92,7 @@ export abstract class LoginStrategy {
|
|||||||
protected vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
protected vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||||
protected KdfConfigService: KdfConfigService,
|
protected KdfConfigService: KdfConfigService,
|
||||||
protected environmentService: EnvironmentService,
|
protected environmentService: EnvironmentService,
|
||||||
|
protected configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
abstract exportCache(): CacheData;
|
abstract exportCache(): CacheData;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { BehaviorSubject } from "rxjs";
|
|||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
|
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||||
@@ -11,6 +12,7 @@ import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/id
|
|||||||
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
||||||
import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response";
|
import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service";
|
import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service";
|
||||||
import {
|
import {
|
||||||
@@ -18,6 +20,7 @@ import {
|
|||||||
VaultTimeoutSettingsService,
|
VaultTimeoutSettingsService,
|
||||||
} from "@bitwarden/common/key-management/vault-timeout";
|
} from "@bitwarden/common/key-management/vault-timeout";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
@@ -54,7 +57,7 @@ const masterKey = new SymmetricCryptoKey(
|
|||||||
) as MasterKey;
|
) as MasterKey;
|
||||||
const userId = Utils.newGuid() as UserId;
|
const userId = Utils.newGuid() as UserId;
|
||||||
const deviceId = Utils.newGuid();
|
const deviceId = Utils.newGuid();
|
||||||
const masterPasswordPolicy = new MasterPasswordPolicyResponse({
|
const masterPasswordPolicyResponse = new MasterPasswordPolicyResponse({
|
||||||
EnforceOnLogin: true,
|
EnforceOnLogin: true,
|
||||||
MinLength: 8,
|
MinLength: 8,
|
||||||
});
|
});
|
||||||
@@ -82,6 +85,7 @@ describe("PasswordLoginStrategy", () => {
|
|||||||
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
||||||
let kdfConfigService: MockProxy<KdfConfigService>;
|
let kdfConfigService: MockProxy<KdfConfigService>;
|
||||||
let environmentService: MockProxy<EnvironmentService>;
|
let environmentService: MockProxy<EnvironmentService>;
|
||||||
|
let configService: MockProxy<ConfigService>;
|
||||||
|
|
||||||
let passwordLoginStrategy: PasswordLoginStrategy;
|
let passwordLoginStrategy: PasswordLoginStrategy;
|
||||||
let credentials: PasswordLoginCredentials;
|
let credentials: PasswordLoginCredentials;
|
||||||
@@ -109,6 +113,7 @@ describe("PasswordLoginStrategy", () => {
|
|||||||
vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
||||||
kdfConfigService = mock<KdfConfigService>();
|
kdfConfigService = mock<KdfConfigService>();
|
||||||
environmentService = mock<EnvironmentService>();
|
environmentService = mock<EnvironmentService>();
|
||||||
|
configService = mock<ConfigService>();
|
||||||
|
|
||||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||||
tokenService.decodeAccessToken.mockResolvedValue({
|
tokenService.decodeAccessToken.mockResolvedValue({
|
||||||
@@ -148,9 +153,10 @@ describe("PasswordLoginStrategy", () => {
|
|||||||
vaultTimeoutSettingsService,
|
vaultTimeoutSettingsService,
|
||||||
kdfConfigService,
|
kdfConfigService,
|
||||||
environmentService,
|
environmentService,
|
||||||
|
configService,
|
||||||
);
|
);
|
||||||
credentials = new PasswordLoginCredentials(email, masterPassword);
|
credentials = new PasswordLoginCredentials(email, masterPassword);
|
||||||
tokenResponse = identityTokenResponseFactory(masterPasswordPolicy);
|
tokenResponse = identityTokenResponseFactory(masterPasswordPolicyResponse);
|
||||||
|
|
||||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||||
|
|
||||||
@@ -227,6 +233,67 @@ describe("PasswordLoginStrategy", () => {
|
|||||||
expect(policyService.evaluateMasterPassword).toHaveBeenCalled();
|
expect(policyService.evaluateMasterPassword).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("when given master password policies as part of the login credentials from an org invite, it combines them with the token response policies to evaluate the user's password as weak", async () => {
|
||||||
|
const passwordStrengthScore = 0;
|
||||||
|
|
||||||
|
passwordStrengthService.getPasswordStrength.mockReturnValue({
|
||||||
|
score: passwordStrengthScore,
|
||||||
|
} as any);
|
||||||
|
policyService.evaluateMasterPassword.mockReturnValue(false);
|
||||||
|
tokenService.decodeAccessToken.mockResolvedValue({ sub: userId });
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(configService, "getFeatureFlag")
|
||||||
|
.mockImplementation((flag: FeatureFlag) =>
|
||||||
|
Promise.resolve(flag === FeatureFlag.PM16117_ChangeExistingPasswordRefactor),
|
||||||
|
);
|
||||||
|
|
||||||
|
credentials.masterPasswordPoliciesFromOrgInvite = Object.assign(
|
||||||
|
new MasterPasswordPolicyOptions(),
|
||||||
|
{
|
||||||
|
minLength: 10,
|
||||||
|
minComplexity: 2,
|
||||||
|
requireUpper: true,
|
||||||
|
requireLower: true,
|
||||||
|
requireNumbers: true,
|
||||||
|
requireSpecial: true,
|
||||||
|
enforceOnLogin: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const combinedMasterPasswordPolicyOptions = Object.assign(new MasterPasswordPolicyOptions(), {
|
||||||
|
minLength: 10,
|
||||||
|
minComplexity: 2,
|
||||||
|
requireUpper: true,
|
||||||
|
requireLower: true,
|
||||||
|
requireNumbers: true,
|
||||||
|
requireSpecial: true,
|
||||||
|
enforceOnLogin: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
policyService.combineMasterPasswordPolicyOptions.mockReturnValue(
|
||||||
|
combinedMasterPasswordPolicyOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
await passwordLoginStrategy.logIn(credentials);
|
||||||
|
|
||||||
|
expect(policyService.combineMasterPasswordPolicyOptions).toHaveBeenCalledWith(
|
||||||
|
credentials.masterPasswordPoliciesFromOrgInvite,
|
||||||
|
MasterPasswordPolicyOptions.fromResponse(masterPasswordPolicyResponse),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(policyService.evaluateMasterPassword).toHaveBeenCalledWith(
|
||||||
|
passwordStrengthScore,
|
||||||
|
credentials.masterPassword,
|
||||||
|
combinedMasterPasswordPolicyOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
|
||||||
|
ForceSetPasswordReason.WeakMasterPassword,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("forces the user to update their master password on successful login when it does not meet master password policy requirements", async () => {
|
it("forces the user to update their master password on successful login when it does not meet master password policy requirements", async () => {
|
||||||
passwordStrengthService.getPasswordStrength.mockReturnValue({ score: 0 } as any);
|
passwordStrengthService.getPasswordStrength.mockReturnValue({ score: 0 } as any);
|
||||||
policyService.evaluateMasterPassword.mockReturnValue(false);
|
policyService.evaluateMasterPassword.mockReturnValue(false);
|
||||||
@@ -251,7 +318,7 @@ describe("PasswordLoginStrategy", () => {
|
|||||||
TwoFactorProviders2: { 0: null },
|
TwoFactorProviders2: { 0: null },
|
||||||
error: "invalid_grant",
|
error: "invalid_grant",
|
||||||
error_description: "Two factor required.",
|
error_description: "Two factor required.",
|
||||||
MasterPasswordPolicy: masterPasswordPolicy,
|
MasterPasswordPolicy: masterPasswordPolicyResponse,
|
||||||
});
|
});
|
||||||
|
|
||||||
// First login request fails requiring 2FA
|
// First login request fails requiring 2FA
|
||||||
@@ -271,7 +338,7 @@ describe("PasswordLoginStrategy", () => {
|
|||||||
TwoFactorProviders2: { 0: null },
|
TwoFactorProviders2: { 0: null },
|
||||||
error: "invalid_grant",
|
error: "invalid_grant",
|
||||||
error_description: "Two factor required.",
|
error_description: "Two factor required.",
|
||||||
MasterPasswordPolicy: masterPasswordPolicy,
|
MasterPasswordPolicy: masterPasswordPolicyResponse,
|
||||||
});
|
});
|
||||||
|
|
||||||
// First login request fails requiring 2FA
|
// First login request fails requiring 2FA
|
||||||
@@ -280,7 +347,7 @@ describe("PasswordLoginStrategy", () => {
|
|||||||
|
|
||||||
// Second login request succeeds
|
// Second login request succeeds
|
||||||
apiService.postIdentityToken.mockResolvedValueOnce(
|
apiService.postIdentityToken.mockResolvedValueOnce(
|
||||||
identityTokenResponseFactory(masterPasswordPolicy),
|
identityTokenResponseFactory(masterPasswordPolicyResponse),
|
||||||
);
|
);
|
||||||
await passwordLoginStrategy.logInTwoFactor({
|
await passwordLoginStrategy.logInTwoFactor({
|
||||||
provider: TwoFactorProviderType.Authenticator,
|
provider: TwoFactorProviderType.Authenticator,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide
|
|||||||
import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response";
|
import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response";
|
||||||
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
||||||
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { HashPurpose } from "@bitwarden/common/platform/enums";
|
import { HashPurpose } from "@bitwarden/common/platform/enums";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||||
@@ -75,7 +76,7 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
|||||||
this.localMasterKeyHash$ = this.cache.pipe(map((state) => state.localMasterKeyHash));
|
this.localMasterKeyHash$ = this.cache.pipe(map((state) => state.localMasterKeyHash));
|
||||||
}
|
}
|
||||||
|
|
||||||
override async logIn(credentials: PasswordLoginCredentials) {
|
override async logIn(credentials: PasswordLoginCredentials): Promise<AuthResult> {
|
||||||
const { email, masterPassword, twoFactor } = credentials;
|
const { email, masterPassword, twoFactor } = credentials;
|
||||||
|
|
||||||
const data = new PasswordLoginStrategyData();
|
const data = new PasswordLoginStrategyData();
|
||||||
@@ -163,19 +164,43 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
|||||||
credentials: PasswordLoginCredentials,
|
credentials: PasswordLoginCredentials,
|
||||||
authResult: AuthResult,
|
authResult: AuthResult,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// TODO: PM-21084 - investigate if we should be sending down masterPasswordPolicy on the IdentityDeviceVerificationResponse like we do for the IdentityTwoFactorResponse
|
// TODO: PM-21084 - investigate if we should be sending down masterPasswordPolicy on the
|
||||||
|
// IdentityDeviceVerificationResponse like we do for the IdentityTwoFactorResponse
|
||||||
// If the response is a device verification response, we don't need to evaluate the password
|
// If the response is a device verification response, we don't need to evaluate the password
|
||||||
if (identityResponse instanceof IdentityDeviceVerificationResponse) {
|
if (identityResponse instanceof IdentityDeviceVerificationResponse) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The identity result can contain master password policies for the user's organizations
|
// The identity result can contain master password policies for the user's organizations
|
||||||
const masterPasswordPolicyOptions =
|
let masterPasswordPolicyOptions: MasterPasswordPolicyOptions | undefined;
|
||||||
|
|
||||||
|
if (
|
||||||
|
await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)
|
||||||
|
) {
|
||||||
|
// Get the master password policy options from both the org invite and the identity response.
|
||||||
|
masterPasswordPolicyOptions = this.policyService.combineMasterPasswordPolicyOptions(
|
||||||
|
credentials.masterPasswordPoliciesFromOrgInvite,
|
||||||
|
this.getMasterPasswordPolicyOptionsFromResponse(identityResponse),
|
||||||
|
);
|
||||||
|
|
||||||
|
// We deliberately do not check enforceOnLogin as existing users who are logging
|
||||||
|
// in after getting an org invite should always be forced to set a password that
|
||||||
|
// meets the org's policy. Org Invite -> Registration also works this way for
|
||||||
|
// new BW users as well.
|
||||||
|
if (
|
||||||
|
!credentials.masterPasswordPoliciesFromOrgInvite &&
|
||||||
|
!masterPasswordPolicyOptions?.enforceOnLogin
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
masterPasswordPolicyOptions =
|
||||||
this.getMasterPasswordPolicyOptionsFromResponse(identityResponse);
|
this.getMasterPasswordPolicyOptionsFromResponse(identityResponse);
|
||||||
|
|
||||||
if (!masterPasswordPolicyOptions?.enforceOnLogin) {
|
if (!masterPasswordPolicyOptions?.enforceOnLogin) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If there is a policy active, evaluate the supplied password before its no longer in memory
|
// If there is a policy active, evaluate the supplied password before its no longer in memory
|
||||||
const meetsRequirements = this.evaluateMasterPassword(credentials, masterPasswordPolicyOptions);
|
const meetsRequirements = this.evaluateMasterPassword(credentials, masterPasswordPolicyOptions);
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ describe("SsoLoginStrategy", () => {
|
|||||||
deviceTrustService,
|
deviceTrustService,
|
||||||
authRequestService,
|
authRequestService,
|
||||||
i18nService,
|
i18nService,
|
||||||
configService,
|
|
||||||
accountService,
|
accountService,
|
||||||
masterPasswordService,
|
masterPasswordService,
|
||||||
keyService,
|
keyService,
|
||||||
@@ -157,6 +156,7 @@ describe("SsoLoginStrategy", () => {
|
|||||||
vaultTimeoutSettingsService,
|
vaultTimeoutSettingsService,
|
||||||
kdfConfigService,
|
kdfConfigService,
|
||||||
environmentService,
|
environmentService,
|
||||||
|
configService,
|
||||||
);
|
);
|
||||||
credentials = new SsoLoginCredentials(ssoCode, ssoCodeVerifier, ssoRedirectUrl, ssoOrgId);
|
credentials = new SsoLoginCredentials(ssoCode, ssoCodeVerifier, ssoRedirectUrl, ssoOrgId);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|||||||
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
|
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
|
||||||
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
|
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
|
||||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
@@ -74,7 +73,6 @@ export class SsoLoginStrategy extends LoginStrategy {
|
|||||||
private deviceTrustService: DeviceTrustServiceAbstraction,
|
private deviceTrustService: DeviceTrustServiceAbstraction,
|
||||||
private authRequestService: AuthRequestServiceAbstraction,
|
private authRequestService: AuthRequestServiceAbstraction,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private configService: ConfigService,
|
|
||||||
...sharedDeps: ConstructorParameters<typeof LoginStrategy>
|
...sharedDeps: ConstructorParameters<typeof LoginStrategy>
|
||||||
) {
|
) {
|
||||||
super(...sharedDeps);
|
super(...sharedDeps);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
VaultTimeoutSettingsService,
|
VaultTimeoutSettingsService,
|
||||||
} from "@bitwarden/common/key-management/vault-timeout";
|
} from "@bitwarden/common/key-management/vault-timeout";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import {
|
import {
|
||||||
Environment,
|
Environment,
|
||||||
EnvironmentService,
|
EnvironmentService,
|
||||||
@@ -56,6 +57,7 @@ describe("UserApiLoginStrategy", () => {
|
|||||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||||
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
||||||
let kdfConfigService: MockProxy<KdfConfigService>;
|
let kdfConfigService: MockProxy<KdfConfigService>;
|
||||||
|
let configService: MockProxy<ConfigService>;
|
||||||
|
|
||||||
let apiLogInStrategy: UserApiLoginStrategy;
|
let apiLogInStrategy: UserApiLoginStrategy;
|
||||||
let credentials: UserApiLoginCredentials;
|
let credentials: UserApiLoginCredentials;
|
||||||
@@ -88,6 +90,7 @@ describe("UserApiLoginStrategy", () => {
|
|||||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||||
vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
||||||
kdfConfigService = mock<KdfConfigService>();
|
kdfConfigService = mock<KdfConfigService>();
|
||||||
|
configService = mock<ConfigService>();
|
||||||
|
|
||||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||||
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
||||||
@@ -115,6 +118,7 @@ describe("UserApiLoginStrategy", () => {
|
|||||||
vaultTimeoutSettingsService,
|
vaultTimeoutSettingsService,
|
||||||
kdfConfigService,
|
kdfConfigService,
|
||||||
environmentService,
|
environmentService,
|
||||||
|
configService,
|
||||||
);
|
);
|
||||||
|
|
||||||
credentials = new UserApiLoginCredentials(apiClientId, apiClientSecret);
|
credentials = new UserApiLoginCredentials(apiClientId, apiClientSecret);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
VaultTimeoutSettingsService,
|
VaultTimeoutSettingsService,
|
||||||
} from "@bitwarden/common/key-management/vault-timeout";
|
} from "@bitwarden/common/key-management/vault-timeout";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
@@ -54,6 +55,7 @@ describe("WebAuthnLoginStrategy", () => {
|
|||||||
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
||||||
let kdfConfigService: MockProxy<KdfConfigService>;
|
let kdfConfigService: MockProxy<KdfConfigService>;
|
||||||
let environmentService: MockProxy<EnvironmentService>;
|
let environmentService: MockProxy<EnvironmentService>;
|
||||||
|
let configService: MockProxy<ConfigService>;
|
||||||
|
|
||||||
let webAuthnLoginStrategy!: WebAuthnLoginStrategy;
|
let webAuthnLoginStrategy!: WebAuthnLoginStrategy;
|
||||||
|
|
||||||
@@ -98,6 +100,7 @@ describe("WebAuthnLoginStrategy", () => {
|
|||||||
vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
||||||
kdfConfigService = mock<KdfConfigService>();
|
kdfConfigService = mock<KdfConfigService>();
|
||||||
environmentService = mock<EnvironmentService>();
|
environmentService = mock<EnvironmentService>();
|
||||||
|
configService = mock<ConfigService>();
|
||||||
|
|
||||||
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
||||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||||
@@ -124,6 +127,7 @@ describe("WebAuthnLoginStrategy", () => {
|
|||||||
vaultTimeoutSettingsService,
|
vaultTimeoutSettingsService,
|
||||||
kdfConfigService,
|
kdfConfigService,
|
||||||
environmentService,
|
environmentService,
|
||||||
|
configService,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create credentials
|
// Create credentials
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Jsonify } from "type-fest";
|
import { Jsonify } from "type-fest";
|
||||||
|
|
||||||
|
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||||
import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type";
|
import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type";
|
||||||
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
||||||
import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request";
|
import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request";
|
||||||
@@ -15,6 +16,7 @@ export class PasswordLoginCredentials {
|
|||||||
public email: string,
|
public email: string,
|
||||||
public masterPassword: string,
|
public masterPassword: string,
|
||||||
public twoFactor?: TokenTwoFactorRequest,
|
public twoFactor?: TokenTwoFactorRequest,
|
||||||
|
public masterPasswordPoliciesFromOrgInvite?: MasterPasswordPolicyOptions,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -402,6 +402,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
|||||||
this.vaultTimeoutSettingsService,
|
this.vaultTimeoutSettingsService,
|
||||||
this.kdfConfigService,
|
this.kdfConfigService,
|
||||||
this.environmentService,
|
this.environmentService,
|
||||||
|
this.configService,
|
||||||
];
|
];
|
||||||
|
|
||||||
return source.pipe(
|
return source.pipe(
|
||||||
@@ -425,7 +426,6 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
|||||||
this.deviceTrustService,
|
this.deviceTrustService,
|
||||||
this.authRequestService,
|
this.authRequestService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.configService,
|
|
||||||
...sharedDeps,
|
...sharedDeps,
|
||||||
);
|
);
|
||||||
case AuthenticationType.UserApiKey:
|
case AuthenticationType.UserApiKey:
|
||||||
|
|||||||
@@ -50,6 +50,25 @@ export abstract class PolicyService {
|
|||||||
policies?: Policy[],
|
policies?: Policy[],
|
||||||
) => Observable<MasterPasswordPolicyOptions | undefined>;
|
) => Observable<MasterPasswordPolicyOptions | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines all Master Password policies that are passed in and returns
|
||||||
|
* back the strongest combination of all the policies in the form of a
|
||||||
|
* MasterPasswordPolicyOptions.
|
||||||
|
* @param policies
|
||||||
|
*/
|
||||||
|
abstract combinePoliciesIntoMasterPasswordPolicyOptions(
|
||||||
|
policies: Policy[],
|
||||||
|
): MasterPasswordPolicyOptions | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes an arbitrary amount of Master Password Policy options in any form and merges them
|
||||||
|
* together using the strictest combination of all of them.
|
||||||
|
* @param masterPasswordPolicyOptions
|
||||||
|
*/
|
||||||
|
abstract combineMasterPasswordPolicyOptions(
|
||||||
|
...masterPasswordPolicyOptions: MasterPasswordPolicyOptions[]
|
||||||
|
): MasterPasswordPolicyOptions | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates whether a proposed Master Password complies with all Master Password policies that apply to the user.
|
* Evaluates whether a proposed Master Password complies with all Master Password policies that apply to the user.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -536,6 +536,152 @@ describe("PolicyService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("combinePoliciesIntoMasterPasswordPolicyOptions", () => {
|
||||||
|
let policyService: DefaultPolicyService;
|
||||||
|
let stateProvider: FakeStateProvider;
|
||||||
|
let organizationService: MockProxy<OrganizationService>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
stateProvider = new FakeStateProvider(mockAccountServiceWith(userId));
|
||||||
|
organizationService = mock<OrganizationService>();
|
||||||
|
policyService = new DefaultPolicyService(stateProvider, organizationService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined when there are no policies", () => {
|
||||||
|
const result = policyService.combinePoliciesIntoMasterPasswordPolicyOptions([]);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns options for a single policy", () => {
|
||||||
|
const masterPasswordPolicyRequirements = {
|
||||||
|
minComplexity: 3,
|
||||||
|
minLength: 10,
|
||||||
|
requireUpper: true,
|
||||||
|
};
|
||||||
|
const policies = [
|
||||||
|
new Policy(
|
||||||
|
policyData(
|
||||||
|
"1",
|
||||||
|
"org1",
|
||||||
|
PolicyType.MasterPassword,
|
||||||
|
true,
|
||||||
|
masterPasswordPolicyRequirements,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = policyService.combinePoliciesIntoMasterPasswordPolicyOptions(policies);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
minComplexity: 3,
|
||||||
|
minLength: 10,
|
||||||
|
requireUpper: true,
|
||||||
|
requireLower: false,
|
||||||
|
requireNumbers: false,
|
||||||
|
requireSpecial: false,
|
||||||
|
enforceOnLogin: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("merges options from multiple policies", () => {
|
||||||
|
const masterPasswordPolicyRequirements1 = {
|
||||||
|
minComplexity: 3,
|
||||||
|
minLength: 10,
|
||||||
|
requireUpper: true,
|
||||||
|
};
|
||||||
|
const masterPasswordPolicyRequirements2 = { minComplexity: 5, requireNumbers: true };
|
||||||
|
const policies = [
|
||||||
|
new Policy(
|
||||||
|
policyData(
|
||||||
|
"1",
|
||||||
|
"org1",
|
||||||
|
PolicyType.MasterPassword,
|
||||||
|
true,
|
||||||
|
masterPasswordPolicyRequirements1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new Policy(
|
||||||
|
policyData(
|
||||||
|
"2",
|
||||||
|
"org2",
|
||||||
|
PolicyType.MasterPassword,
|
||||||
|
true,
|
||||||
|
masterPasswordPolicyRequirements2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = policyService.combinePoliciesIntoMasterPasswordPolicyOptions(policies);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
minComplexity: 5,
|
||||||
|
minLength: 10,
|
||||||
|
requireUpper: true,
|
||||||
|
requireLower: false,
|
||||||
|
requireNumbers: true,
|
||||||
|
requireSpecial: false,
|
||||||
|
enforceOnLogin: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores disabled policies", () => {
|
||||||
|
const masterPasswordPolicyRequirements = {
|
||||||
|
minComplexity: 3,
|
||||||
|
minLength: 10,
|
||||||
|
requireUpper: true,
|
||||||
|
};
|
||||||
|
const policies = [
|
||||||
|
new Policy(
|
||||||
|
policyData(
|
||||||
|
"1",
|
||||||
|
"org1",
|
||||||
|
PolicyType.MasterPassword,
|
||||||
|
false,
|
||||||
|
masterPasswordPolicyRequirements,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = policyService.combinePoliciesIntoMasterPasswordPolicyOptions(policies);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores policies with no data", () => {
|
||||||
|
const policies = [new Policy(policyData("1", "org1", PolicyType.MasterPassword, true))];
|
||||||
|
|
||||||
|
const result = policyService.combinePoliciesIntoMasterPasswordPolicyOptions(policies);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined when policies are not MasterPassword related", () => {
|
||||||
|
const unrelatedPolicyRequirements = {
|
||||||
|
minComplexity: 3,
|
||||||
|
minLength: 10,
|
||||||
|
requireUpper: true,
|
||||||
|
};
|
||||||
|
const policies = [
|
||||||
|
new Policy(
|
||||||
|
policyData(
|
||||||
|
"1",
|
||||||
|
"org1",
|
||||||
|
PolicyType.MaximumVaultTimeout,
|
||||||
|
true,
|
||||||
|
unrelatedPolicyRequirements,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new Policy(
|
||||||
|
policyData("2", "org2", PolicyType.DisableSend, true, unrelatedPolicyRequirements),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = policyService.combinePoliciesIntoMasterPasswordPolicyOptions(policies);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function policyData(
|
function policyData(
|
||||||
id: string,
|
id: string,
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ export class DefaultPolicyService implements PolicyService {
|
|||||||
const policies$ = policies ? of(policies) : this.policies$(userId);
|
const policies$ = policies ? of(policies) : this.policies$(userId);
|
||||||
return policies$.pipe(
|
return policies$.pipe(
|
||||||
map((obsPolicies) => {
|
map((obsPolicies) => {
|
||||||
|
// TODO: replace with this.combinePoliciesIntoMasterPasswordPolicyOptions(obsPolicies)) once
|
||||||
|
// FeatureFlag.PM16117_ChangeExistingPasswordRefactor is removed.
|
||||||
let enforcedOptions: MasterPasswordPolicyOptions | undefined = undefined;
|
let enforcedOptions: MasterPasswordPolicyOptions | undefined = undefined;
|
||||||
const filteredPolicies =
|
const filteredPolicies =
|
||||||
obsPolicies.filter((p) => p.type === PolicyType.MasterPassword) ?? [];
|
obsPolicies.filter((p) => p.type === PolicyType.MasterPassword) ?? [];
|
||||||
@@ -146,6 +148,47 @@ export class DefaultPolicyService implements PolicyService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
combinePoliciesIntoMasterPasswordPolicyOptions(
|
||||||
|
policies: Policy[],
|
||||||
|
): MasterPasswordPolicyOptions | undefined {
|
||||||
|
let enforcedOptions: MasterPasswordPolicyOptions | undefined = undefined;
|
||||||
|
const filteredPolicies = policies.filter((p) => p.type === PolicyType.MasterPassword) ?? [];
|
||||||
|
|
||||||
|
if (filteredPolicies.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredPolicies.forEach((currentPolicy) => {
|
||||||
|
if (!currentPolicy.enabled || !currentPolicy.data) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enforcedOptions) {
|
||||||
|
enforcedOptions = new MasterPasswordPolicyOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mergeMasterPasswordPolicyOptions(enforcedOptions, currentPolicy.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
return enforcedOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
combineMasterPasswordPolicyOptions(
|
||||||
|
...policies: MasterPasswordPolicyOptions[]
|
||||||
|
): MasterPasswordPolicyOptions | undefined {
|
||||||
|
let combinedOptions: MasterPasswordPolicyOptions | undefined = undefined;
|
||||||
|
|
||||||
|
policies.forEach((currentOptions) => {
|
||||||
|
if (!combinedOptions) {
|
||||||
|
combinedOptions = new MasterPasswordPolicyOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mergeMasterPasswordPolicyOptions(combinedOptions, currentOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
return combinedOptions;
|
||||||
|
}
|
||||||
|
|
||||||
evaluateMasterPassword(
|
evaluateMasterPassword(
|
||||||
passwordStrength: number,
|
passwordStrength: number,
|
||||||
newPassword: string,
|
newPassword: string,
|
||||||
@@ -245,4 +288,28 @@ export class DefaultPolicyService implements PolicyService {
|
|||||||
return organization.canManagePolicies;
|
return organization.canManagePolicies;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private mergeMasterPasswordPolicyOptions(
|
||||||
|
target: MasterPasswordPolicyOptions | undefined,
|
||||||
|
source: MasterPasswordPolicyOptions | undefined,
|
||||||
|
) {
|
||||||
|
if (!target) {
|
||||||
|
target = new MasterPasswordPolicyOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
// For complexity and minLength, take the highest value.
|
||||||
|
// For boolean settings, enable it if either policy has it enabled (OR).
|
||||||
|
if (source) {
|
||||||
|
target.minComplexity = Math.max(
|
||||||
|
target.minComplexity,
|
||||||
|
source.minComplexity ?? target.minComplexity,
|
||||||
|
);
|
||||||
|
target.minLength = Math.max(target.minLength, source.minLength ?? target.minLength);
|
||||||
|
target.requireUpper = Boolean(target.requireUpper || source.requireUpper);
|
||||||
|
target.requireLower = Boolean(target.requireLower || source.requireLower);
|
||||||
|
target.requireNumbers = Boolean(target.requireNumbers || source.requireNumbers);
|
||||||
|
target.requireSpecial = Boolean(target.requireSpecial || source.requireSpecial);
|
||||||
|
target.enforceOnLogin = Boolean(target.enforceOnLogin || source.enforceOnLogin);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { OrganizationInvite } from "@bitwarden/common/auth/services/organization-invite/organization-invite";
|
||||||
|
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
|
||||||
|
|
||||||
|
export class DefaultOrganizationInviteService implements OrganizationInviteService {
|
||||||
|
/**
|
||||||
|
* No-op implementation.
|
||||||
|
*/
|
||||||
|
async getOrganizationInvite(): Promise<OrganizationInvite | null> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No-op implementation.
|
||||||
|
* @param invite an organization invite
|
||||||
|
*/
|
||||||
|
async setOrganizationInvitation(invite: OrganizationInvite): Promise<void> {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No-op implementation.
|
||||||
|
* */
|
||||||
|
async clearOrganizationInvitation(): Promise<void> {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { OrganizationInvite } from "@bitwarden/common/auth/services/organization-invite/organization-invite";
|
||||||
|
import { KeyDefinition, ORGANIZATION_INVITE_DISK } from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
|
// We're storing the organization invite for 2 reasons:
|
||||||
|
// 1. If the org requires a MP policy check, we need to keep track that the user has already been redirected when they return.
|
||||||
|
// 2. The MP policy check happens on login/register flows, we need to store the token to retrieve the policies then.
|
||||||
|
export const ORGANIZATION_INVITE = new KeyDefinition<OrganizationInvite | null>(
|
||||||
|
ORGANIZATION_INVITE_DISK,
|
||||||
|
"organizationInvite",
|
||||||
|
{
|
||||||
|
deserializer: (invite) => (invite ? OrganizationInvite.fromJSON(invite) : null),
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { OrganizationInvite } from "@bitwarden/common/auth/services/organization-invite/organization-invite";
|
||||||
|
|
||||||
|
export abstract class OrganizationInviteService {
|
||||||
|
/**
|
||||||
|
* Returns the currently stored organization invite
|
||||||
|
*/
|
||||||
|
abstract getOrganizationInvite: () => Promise<OrganizationInvite | null>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a new organization invite
|
||||||
|
* @param invite an organization invite
|
||||||
|
* @throws if the invite is nullish
|
||||||
|
*/
|
||||||
|
abstract setOrganizationInvitation: (invite: OrganizationInvite) => Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the currently stored organization invite
|
||||||
|
*/
|
||||||
|
abstract clearOrganizationInvitation: () => Promise<void>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Jsonify } from "type-fest";
|
||||||
|
|
||||||
|
export class OrganizationInvite {
|
||||||
|
email?: string;
|
||||||
|
initOrganization?: boolean;
|
||||||
|
orgSsoIdentifier?: string;
|
||||||
|
orgUserHasExistingUser?: boolean;
|
||||||
|
organizationId?: string;
|
||||||
|
organizationName?: string;
|
||||||
|
organizationUserId?: string;
|
||||||
|
token?: string;
|
||||||
|
|
||||||
|
static fromJSON(json: Jsonify<OrganizationInvite>): OrganizationInvite | null {
|
||||||
|
if (json == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign(new OrganizationInvite(), json);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user