mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 16:53:34 +00:00
Conflict resolution
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -19,6 +21,8 @@ export class CollectionsComponent implements OnInit {
|
||||
cipher: CipherView;
|
||||
collectionIds: string[];
|
||||
collections: CollectionView[] = [];
|
||||
organization: Organization;
|
||||
flexibleCollectionsV1Enabled: boolean;
|
||||
|
||||
protected cipherDomain: Cipher;
|
||||
|
||||
@@ -27,6 +31,7 @@ export class CollectionsComponent implements OnInit {
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected i18nService: I18nService,
|
||||
protected cipherService: CipherService,
|
||||
protected organizationService: OrganizationService,
|
||||
private logService: LogService,
|
||||
) {}
|
||||
|
||||
@@ -48,11 +53,21 @@ export class CollectionsComponent implements OnInit {
|
||||
(c as any).checked = this.collectionIds != null && this.collectionIds.indexOf(c.id) > -1;
|
||||
});
|
||||
}
|
||||
|
||||
if (this.organization == null) {
|
||||
this.organization = await this.organizationService.get(this.cipher.organizationId);
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const selectedCollectionIds = this.collections
|
||||
.filter((c) => !!(c as any).checked)
|
||||
.filter((c) => {
|
||||
if (this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
||||
return !!(c as any).checked;
|
||||
} else {
|
||||
return !!(c as any).checked && c.readOnly == null;
|
||||
}
|
||||
})
|
||||
.map((c) => c.id);
|
||||
if (!this.allowSelectNone && selectedCollectionIds.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
|
||||
@@ -14,12 +14,17 @@ import {
|
||||
throwError,
|
||||
} from "rxjs";
|
||||
|
||||
import {
|
||||
LoginEmailServiceAbstraction,
|
||||
UserDecryptionOptions,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} 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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
|
||||
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
@@ -30,7 +35,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
enum State {
|
||||
NewUser,
|
||||
@@ -62,6 +67,8 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
protected data?: Data;
|
||||
protected loading = true;
|
||||
|
||||
activeAccountId: UserId;
|
||||
|
||||
// Remember device means for the user to trust the device
|
||||
rememberDeviceForm = this.formBuilder.group({
|
||||
rememberDevice: [true],
|
||||
@@ -79,7 +86,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
protected activatedRoute: ActivatedRoute,
|
||||
protected messagingService: MessagingService,
|
||||
protected tokenService: TokenService,
|
||||
protected loginService: LoginService,
|
||||
protected loginEmailService: LoginEmailServiceAbstraction,
|
||||
protected organizationApiService: OrganizationApiServiceAbstraction,
|
||||
protected cryptoService: CryptoService,
|
||||
protected organizationUserService: OrganizationUserService,
|
||||
@@ -88,12 +95,15 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
protected validationService: ValidationService,
|
||||
protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
protected passwordResetEnrollmentService: PasswordResetEnrollmentServiceAbstraction,
|
||||
protected ssoLoginService: SsoLoginServiceAbstraction,
|
||||
protected accountService: AccountService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.loading = true;
|
||||
this.activeAccountId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
|
||||
this.setupRememberDeviceValueChanges();
|
||||
|
||||
@@ -101,14 +111,15 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
await this.setRememberDeviceDefaultValue();
|
||||
|
||||
try {
|
||||
const accountDecryptionOptions: AccountDecryptionOptions =
|
||||
await this.stateService.getAccountDecryptionOptions();
|
||||
const userDecryptionOptions = await firstValueFrom(
|
||||
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||
);
|
||||
|
||||
// see sso-login.strategy - to determine if a user is new or not it just checks if there is a key on the token response..
|
||||
// can we check if they have a user key or master key in crypto service? Would that be sufficient?
|
||||
if (
|
||||
!accountDecryptionOptions?.trustedDeviceOption?.hasAdminApproval &&
|
||||
!accountDecryptionOptions?.hasMasterPassword
|
||||
!userDecryptionOptions?.trustedDeviceOption?.hasAdminApproval &&
|
||||
!userDecryptionOptions?.hasMasterPassword
|
||||
) {
|
||||
// We are dealing with a new account if:
|
||||
// - User does not have admin approval (i.e. has not enrolled into admin reset)
|
||||
@@ -118,7 +129,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.loadNewUserData();
|
||||
} else {
|
||||
this.loadUntrustedDeviceData(accountDecryptionOptions);
|
||||
this.loadUntrustedDeviceData(userDecryptionOptions);
|
||||
}
|
||||
|
||||
// Note: this is probably not a comprehensive write up of all scenarios:
|
||||
@@ -145,7 +156,9 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private async setRememberDeviceDefaultValue() {
|
||||
const rememberDeviceFromState = await this.deviceTrustCryptoService.getShouldTrustDevice();
|
||||
const rememberDeviceFromState = await this.deviceTrustCryptoService.getShouldTrustDevice(
|
||||
this.activeAccountId,
|
||||
);
|
||||
|
||||
const rememberDevice = rememberDeviceFromState ?? true;
|
||||
|
||||
@@ -156,7 +169,9 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
this.rememberDevice.valueChanges
|
||||
.pipe(
|
||||
switchMap((value) =>
|
||||
defer(() => this.deviceTrustCryptoService.setShouldTrustDevice(value)),
|
||||
defer(() =>
|
||||
this.deviceTrustCryptoService.setShouldTrustDevice(this.activeAccountId, value),
|
||||
),
|
||||
),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
@@ -195,7 +210,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
loadUntrustedDeviceData(accountDecryptionOptions: AccountDecryptionOptions) {
|
||||
loadUntrustedDeviceData(userDecryptionOptions: UserDecryptionOptions) {
|
||||
this.loading = true;
|
||||
|
||||
const email$ = from(this.stateService.getEmail()).pipe(
|
||||
@@ -215,13 +230,12 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
)
|
||||
.subscribe((email) => {
|
||||
const showApproveFromOtherDeviceBtn =
|
||||
accountDecryptionOptions?.trustedDeviceOption?.hasLoginApprovingDevice || false;
|
||||
userDecryptionOptions?.trustedDeviceOption?.hasLoginApprovingDevice || false;
|
||||
|
||||
const showReqAdminApprovalBtn =
|
||||
!!accountDecryptionOptions?.trustedDeviceOption?.hasAdminApproval || false;
|
||||
!!userDecryptionOptions?.trustedDeviceOption?.hasAdminApproval || false;
|
||||
|
||||
const showApproveWithMasterPasswordBtn =
|
||||
accountDecryptionOptions?.hasMasterPassword || false;
|
||||
const showApproveWithMasterPasswordBtn = userDecryptionOptions?.hasMasterPassword || false;
|
||||
|
||||
const userEmail = email;
|
||||
|
||||
@@ -240,23 +254,17 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loginService.setEmail(this.data.userEmail);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/login-with-device"]);
|
||||
this.loginEmailService.setEmail(this.data.userEmail);
|
||||
await this.router.navigate(["/login-with-device"]);
|
||||
}
|
||||
|
||||
async requestAdminApproval() {
|
||||
this.loginService.setEmail(this.data.userEmail);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/admin-approval-requested"]);
|
||||
this.loginEmailService.setEmail(this.data.userEmail);
|
||||
await this.router.navigate(["/admin-approval-requested"]);
|
||||
}
|
||||
|
||||
async approveWithMasterPassword() {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/lock"], { queryParams: { from: "login-initiated" } });
|
||||
await this.router.navigate(["/lock"], { queryParams: { from: "login-initiated" } });
|
||||
}
|
||||
|
||||
async createUser() {
|
||||
@@ -280,7 +288,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
await this.passwordResetEnrollmentService.enroll(this.data.organizationId);
|
||||
|
||||
if (this.rememberDeviceForm.value.rememberDevice) {
|
||||
await this.deviceTrustCryptoService.trustDevice();
|
||||
await this.deviceTrustCryptoService.trustDevice(this.activeAccountId);
|
||||
}
|
||||
} catch (error) {
|
||||
this.validationService.showError(error);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Directive, Input } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { CaptchaIFrame } from "@bitwarden/common/auth/captcha-iframe";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
@@ -19,7 +20,8 @@ export abstract class CaptchaProtectedComponent {
|
||||
) {}
|
||||
|
||||
async setupCaptcha() {
|
||||
const webVaultUrl = this.environmentService.getWebVaultUrl();
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const webVaultUrl = env.getWebVaultUrl();
|
||||
|
||||
this.captcha = new CaptchaIFrame(
|
||||
window,
|
||||
|
||||
@@ -7,17 +7,15 @@
|
||||
#trigger="cdkOverlayOrigin"
|
||||
aria-haspopup="dialog"
|
||||
aria-controls="cdk-overlay-container"
|
||||
[ngSwitch]="selectedEnvironment"
|
||||
>
|
||||
<span *ngSwitchCase="ServerEnvironmentType.US" class="text-primary">{{
|
||||
"usDomain" | i18n
|
||||
}}</span>
|
||||
<span *ngSwitchCase="ServerEnvironmentType.EU" class="text-primary">{{
|
||||
"euDomain" | i18n
|
||||
}}</span>
|
||||
<span *ngSwitchCase="ServerEnvironmentType.SelfHosted" class="text-primary">{{
|
||||
"selfHostedServer" | i18n
|
||||
}}</span>
|
||||
<span class="text-primary">
|
||||
<ng-container *ngIf="selectedRegion$ | async as selectedRegion; else fallback">
|
||||
{{ selectedRegion.domain }}
|
||||
</ng-container>
|
||||
<ng-template #fallback>
|
||||
{{ "selfHostedServer" | i18n }}
|
||||
</ng-template>
|
||||
</span>
|
||||
<i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -41,40 +39,23 @@
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="environment-selector-dialog-item"
|
||||
(click)="toggle(ServerEnvironmentType.US)"
|
||||
[attr.aria-pressed]="selectedEnvironment === ServerEnvironmentType.US ? 'true' : 'false'"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw bwi-sm bwi-check"
|
||||
style="padding-bottom: 1px"
|
||||
aria-hidden="true"
|
||||
[style.visibility]="
|
||||
selectedEnvironment === ServerEnvironmentType.US ? 'visible' : 'hidden'
|
||||
"
|
||||
></i>
|
||||
<span>{{ "usDomain" | i18n }}</span>
|
||||
</button>
|
||||
<br />
|
||||
<button
|
||||
type="button"
|
||||
class="environment-selector-dialog-item"
|
||||
(click)="toggle(ServerEnvironmentType.EU)"
|
||||
[attr.aria-pressed]="selectedEnvironment === ServerEnvironmentType.EU ? 'true' : 'false'"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw bwi-sm bwi-check"
|
||||
style="padding-bottom: 1px"
|
||||
aria-hidden="true"
|
||||
[style.visibility]="
|
||||
selectedEnvironment === ServerEnvironmentType.EU ? 'visible' : 'hidden'
|
||||
"
|
||||
></i>
|
||||
<span>{{ "euDomain" | i18n }}</span>
|
||||
</button>
|
||||
<br />
|
||||
<ng-container *ngFor="let region of availableRegions">
|
||||
<button
|
||||
type="button"
|
||||
class="environment-selector-dialog-item"
|
||||
(click)="toggle(region.key)"
|
||||
[attr.aria-pressed]="selectedEnvironment === region.key ? 'true' : 'false'"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw bwi-sm bwi-check"
|
||||
style="padding-bottom: 1px"
|
||||
aria-hidden="true"
|
||||
[style.visibility]="selectedEnvironment === region.key ? 'visible' : 'hidden'"
|
||||
></i>
|
||||
<span>{{ region.domain }}</span>
|
||||
</button>
|
||||
<br />
|
||||
</ng-container>
|
||||
<button
|
||||
type="button"
|
||||
class="environment-selector-dialog-item"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { animate, state, style, transition, trigger } from "@angular/animations";
|
||||
import { ConnectedPosition } from "@angular/cdk/overlay";
|
||||
import { Component, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { Component, EventEmitter, Output } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
import { Observable, map } from "rxjs";
|
||||
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import {
|
||||
EnvironmentService as EnvironmentServiceAbstraction,
|
||||
EnvironmentService,
|
||||
Region,
|
||||
RegionConfig,
|
||||
} from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
|
||||
@Component({
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
|
||||
export class EnvironmentSelectorComponent {
|
||||
@Output() onOpenSelfHostedSettings = new EventEmitter();
|
||||
isOpen = false;
|
||||
showingModal = false;
|
||||
@@ -48,59 +48,34 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
|
||||
overlayY: "top",
|
||||
},
|
||||
];
|
||||
protected componentDestroyed$: Subject<void> = new Subject();
|
||||
|
||||
protected availableRegions = this.environmentService.availableRegions();
|
||||
protected selectedRegion$: Observable<RegionConfig | undefined> =
|
||||
this.environmentService.environment$.pipe(
|
||||
map((e) => e.getRegion()),
|
||||
map((r) => this.availableRegions.find((ar) => ar.key === r)),
|
||||
);
|
||||
|
||||
constructor(
|
||||
protected environmentService: EnvironmentServiceAbstraction,
|
||||
protected configService: ConfigServiceAbstraction,
|
||||
protected environmentService: EnvironmentService,
|
||||
protected router: Router,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.configService.serverConfig$.pipe(takeUntil(this.componentDestroyed$)).subscribe(() => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.updateEnvironmentInfo();
|
||||
});
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.updateEnvironmentInfo();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.componentDestroyed$.next();
|
||||
this.componentDestroyed$.complete();
|
||||
}
|
||||
|
||||
async toggle(option: Region) {
|
||||
this.isOpen = !this.isOpen;
|
||||
if (option === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.updateEnvironmentInfo();
|
||||
|
||||
if (option === Region.SelfHosted) {
|
||||
this.onOpenSelfHostedSettings.emit();
|
||||
return;
|
||||
}
|
||||
|
||||
await this.environmentService.setRegion(option);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.updateEnvironmentInfo();
|
||||
}
|
||||
|
||||
async updateEnvironmentInfo() {
|
||||
this.selectedEnvironment = this.environmentService.selectedRegion;
|
||||
await this.environmentService.setEnvironment(option);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.isOpen = false;
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.updateEnvironmentInfo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Directive, EventEmitter, Output } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
|
||||
import {
|
||||
EnvironmentService,
|
||||
@@ -27,21 +28,29 @@ export class EnvironmentComponent {
|
||||
protected i18nService: I18nService,
|
||||
private modalService: ModalService,
|
||||
) {
|
||||
const urls = this.environmentService.getUrls();
|
||||
if (this.environmentService.selectedRegion != Region.SelfHosted) {
|
||||
return;
|
||||
}
|
||||
this.environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => {
|
||||
if (env.getRegion() !== Region.SelfHosted) {
|
||||
this.baseUrl = "";
|
||||
this.webVaultUrl = "";
|
||||
this.apiUrl = "";
|
||||
this.identityUrl = "";
|
||||
this.iconsUrl = "";
|
||||
this.notificationsUrl = "";
|
||||
return;
|
||||
}
|
||||
|
||||
this.baseUrl = urls.base || "";
|
||||
this.webVaultUrl = urls.webVault || "";
|
||||
this.apiUrl = urls.api || "";
|
||||
this.identityUrl = urls.identity || "";
|
||||
this.iconsUrl = urls.icons || "";
|
||||
this.notificationsUrl = urls.notifications || "";
|
||||
const urls = env.getUrls();
|
||||
this.baseUrl = urls.base || "";
|
||||
this.webVaultUrl = urls.webVault || "";
|
||||
this.apiUrl = urls.api || "";
|
||||
this.identityUrl = urls.identity || "";
|
||||
this.iconsUrl = urls.icons || "";
|
||||
this.notificationsUrl = urls.notifications || "";
|
||||
});
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const resUrls = await this.environmentService.setUrls({
|
||||
await this.environmentService.setEnvironment(Region.SelfHosted, {
|
||||
base: this.baseUrl,
|
||||
api: this.apiUrl,
|
||||
identity: this.identityUrl,
|
||||
@@ -50,14 +59,6 @@ export class EnvironmentComponent {
|
||||
notifications: this.notificationsUrl,
|
||||
});
|
||||
|
||||
// re-set urls since service can change them, ex: prefixing https://
|
||||
this.baseUrl = resUrls.base;
|
||||
this.apiUrl = resUrls.api;
|
||||
this.identityUrl = resUrls.identity;
|
||||
this.webVaultUrl = resUrls.webVault;
|
||||
this.iconsUrl = resUrls.icons;
|
||||
this.notificationsUrl = resUrls.notifications;
|
||||
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("environmentSaved"));
|
||||
this.saved();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { PasswordHintRequest } from "@bitwarden/common/auth/models/request/password-hint.request";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -22,11 +22,11 @@ export class HintComponent implements OnInit {
|
||||
protected apiService: ApiService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
private logService: LogService,
|
||||
private loginService: LoginService,
|
||||
private loginEmailService: LoginEmailServiceAbstraction,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.email = this.loginService.getEmail() ?? "";
|
||||
this.email = this.loginEmailService.getEmail() ?? "";
|
||||
}
|
||||
|
||||
async submit() {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeou
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
@@ -75,6 +76,7 @@ export class LockComponent implements OnInit, OnDestroy {
|
||||
protected userVerificationService: UserVerificationService,
|
||||
protected pinCryptoService: PinCryptoServiceAbstraction,
|
||||
protected biometricStateService: BiometricStateService,
|
||||
protected accountService: AccountService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -119,7 +121,7 @@ export class LockComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.biometricStateService.setPromptCancelled();
|
||||
await this.biometricStateService.setUserPromptCancelled();
|
||||
const userKey = await this.cryptoService.getUserKeyFromStorage(KeySuffixOptions.Biometric);
|
||||
|
||||
if (userKey) {
|
||||
@@ -269,14 +271,15 @@ export class LockComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Now that we have a decrypted user key in memory, we can check if we
|
||||
// need to establish trust on the current device
|
||||
await this.deviceTrustCryptoService.trustDeviceIfRequired();
|
||||
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||
await this.deviceTrustCryptoService.trustDeviceIfRequired(activeAccount.id);
|
||||
|
||||
await this.doContinue(evaluatePasswordAfterUnlock);
|
||||
}
|
||||
|
||||
private async doContinue(evaluatePasswordAfterUnlock: boolean) {
|
||||
await this.stateService.setEverBeenUnlocked(true);
|
||||
await this.biometricStateService.resetPromptCancelled();
|
||||
await this.biometricStateService.resetUserPromptCancelled();
|
||||
this.messagingService.send("unlocked");
|
||||
|
||||
if (evaluatePasswordAfterUnlock) {
|
||||
@@ -346,7 +349,7 @@ export class LockComponent implements OnInit, OnDestroy {
|
||||
!this.platformUtilsService.supportsSecureStorage());
|
||||
this.email = await this.stateService.getEmail();
|
||||
|
||||
this.webVaultHostname = await this.environmentService.getHost();
|
||||
this.webVaultHostname = (await this.environmentService.getEnvironment()).getHostname();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { Directive, OnDestroy, OnInit } from "@angular/core";
|
||||
import { IsActiveMatchOptions, Router } from "@angular/router";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
import { Subject, firstValueFrom, takeUntil } from "rxjs";
|
||||
|
||||
import {
|
||||
AuthRequestLoginCredentials,
|
||||
AuthRequestServiceAbstraction,
|
||||
LoginStrategyServiceAbstraction,
|
||||
LoginEmailServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
|
||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { AuthRequestType } from "@bitwarden/common/auth/enums/auth-request-type";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable";
|
||||
@@ -68,7 +69,6 @@ export class LoginViaAuthRequestComponent
|
||||
|
||||
private authRequestKeyPair: { publicKey: Uint8Array; privateKey: Uint8Array };
|
||||
|
||||
// TODO: in future, go to child components and remove child constructors and let deps fall through to the super class
|
||||
constructor(
|
||||
protected router: Router,
|
||||
private cryptoService: CryptoService,
|
||||
@@ -84,10 +84,11 @@ export class LoginViaAuthRequestComponent
|
||||
private anonymousHubService: AnonymousHubService,
|
||||
private validationService: ValidationService,
|
||||
private stateService: StateService,
|
||||
private loginService: LoginService,
|
||||
private loginEmailService: LoginEmailServiceAbstraction,
|
||||
private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||
private authRequestService: AuthRequestServiceAbstraction,
|
||||
private loginStrategyService: LoginStrategyServiceAbstraction,
|
||||
private accountService: AccountService,
|
||||
) {
|
||||
super(environmentService, i18nService, platformUtilsService);
|
||||
|
||||
@@ -95,17 +96,19 @@ export class LoginViaAuthRequestComponent
|
||||
// Why would the existence of the email depend on the navigation?
|
||||
const navigation = this.router.getCurrentNavigation();
|
||||
if (navigation) {
|
||||
this.email = this.loginService.getEmail();
|
||||
this.email = this.loginEmailService.getEmail();
|
||||
}
|
||||
|
||||
//gets signalR push notification
|
||||
this.loginStrategyService.authRequestPushNotification$
|
||||
// Gets signalR push notification
|
||||
// Only fires on approval to prevent enumeration
|
||||
this.authRequestService.authRequestPushNotification$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((id) => {
|
||||
// Only fires on approval currently
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.verifyAndHandleApprovedAuthReq(id);
|
||||
this.verifyAndHandleApprovedAuthReq(id).catch((e: Error) => {
|
||||
this.platformUtilsService.showToast("error", this.i18nService.t("error"), e.message);
|
||||
this.logService.error("Failed to use approved auth request: " + e.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -150,7 +153,7 @@ export class LoginViaAuthRequestComponent
|
||||
} else {
|
||||
// Standard auth request
|
||||
// TODO: evaluate if we can remove the setting of this.email in the constructor
|
||||
this.email = this.loginService.getEmail();
|
||||
this.email = this.loginEmailService.getEmail();
|
||||
|
||||
if (!this.email) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("userEmailMissing"));
|
||||
@@ -164,10 +167,10 @@ export class LoginViaAuthRequestComponent
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
async ngOnDestroy() {
|
||||
await this.anonymousHubService.stopHubConnection();
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
this.anonymousHubService.stopHubConnection();
|
||||
}
|
||||
|
||||
private async handleExistingAdminAuthRequest(adminAuthReqStorable: AdminAuthRequestStorable) {
|
||||
@@ -213,7 +216,7 @@ export class LoginViaAuthRequestComponent
|
||||
|
||||
// Request still pending response from admin
|
||||
// So, create hub connection so that any approvals will be received via push notification
|
||||
this.anonymousHubService.createHubConnection(adminAuthReqStorable.id);
|
||||
await this.anonymousHubService.createHubConnection(adminAuthReqStorable.id);
|
||||
}
|
||||
|
||||
private async handleExistingAdminAuthReqDeletedOrDenied() {
|
||||
@@ -273,7 +276,7 @@ export class LoginViaAuthRequestComponent
|
||||
}
|
||||
|
||||
if (reqResponse.id) {
|
||||
this.anonymousHubService.createHubConnection(reqResponse.id);
|
||||
await this.anonymousHubService.createHubConnection(reqResponse.id);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
@@ -387,7 +390,8 @@ export class LoginViaAuthRequestComponent
|
||||
|
||||
// Now that we have a decrypted user key in memory, we can check if we
|
||||
// need to establish trust on the current device
|
||||
await this.deviceTrustCryptoService.trustDeviceIfRequired();
|
||||
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||
await this.deviceTrustCryptoService.trustDeviceIfRequired(activeAccount.id);
|
||||
|
||||
// TODO: don't forget to use auto enrollment service everywhere we trust device
|
||||
|
||||
@@ -471,17 +475,10 @@ export class LoginViaAuthRequestComponent
|
||||
}
|
||||
}
|
||||
|
||||
async setRememberEmailValues() {
|
||||
const rememberEmail = this.loginService.getRememberEmail();
|
||||
const rememberedEmail = this.loginService.getEmail();
|
||||
await this.stateService.setRememberedEmail(rememberEmail ? rememberedEmail : null);
|
||||
this.loginService.clearValues();
|
||||
}
|
||||
|
||||
private async handleSuccessfulLoginNavigation() {
|
||||
if (this.state === State.StandardAuthRequest) {
|
||||
// Only need to set remembered email on standard login with auth req flow
|
||||
await this.setRememberEmailValues();
|
||||
await this.loginEmailService.saveEmailSettings();
|
||||
}
|
||||
|
||||
if (this.onSuccessfulLogin != null) {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { Directive, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { Subject } from "rxjs";
|
||||
import { Subject, firstValueFrom } from "rxjs";
|
||||
import { take, takeUntil } from "rxjs/operators";
|
||||
|
||||
import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common";
|
||||
import {
|
||||
LoginStrategyServiceAbstraction,
|
||||
LoginEmailServiceAbstraction,
|
||||
PasswordLoginCredentials,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
|
||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
@@ -77,17 +80,13 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
|
||||
protected formBuilder: FormBuilder,
|
||||
protected formValidationErrorService: FormValidationErrorsService,
|
||||
protected route: ActivatedRoute,
|
||||
protected loginService: LoginService,
|
||||
protected loginEmailService: LoginEmailServiceAbstraction,
|
||||
protected ssoLoginService: SsoLoginServiceAbstraction,
|
||||
protected webAuthnLoginService: WebAuthnLoginServiceAbstraction,
|
||||
) {
|
||||
super(environmentService, i18nService, platformUtilsService);
|
||||
}
|
||||
|
||||
get selfHostedDomain() {
|
||||
return this.environmentService.hasBaseUrl() ? this.environmentService.getWebVaultUrl() : null;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route?.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
|
||||
if (!params) {
|
||||
@@ -97,25 +96,23 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
|
||||
const queryParamsEmail = params.email;
|
||||
|
||||
if (queryParamsEmail != null && queryParamsEmail.indexOf("@") > -1) {
|
||||
this.formGroup.get("email").setValue(queryParamsEmail);
|
||||
this.loginService.setEmail(queryParamsEmail);
|
||||
this.formGroup.controls.email.setValue(queryParamsEmail);
|
||||
this.paramEmailSet = true;
|
||||
}
|
||||
});
|
||||
let email = this.loginService.getEmail();
|
||||
|
||||
if (email == null || email === "") {
|
||||
email = await this.stateService.getRememberedEmail();
|
||||
}
|
||||
|
||||
if (!this.paramEmailSet) {
|
||||
this.formGroup.get("email")?.setValue(email ?? "");
|
||||
const storedEmail = await firstValueFrom(this.loginEmailService.storedEmail$);
|
||||
this.formGroup.controls.email.setValue(storedEmail ?? "");
|
||||
}
|
||||
let rememberEmail = this.loginService.getRememberEmail();
|
||||
|
||||
let rememberEmail = this.loginEmailService.getRememberEmail();
|
||||
|
||||
if (rememberEmail == null) {
|
||||
rememberEmail = (await this.stateService.getRememberedEmail()) != null;
|
||||
rememberEmail = (await firstValueFrom(this.loginEmailService.storedEmail$)) != null;
|
||||
}
|
||||
this.formGroup.get("rememberEmail")?.setValue(rememberEmail);
|
||||
|
||||
this.formGroup.controls.rememberEmail.setValue(rememberEmail);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -152,8 +149,10 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
|
||||
|
||||
this.formPromise = this.loginStrategyService.logIn(credentials);
|
||||
const response = await this.formPromise;
|
||||
this.setFormValues();
|
||||
await this.loginService.saveEmailSettings();
|
||||
|
||||
this.setLoginEmailValues();
|
||||
await this.loginEmailService.saveEmailSettings();
|
||||
|
||||
if (this.handleCaptchaRequired(response)) {
|
||||
return;
|
||||
} else if (this.handleMigrateEncryptionKey(response)) {
|
||||
@@ -218,7 +217,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
|
||||
return;
|
||||
}
|
||||
|
||||
this.setFormValues();
|
||||
this.setLoginEmailValues();
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/login-with-device"]);
|
||||
@@ -245,7 +244,8 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
|
||||
await this.ssoLoginService.setCodeVerifier(ssoCodeVerifier);
|
||||
|
||||
// Build URI
|
||||
const webUrl = this.environmentService.getWebVaultUrl();
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const webUrl = env.getWebVaultUrl();
|
||||
|
||||
// Launch browser
|
||||
this.platformUtilsService.launchUri(
|
||||
@@ -295,14 +295,14 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
|
||||
}
|
||||
}
|
||||
|
||||
setFormValues() {
|
||||
this.loginService.setEmail(this.formGroup.value.email);
|
||||
this.loginService.setRememberEmail(this.formGroup.value.rememberEmail);
|
||||
setLoginEmailValues() {
|
||||
this.loginEmailService.setEmail(this.formGroup.value.email);
|
||||
this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail);
|
||||
}
|
||||
|
||||
async saveEmailSettings() {
|
||||
this.setFormValues();
|
||||
await this.loginService.saveEmailSettings();
|
||||
this.setLoginEmailValues();
|
||||
await this.loginEmailService.saveEmailSettings();
|
||||
|
||||
// Save off email for SSO
|
||||
await this.ssoLoginService.setSsoEmail(this.formGroup.value.email);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Directive } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { of } from "rxjs";
|
||||
import { firstValueFrom, of } from "rxjs";
|
||||
import { filter, first, switchMap, tap } from "rxjs/operators";
|
||||
|
||||
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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
@@ -26,7 +27,6 @@ import {
|
||||
DEFAULT_KDF_CONFIG,
|
||||
} from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
@@ -64,6 +64,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||
stateService: StateService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
private ssoLoginService: SsoLoginServiceAbstraction,
|
||||
dialogService: DialogService,
|
||||
) {
|
||||
@@ -228,11 +229,11 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||
await this.stateService.setForceSetPasswordReason(ForceSetPasswordReason.None);
|
||||
|
||||
// User now has a password so update account decryption options in state
|
||||
const acctDecryptionOpts: AccountDecryptionOptions =
|
||||
await this.stateService.getAccountDecryptionOptions();
|
||||
|
||||
acctDecryptionOpts.hasMasterPassword = true;
|
||||
await this.stateService.setAccountDecryptionOptions(acctDecryptionOpts);
|
||||
const userDecryptionOpts = await firstValueFrom(
|
||||
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||
);
|
||||
userDecryptionOpts.hasMasterPassword = true;
|
||||
await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts);
|
||||
|
||||
await this.stateService.setKdfType(this.kdf);
|
||||
await this.stateService.setKdfConfig(this.kdfConfig);
|
||||
|
||||
@@ -2,24 +2,27 @@ import { Component } from "@angular/core";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { Observable, of } from "rxjs";
|
||||
import { BehaviorSubject, Observable, of } from "rxjs";
|
||||
|
||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import {
|
||||
FakeKeyConnectorUserDecryptionOption as KeyConnectorUserDecryptionOption,
|
||||
LoginStrategyServiceAbstraction,
|
||||
FakeTrustedDeviceUserDecryptionOption as TrustedDeviceUserDecryptionOption,
|
||||
FakeUserDecryptionOptions as UserDecryptionOptions,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { KeyConnectorUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/key-connector-user-decryption-option";
|
||||
import { TrustedDeviceUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/trusted-device-user-decryption-option";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
|
||||
import { SsoComponent } from "./sso.component";
|
||||
@@ -62,7 +65,8 @@ describe("SsoComponent", () => {
|
||||
let mockEnvironmentService: MockProxy<EnvironmentService>;
|
||||
let mockPasswordGenerationService: MockProxy<PasswordGenerationServiceAbstraction>;
|
||||
let mockLogService: MockProxy<LogService>;
|
||||
let mockConfigService: MockProxy<ConfigServiceAbstraction>;
|
||||
let mockUserDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>;
|
||||
let mockConfigService: MockProxy<ConfigService>;
|
||||
|
||||
// Mock authService.logIn params
|
||||
let code: string;
|
||||
@@ -77,17 +81,19 @@ describe("SsoComponent", () => {
|
||||
let mockOnSuccessfulLoginForceResetNavigate: jest.Mock;
|
||||
let mockOnSuccessfulLoginTdeNavigate: jest.Mock;
|
||||
|
||||
let mockAcctDecryptionOpts: {
|
||||
noMasterPassword: AccountDecryptionOptions;
|
||||
withMasterPassword: AccountDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDevice: AccountDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: AccountDecryptionOptions;
|
||||
withMasterPasswordAndKeyConnector: AccountDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDevice: AccountDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: AccountDecryptionOptions;
|
||||
noMasterPasswordWithKeyConnector: AccountDecryptionOptions;
|
||||
let mockUserDecryptionOpts: {
|
||||
noMasterPassword: UserDecryptionOptions;
|
||||
withMasterPassword: UserDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDevice: UserDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
|
||||
withMasterPasswordAndKeyConnector: UserDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDevice: UserDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
|
||||
noMasterPasswordWithKeyConnector: UserDecryptionOptions;
|
||||
};
|
||||
|
||||
let selectedUserDecryptionOptions: BehaviorSubject<UserDecryptionOptions>;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock Services
|
||||
mockLoginStrategyService = mock<LoginStrategyServiceAbstraction>();
|
||||
@@ -101,15 +107,16 @@ describe("SsoComponent", () => {
|
||||
queryParams: mockQueryParams,
|
||||
} as any as ActivatedRoute;
|
||||
|
||||
mockSsoLoginService = mock<SsoLoginServiceAbstraction>();
|
||||
mockStateService = mock<StateService>();
|
||||
mockPlatformUtilsService = mock<PlatformUtilsService>();
|
||||
mockApiService = mock<ApiService>();
|
||||
mockCryptoFunctionService = mock<CryptoFunctionService>();
|
||||
mockEnvironmentService = mock<EnvironmentService>();
|
||||
mockPasswordGenerationService = mock<PasswordGenerationServiceAbstraction>();
|
||||
mockLogService = mock<LogService>();
|
||||
mockConfigService = mock<ConfigServiceAbstraction>();
|
||||
mockSsoLoginService = mock();
|
||||
mockStateService = mock();
|
||||
mockPlatformUtilsService = mock();
|
||||
mockApiService = mock();
|
||||
mockCryptoFunctionService = mock();
|
||||
mockEnvironmentService = mock();
|
||||
mockPasswordGenerationService = mock();
|
||||
mockLogService = mock();
|
||||
mockUserDecryptionOptionsService = mock();
|
||||
mockConfigService = mock();
|
||||
|
||||
// Mock loginStrategyService.logIn params
|
||||
code = "code";
|
||||
@@ -124,49 +131,52 @@ describe("SsoComponent", () => {
|
||||
mockOnSuccessfulLoginForceResetNavigate = jest.fn();
|
||||
mockOnSuccessfulLoginTdeNavigate = jest.fn();
|
||||
|
||||
mockAcctDecryptionOpts = {
|
||||
noMasterPassword: new AccountDecryptionOptions({
|
||||
mockUserDecryptionOpts = {
|
||||
noMasterPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPassword: new AccountDecryptionOptions({
|
||||
withMasterPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPasswordAndTrustedDevice: new AccountDecryptionOptions({
|
||||
withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new AccountDecryptionOptions({
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPasswordAndKeyConnector: new AccountDecryptionOptions({
|
||||
withMasterPasswordAndKeyConnector: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
||||
}),
|
||||
noMasterPasswordWithTrustedDevice: new AccountDecryptionOptions({
|
||||
noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new AccountDecryptionOptions({
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
noMasterPasswordWithKeyConnector: new AccountDecryptionOptions({
|
||||
noMasterPasswordWithKeyConnector: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
||||
}),
|
||||
};
|
||||
|
||||
selectedUserDecryptionOptions = new BehaviorSubject<UserDecryptionOptions>(null);
|
||||
mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions;
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TestSsoComponent],
|
||||
providers: [
|
||||
@@ -183,8 +193,12 @@ describe("SsoComponent", () => {
|
||||
{ provide: EnvironmentService, useValue: mockEnvironmentService },
|
||||
{ provide: PasswordGenerationServiceAbstraction, useValue: mockPasswordGenerationService },
|
||||
|
||||
{
|
||||
provide: UserDecryptionOptionsServiceAbstraction,
|
||||
useValue: mockUserDecryptionOptionsService,
|
||||
},
|
||||
{ provide: LogService, useValue: mockLogService },
|
||||
{ provide: ConfigServiceAbstraction, useValue: mockConfigService },
|
||||
{ provide: ConfigService, useValue: mockConfigService },
|
||||
],
|
||||
});
|
||||
|
||||
@@ -230,9 +244,7 @@ describe("SsoComponent", () => {
|
||||
authResult.twoFactorProviders = new Map([[TwoFactorProviderType.Authenticator, {}]]);
|
||||
|
||||
// use standard user with MP because this test is not concerned with password reset.
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||
|
||||
mockLoginStrategyService.logIn.mockResolvedValue(authResult);
|
||||
});
|
||||
@@ -341,8 +353,8 @@ describe("SsoComponent", () => {
|
||||
describe("Given Trusted Device Encryption is enabled and user needs to set a master password", () => {
|
||||
let authResult;
|
||||
beforeEach(() => {
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
|
||||
);
|
||||
|
||||
authResult = new AuthResult();
|
||||
@@ -377,8 +389,8 @@ describe("SsoComponent", () => {
|
||||
const reasonString = ForceSetPasswordReason[forceResetPasswordReason];
|
||||
let authResult;
|
||||
beforeEach(() => {
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
);
|
||||
|
||||
authResult = new AuthResult();
|
||||
@@ -394,8 +406,8 @@ describe("SsoComponent", () => {
|
||||
describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is not required", () => {
|
||||
let authResult;
|
||||
beforeEach(() => {
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
);
|
||||
|
||||
authResult = new AuthResult();
|
||||
@@ -440,9 +452,7 @@ describe("SsoComponent", () => {
|
||||
describe("Given user needs to set a master password", () => {
|
||||
beforeEach(() => {
|
||||
// Only need to test the case where the user has no master password to test the primary change mp flow here
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.noMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.noMasterPassword);
|
||||
});
|
||||
|
||||
testChangePasswordOnSuccessfulLogin();
|
||||
@@ -450,9 +460,7 @@ describe("SsoComponent", () => {
|
||||
});
|
||||
|
||||
it("does not navigate to the change password route when the user has key connector even if user has no master password", async () => {
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.noMasterPasswordWithKeyConnector,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.noMasterPasswordWithKeyConnector);
|
||||
|
||||
await _component.logIn(code, codeVerifier, orgIdFromState);
|
||||
expect(mockLoginStrategyService.logIn).toHaveBeenCalledTimes(1);
|
||||
@@ -475,9 +483,7 @@ describe("SsoComponent", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// use standard user with MP because this test is not concerned with password reset.
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||
|
||||
const authResult = new AuthResult();
|
||||
authResult.forcePasswordReset = forceResetPasswordReason;
|
||||
@@ -494,9 +500,7 @@ describe("SsoComponent", () => {
|
||||
const authResult = new AuthResult();
|
||||
authResult.twoFactorProviders = null;
|
||||
// use standard user with MP because this test is not concerned with password reset.
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||
authResult.forcePasswordReset = ForceSetPasswordReason.None;
|
||||
mockLoginStrategyService.logIn.mockResolvedValue(authResult);
|
||||
});
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import { Directive } from "@angular/core";
|
||||
import { ActivatedRoute, NavigationExtras, Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { LoginStrategyServiceAbstraction, SsoLoginCredentials } from "@bitwarden/auth/common";
|
||||
import {
|
||||
LoginStrategyServiceAbstraction,
|
||||
SsoLoginCredentials,
|
||||
TrustedDeviceUserDecryptionOption,
|
||||
UserDecryptionOptions,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { TrustedDeviceUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/trusted-device-user-decryption-option";
|
||||
import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -17,7 +23,6 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
|
||||
@Directive()
|
||||
@@ -59,7 +64,8 @@ export class SsoComponent {
|
||||
protected environmentService: EnvironmentService,
|
||||
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
protected logService: LogService,
|
||||
protected configService: ConfigServiceAbstraction,
|
||||
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
protected configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -151,8 +157,10 @@ export class SsoComponent {
|
||||
// Save state (regardless of new or existing)
|
||||
await this.ssoLoginService.setSsoState(state);
|
||||
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
|
||||
let authorizeUrl =
|
||||
this.environmentService.getIdentityUrl() +
|
||||
env.getIdentityUrl() +
|
||||
"/connect/authorize?" +
|
||||
"client_id=" +
|
||||
this.clientId +
|
||||
@@ -194,9 +202,6 @@ export class SsoComponent {
|
||||
this.formPromise = this.loginStrategyService.logIn(credentials);
|
||||
const authResult = await this.formPromise;
|
||||
|
||||
const acctDecryptionOpts: AccountDecryptionOptions =
|
||||
await this.stateService.getAccountDecryptionOptions();
|
||||
|
||||
if (authResult.requiresTwoFactor) {
|
||||
return await this.handleTwoFactorRequired(orgSsoIdentifier);
|
||||
}
|
||||
@@ -217,15 +222,20 @@ export class SsoComponent {
|
||||
return await this.handleForcePasswordReset(orgSsoIdentifier);
|
||||
}
|
||||
|
||||
// must come after 2fa check since user decryption options aren't available if 2fa is required
|
||||
const userDecryptionOpts = await firstValueFrom(
|
||||
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||
);
|
||||
|
||||
const tdeEnabled = await this.isTrustedDeviceEncEnabled(
|
||||
acctDecryptionOpts.trustedDeviceOption,
|
||||
userDecryptionOpts.trustedDeviceOption,
|
||||
);
|
||||
|
||||
if (tdeEnabled) {
|
||||
return await this.handleTrustedDeviceEncryptionEnabled(
|
||||
authResult,
|
||||
orgSsoIdentifier,
|
||||
acctDecryptionOpts,
|
||||
userDecryptionOpts,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -233,8 +243,8 @@ export class SsoComponent {
|
||||
// have one and they aren't using key connector.
|
||||
// Note: TDE & Key connector are mutually exclusive org config options.
|
||||
const requireSetPassword =
|
||||
!acctDecryptionOpts.hasMasterPassword &&
|
||||
acctDecryptionOpts.keyConnectorOption === undefined;
|
||||
!userDecryptionOpts.hasMasterPassword &&
|
||||
userDecryptionOpts.keyConnectorOption === undefined;
|
||||
|
||||
if (requireSetPassword || authResult.resetMasterPassword) {
|
||||
// Change implies going no password -> password in this case
|
||||
@@ -270,12 +280,12 @@ export class SsoComponent {
|
||||
private async handleTrustedDeviceEncryptionEnabled(
|
||||
authResult: AuthResult,
|
||||
orgIdentifier: string,
|
||||
acctDecryptionOpts: AccountDecryptionOptions,
|
||||
userDecryptionOpts: UserDecryptionOptions,
|
||||
): Promise<void> {
|
||||
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
||||
if (
|
||||
!acctDecryptionOpts.hasMasterPassword &&
|
||||
acctDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
|
||||
!userDecryptionOpts.hasMasterPassword &&
|
||||
userDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
|
||||
) {
|
||||
// Set flag so that auth guard can redirect to set password screen after decryption (trusted or untrusted device)
|
||||
// Note: we cannot directly navigate in this scenario as we are in a pre-decryption state, and
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Directive, EventEmitter, OnInit, Output } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
@@ -31,8 +32,9 @@ export class TwoFactorOptionsComponent implements OnInit {
|
||||
this.onProviderSelected.emit(p.type);
|
||||
}
|
||||
|
||||
recover() {
|
||||
const webVault = this.environmentService.getWebVaultUrl();
|
||||
async recover() {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const webVault = env.getWebVaultUrl();
|
||||
this.platformUtilsService.launchUri(webVault + "/#/recover-2fa");
|
||||
this.onRecoverSelected.emit();
|
||||
}
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ActivatedRoute, Router, convertToParamMap } from "@angular/router";
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { ActivatedRoute, convertToParamMap, Router } from "@angular/router";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import {
|
||||
LoginStrategyServiceAbstraction,
|
||||
LoginEmailServiceAbstraction,
|
||||
FakeKeyConnectorUserDecryptionOption as KeyConnectorUserDecryptionOption,
|
||||
FakeTrustedDeviceUserDecryptionOption as TrustedDeviceUserDecryptionOption,
|
||||
FakeUserDecryptionOptions as UserDecryptionOptions,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { KeyConnectorUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/key-connector-user-decryption-option";
|
||||
import { TrustedDeviceUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/trusted-device-user-decryption-option";
|
||||
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
||||
|
||||
import { TwoFactorComponent } from "./two-factor.component";
|
||||
|
||||
@@ -55,21 +59,24 @@ describe("TwoFactorComponent", () => {
|
||||
let mockLogService: MockProxy<LogService>;
|
||||
let mockTwoFactorService: MockProxy<TwoFactorService>;
|
||||
let mockAppIdService: MockProxy<AppIdService>;
|
||||
let mockLoginService: MockProxy<LoginService>;
|
||||
let mockLoginEmailService: MockProxy<LoginEmailServiceAbstraction>;
|
||||
let mockUserDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>;
|
||||
let mockSsoLoginService: MockProxy<SsoLoginServiceAbstraction>;
|
||||
let mockConfigService: MockProxy<ConfigServiceAbstraction>;
|
||||
let mockConfigService: MockProxy<ConfigService>;
|
||||
|
||||
let mockAcctDecryptionOpts: {
|
||||
noMasterPassword: AccountDecryptionOptions;
|
||||
withMasterPassword: AccountDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDevice: AccountDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: AccountDecryptionOptions;
|
||||
withMasterPasswordAndKeyConnector: AccountDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDevice: AccountDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: AccountDecryptionOptions;
|
||||
noMasterPasswordWithKeyConnector: AccountDecryptionOptions;
|
||||
let mockUserDecryptionOpts: {
|
||||
noMasterPassword: UserDecryptionOptions;
|
||||
withMasterPassword: UserDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDevice: UserDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
|
||||
withMasterPasswordAndKeyConnector: UserDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDevice: UserDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
|
||||
noMasterPasswordWithKeyConnector: UserDecryptionOptions;
|
||||
};
|
||||
|
||||
let selectedUserDecryptionOptions: BehaviorSubject<UserDecryptionOptions>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLoginStrategyService = mock<LoginStrategyServiceAbstraction>();
|
||||
mockRouter = mock<Router>();
|
||||
@@ -82,53 +89,57 @@ describe("TwoFactorComponent", () => {
|
||||
mockLogService = mock<LogService>();
|
||||
mockTwoFactorService = mock<TwoFactorService>();
|
||||
mockAppIdService = mock<AppIdService>();
|
||||
mockLoginService = mock<LoginService>();
|
||||
mockLoginEmailService = mock<LoginEmailServiceAbstraction>();
|
||||
mockUserDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>();
|
||||
mockSsoLoginService = mock<SsoLoginServiceAbstraction>();
|
||||
mockConfigService = mock<ConfigServiceAbstraction>();
|
||||
mockConfigService = mock<ConfigService>();
|
||||
|
||||
mockAcctDecryptionOpts = {
|
||||
noMasterPassword: new AccountDecryptionOptions({
|
||||
mockUserDecryptionOpts = {
|
||||
noMasterPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPassword: new AccountDecryptionOptions({
|
||||
withMasterPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPasswordAndTrustedDevice: new AccountDecryptionOptions({
|
||||
withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new AccountDecryptionOptions({
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPasswordAndKeyConnector: new AccountDecryptionOptions({
|
||||
withMasterPasswordAndKeyConnector: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
||||
}),
|
||||
noMasterPasswordWithTrustedDevice: new AccountDecryptionOptions({
|
||||
noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new AccountDecryptionOptions({
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
noMasterPasswordWithKeyConnector: new AccountDecryptionOptions({
|
||||
noMasterPasswordWithKeyConnector: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
||||
}),
|
||||
};
|
||||
|
||||
selectedUserDecryptionOptions = new BehaviorSubject<UserDecryptionOptions>(null);
|
||||
mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions;
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TestTwoFactorComponent],
|
||||
providers: [
|
||||
@@ -152,9 +163,13 @@ describe("TwoFactorComponent", () => {
|
||||
{ provide: LogService, useValue: mockLogService },
|
||||
{ provide: TwoFactorService, useValue: mockTwoFactorService },
|
||||
{ provide: AppIdService, useValue: mockAppIdService },
|
||||
{ provide: LoginService, useValue: mockLoginService },
|
||||
{ provide: LoginEmailServiceAbstraction, useValue: mockLoginEmailService },
|
||||
{
|
||||
provide: UserDecryptionOptionsServiceAbstraction,
|
||||
useValue: mockUserDecryptionOptionsService,
|
||||
},
|
||||
{ provide: SsoLoginServiceAbstraction, useValue: mockSsoLoginService },
|
||||
{ provide: ConfigServiceAbstraction, useValue: mockConfigService },
|
||||
{ provide: ConfigService, useValue: mockConfigService },
|
||||
],
|
||||
});
|
||||
|
||||
@@ -213,9 +228,7 @@ describe("TwoFactorComponent", () => {
|
||||
component.remember = remember;
|
||||
component.captchaToken = captchaToken;
|
||||
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||
});
|
||||
|
||||
it("calls authService.logInTwoFactor with correct parameters when form is submitted", async () => {
|
||||
@@ -267,11 +280,11 @@ describe("TwoFactorComponent", () => {
|
||||
expect(component.onSuccessfulLogin).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls loginService.clearValues() when login is successful", async () => {
|
||||
it("calls loginEmailService.clearValues() when login is successful", async () => {
|
||||
// Arrange
|
||||
mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult());
|
||||
// spy on loginService.clearValues
|
||||
const clearValuesSpy = jest.spyOn(mockLoginService, "clearValues");
|
||||
// spy on loginEmailService.clearValues
|
||||
const clearValuesSpy = jest.spyOn(mockLoginEmailService, "clearValues");
|
||||
|
||||
// Act
|
||||
await component.doSubmit();
|
||||
@@ -289,17 +302,15 @@ describe("TwoFactorComponent", () => {
|
||||
describe("Given user needs to set a master password", () => {
|
||||
beforeEach(() => {
|
||||
// Only need to test the case where the user has no master password to test the primary change mp flow here
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.noMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.noMasterPassword);
|
||||
});
|
||||
|
||||
testChangePasswordOnSuccessfulLogin();
|
||||
});
|
||||
|
||||
it("does not navigate to the change password route when the user has key connector even if user has no master password", async () => {
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.noMasterPasswordWithKeyConnector,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.noMasterPasswordWithKeyConnector,
|
||||
);
|
||||
|
||||
await component.doSubmit();
|
||||
@@ -321,9 +332,7 @@ describe("TwoFactorComponent", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// use standard user with MP because this test is not concerned with password reset.
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||
|
||||
const authResult = new AuthResult();
|
||||
authResult.forcePasswordReset = forceResetPasswordReason;
|
||||
@@ -385,8 +394,8 @@ describe("TwoFactorComponent", () => {
|
||||
|
||||
describe("Given Trusted Device Encryption is enabled and user needs to set a master password", () => {
|
||||
beforeEach(() => {
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
|
||||
);
|
||||
|
||||
const authResult = new AuthResult();
|
||||
@@ -420,8 +429,8 @@ describe("TwoFactorComponent", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// use standard user with MP because this test is not concerned with password reset.
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
);
|
||||
|
||||
const authResult = new AuthResult();
|
||||
@@ -436,8 +445,8 @@ describe("TwoFactorComponent", () => {
|
||||
describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is not required", () => {
|
||||
let authResult;
|
||||
beforeEach(() => {
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
);
|
||||
|
||||
authResult = new AuthResult();
|
||||
|
||||
@@ -6,28 +6,31 @@ import { first } from "rxjs/operators";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import {
|
||||
LoginStrategyServiceAbstraction,
|
||||
LoginEmailServiceAbstraction,
|
||||
TrustedDeviceUserDecryptionOption,
|
||||
UserDecryptionOptions,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { TrustedDeviceUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/trusted-device-user-decryption-option";
|
||||
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
||||
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
|
||||
import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service";
|
||||
import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
||||
|
||||
import { CaptchaProtectedComponent } from "./captcha-protected.component";
|
||||
|
||||
@@ -85,9 +88,10 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||
protected logService: LogService,
|
||||
protected twoFactorService: TwoFactorService,
|
||||
protected appIdService: AppIdService,
|
||||
protected loginService: LoginService,
|
||||
protected loginEmailService: LoginEmailServiceAbstraction,
|
||||
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
protected ssoLoginService: SsoLoginServiceAbstraction,
|
||||
protected configService: ConfigServiceAbstraction,
|
||||
protected configService: ConfigService,
|
||||
) {
|
||||
super(environmentService, i18nService, platformUtilsService);
|
||||
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
|
||||
@@ -112,7 +116,8 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||
}
|
||||
|
||||
if (this.win != null && this.webAuthnSupported) {
|
||||
const webVaultUrl = this.environmentService.getWebVaultUrl();
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const webVaultUrl = env.getWebVaultUrl();
|
||||
this.webAuthn = new WebAuthnIFrame(
|
||||
this.win,
|
||||
webVaultUrl,
|
||||
@@ -283,29 +288,30 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||
// - TDE login decryption options component
|
||||
// - Browser SSO on extension open
|
||||
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier);
|
||||
this.loginService.clearValues();
|
||||
this.loginEmailService.clearValues();
|
||||
|
||||
// note: this flow affects both TDE & standard users
|
||||
if (this.isForcePasswordResetRequired(authResult)) {
|
||||
return await this.handleForcePasswordReset(this.orgIdentifier);
|
||||
}
|
||||
|
||||
const acctDecryptionOpts: AccountDecryptionOptions =
|
||||
await this.stateService.getAccountDecryptionOptions();
|
||||
const userDecryptionOpts = await firstValueFrom(
|
||||
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||
);
|
||||
|
||||
const tdeEnabled = await this.isTrustedDeviceEncEnabled(acctDecryptionOpts.trustedDeviceOption);
|
||||
const tdeEnabled = await this.isTrustedDeviceEncEnabled(userDecryptionOpts.trustedDeviceOption);
|
||||
|
||||
if (tdeEnabled) {
|
||||
return await this.handleTrustedDeviceEncryptionEnabled(
|
||||
authResult,
|
||||
this.orgIdentifier,
|
||||
acctDecryptionOpts,
|
||||
userDecryptionOpts,
|
||||
);
|
||||
}
|
||||
|
||||
// User must set password if they don't have one and they aren't using either TDE or key connector.
|
||||
const requireSetPassword =
|
||||
!acctDecryptionOpts.hasMasterPassword && acctDecryptionOpts.keyConnectorOption === undefined;
|
||||
!userDecryptionOpts.hasMasterPassword && userDecryptionOpts.keyConnectorOption === undefined;
|
||||
|
||||
if (requireSetPassword || authResult.resetMasterPassword) {
|
||||
// Change implies going no password -> password in this case
|
||||
@@ -326,12 +332,12 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||
private async handleTrustedDeviceEncryptionEnabled(
|
||||
authResult: AuthResult,
|
||||
orgIdentifier: string,
|
||||
acctDecryptionOpts: AccountDecryptionOptions,
|
||||
userDecryptionOpts: UserDecryptionOptions,
|
||||
): Promise<void> {
|
||||
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
||||
if (
|
||||
!acctDecryptionOpts.hasMasterPassword &&
|
||||
acctDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
|
||||
!userDecryptionOpts.hasMasterPassword &&
|
||||
userDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
|
||||
) {
|
||||
// Set flag so that auth guard can redirect to set password screen after decryption (trusted or untrusted device)
|
||||
// Note: we cannot directly navigate to the set password screen in this scenario as we are in a pre-decryption state, and
|
||||
@@ -489,5 +495,5 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||
}
|
||||
|
||||
// implemented in clients
|
||||
launchDuoFrameless() {}
|
||||
async launchDuoFrameless() {}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export function lockGuard(): CanActivateFn {
|
||||
|
||||
// User is authN and in locked state.
|
||||
|
||||
const tdeEnabled = await deviceTrustCryptoService.supportsDeviceTrust();
|
||||
const tdeEnabled = await firstValueFrom(deviceTrustCryptoService.supportsDeviceTrust$);
|
||||
|
||||
// Create special exception which allows users to go from the login-initiated page to the lock page for the approve w/ MP flow
|
||||
// The MP check is necessary to prevent direct manual navigation from other locked state pages for users who don't have a MP
|
||||
|
||||
@@ -46,7 +46,7 @@ export function redirectGuard(overrides: Partial<RedirectRoutes> = {}): CanActiv
|
||||
|
||||
// If locked, TDE is enabled, and the user hasn't decrypted yet, then redirect to the
|
||||
// login decryption options component.
|
||||
const tdeEnabled = await deviceTrustCryptoService.supportsDeviceTrust();
|
||||
const tdeEnabled = await firstValueFrom(deviceTrustCryptoService.supportsDeviceTrust$);
|
||||
const everHadUserKey = await firstValueFrom(cryptoService.everHadUserKey$);
|
||||
if (authStatus === AuthenticationStatus.Locked && tdeEnabled && !everHadUserKey) {
|
||||
return router.createUrlTree([routes.notDecrypted], { queryParams: route.queryParams });
|
||||
|
||||
@@ -26,7 +26,7 @@ export function tdeDecryptionRequiredGuard(): CanActivateFn {
|
||||
const router = inject(Router);
|
||||
|
||||
const authStatus = await authService.getAuthStatus();
|
||||
const tdeEnabled = await deviceTrustCryptoService.supportsDeviceTrust();
|
||||
const tdeEnabled = await firstValueFrom(deviceTrustCryptoService.supportsDeviceTrust$);
|
||||
const everHadUserKey = await firstValueFrom(cryptoService.everHadUserKey$);
|
||||
if (authStatus !== AuthenticationStatus.Locked || !tdeEnabled || everHadUserKey) {
|
||||
return router.createUrlTree(["/"]);
|
||||
|
||||
@@ -62,6 +62,7 @@ export class ShareComponent implements OnInit, OnDestroy {
|
||||
this.organizations$.pipe(takeUntil(this._destroy)).subscribe((orgs) => {
|
||||
if (this.organizationId == null && orgs.length > 0) {
|
||||
this.organizationId = orgs[0].id;
|
||||
this.filterCollections();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -69,8 +70,6 @@ export class ShareComponent implements OnInit, OnDestroy {
|
||||
this.cipher = await cipherDomain.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain),
|
||||
);
|
||||
|
||||
this.filterCollections();
|
||||
}
|
||||
|
||||
filterCollections() {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { By } from "@angular/platform-browser";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { FeatureFlag, FeatureFlagValue } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
import { IfFeatureDirective } from "./if-feature.directive";
|
||||
@@ -39,7 +39,7 @@ class TestComponent {
|
||||
describe("IfFeatureDirective", () => {
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
let content: HTMLElement;
|
||||
let mockConfigService: MockProxy<ConfigServiceAbstraction>;
|
||||
let mockConfigService: MockProxy<ConfigService>;
|
||||
|
||||
const mockConfigFlagValue = (flag: FeatureFlag, flagValue: FeatureFlagValue) => {
|
||||
mockConfigService.getFeatureFlag.mockImplementation((f, defaultValue) =>
|
||||
@@ -51,14 +51,14 @@ describe("IfFeatureDirective", () => {
|
||||
fixture.debugElement.query(By.css(`[data-testid="${testId}"]`))?.nativeElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockConfigService = mock<ConfigServiceAbstraction>();
|
||||
mockConfigService = mock<ConfigService>();
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [IfFeatureDirective, TestComponent],
|
||||
providers: [
|
||||
{ provide: LogService, useValue: mock<LogService>() },
|
||||
{
|
||||
provide: ConfigServiceAbstraction,
|
||||
provide: ConfigService,
|
||||
useValue: mockConfigService,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Directive, Input, OnInit, TemplateRef, ViewContainerRef } from "@angular/core";
|
||||
|
||||
import { FeatureFlag, FeatureFlagValue } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
/**
|
||||
@@ -30,7 +30,7 @@ export class IfFeatureDirective implements OnInit {
|
||||
constructor(
|
||||
private templateRef: TemplateRef<any>,
|
||||
private viewContainer: ViewContainerRef,
|
||||
private configService: ConfigServiceAbstraction,
|
||||
private configService: ConfigService,
|
||||
private logService: LogService,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import { UserTypePipe } from "./pipes/user-type.pipe";
|
||||
import { EllipsisPipe } from "./platform/pipes/ellipsis.pipe";
|
||||
import { FingerprintPipe } from "./platform/pipes/fingerprint.pipe";
|
||||
import { I18nPipe } from "./platform/pipes/i18n.pipe";
|
||||
import { ExportScopeCalloutComponent } from "./tools/export/components/export-scope-callout.component";
|
||||
import { PasswordStrengthComponent } from "./tools/password-strength/password-strength.component";
|
||||
import { IconComponent } from "./vault/components/icon.component";
|
||||
|
||||
@@ -54,7 +53,6 @@ import { IconComponent } from "./vault/components/icon.component";
|
||||
CopyTextDirective,
|
||||
CreditCardNumberPipe,
|
||||
EllipsisPipe,
|
||||
ExportScopeCalloutComponent,
|
||||
FallbackSrcDirective,
|
||||
I18nPipe,
|
||||
IconComponent,
|
||||
@@ -85,7 +83,6 @@ import { IconComponent } from "./vault/components/icon.component";
|
||||
CopyTextDirective,
|
||||
CreditCardNumberPipe,
|
||||
EllipsisPipe,
|
||||
ExportScopeCalloutComponent,
|
||||
FallbackSrcDirective,
|
||||
I18nPipe,
|
||||
IconComponent,
|
||||
|
||||
@@ -9,5 +9,5 @@ export interface FormGroupControls {
|
||||
}
|
||||
|
||||
export abstract class FormValidationErrorsService {
|
||||
getFormValidationErrors: (controls: FormGroupControls) => AllValidationErrors[];
|
||||
abstract getFormValidationErrors(controls: FormGroupControls): AllValidationErrors[];
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -21,11 +21,11 @@ describe("canAccessFeature", () => {
|
||||
const featureRoute = "enabled-feature";
|
||||
const redirectRoute = "redirect";
|
||||
|
||||
let mockConfigService: MockProxy<ConfigServiceAbstraction>;
|
||||
let mockConfigService: MockProxy<ConfigService>;
|
||||
let mockPlatformUtilsService: MockProxy<PlatformUtilsService>;
|
||||
|
||||
const setup = (featureGuard: CanActivateFn, flagValue: any) => {
|
||||
mockConfigService = mock<ConfigServiceAbstraction>();
|
||||
mockConfigService = mock<ConfigService>();
|
||||
mockPlatformUtilsService = mock<PlatformUtilsService>();
|
||||
|
||||
// Mock the correct getter based on the type of flagValue; also mock default values if one is not provided
|
||||
@@ -56,7 +56,7 @@ describe("canAccessFeature", () => {
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
{ provide: ConfigServiceAbstraction, useValue: mockConfigService },
|
||||
{ provide: ConfigService, useValue: mockConfigService },
|
||||
{ provide: PlatformUtilsService, useValue: mockPlatformUtilsService },
|
||||
{ provide: LogService, useValue: mock<LogService>() },
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@ import { inject } from "@angular/core";
|
||||
import { CanActivateFn, Router } from "@angular/router";
|
||||
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -23,7 +23,7 @@ export const canAccessFeature = (
|
||||
redirectUrlOnDisabled?: string,
|
||||
): CanActivateFn => {
|
||||
return async () => {
|
||||
const configService = inject(ConfigServiceAbstraction);
|
||||
const configService = inject(ConfigService);
|
||||
const platformUtilsService = inject(PlatformUtilsService);
|
||||
const router = inject(Router);
|
||||
const i18nService = inject(I18nService);
|
||||
|
||||
22
libs/angular/src/platform/services/logging-error-handler.ts
Normal file
22
libs/angular/src/platform/services/logging-error-handler.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ErrorHandler, Injectable, Injector, inject } from "@angular/core";
|
||||
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
@Injectable()
|
||||
export class LoggingErrorHandler extends ErrorHandler {
|
||||
/**
|
||||
* When injecting services into an `ErrorHandler`, we must use the `Injector` manually to avoid circular dependency errors.
|
||||
*
|
||||
* https://stackoverflow.com/a/57115053
|
||||
*/
|
||||
private injector = inject(Injector);
|
||||
|
||||
override handleError(error: any): void {
|
||||
try {
|
||||
const logService = this.injector.get(LogService, null);
|
||||
logService.error(error);
|
||||
} catch {
|
||||
super.handleError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,12 @@ export abstract class AbstractThemingService {
|
||||
* The effective theme based on the user configured choice and the current system theme if
|
||||
* the configured choice is {@link ThemeType.System}.
|
||||
*/
|
||||
theme$: Observable<ThemeType>;
|
||||
abstract theme$: Observable<ThemeType>;
|
||||
/**
|
||||
* Listens for effective theme changes and applies changes to the provided document.
|
||||
* @param document The document that should have theme classes applied to it.
|
||||
*
|
||||
* @returns A subscription that can be unsubscribed from to cancel the application of theme classes.
|
||||
*/
|
||||
applyThemeChangesTo: (document: Document) => Subscription;
|
||||
abstract applyThemeChangesTo(document: Document): Subscription;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Constructor, Opaque } from "type-fest";
|
||||
import { SafeInjectionToken } from "../../services/injection-tokens";
|
||||
|
||||
/**
|
||||
* The return type of our dependency helper functions.
|
||||
* The return type of the {@link safeProvider} helper function.
|
||||
* Used to distinguish a type safe provider definition from a non-type safe provider definition.
|
||||
*/
|
||||
export type SafeProvider = Opaque<Provider>;
|
||||
@@ -18,12 +18,22 @@ type MapParametersToDeps<T> = {
|
||||
|
||||
type SafeInjectionTokenType<T> = T extends SafeInjectionToken<infer J> ? J : never;
|
||||
|
||||
/**
|
||||
* Gets the instance type from a constructor, abstract constructor, or SafeInjectionToken
|
||||
*/
|
||||
type ProviderInstanceType<T> =
|
||||
T extends SafeInjectionToken<any>
|
||||
? InstanceType<SafeInjectionTokenType<T>>
|
||||
: T extends Constructor<any> | AbstractConstructor<any>
|
||||
? InstanceType<T>
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Represents a dependency provided with the useClass option.
|
||||
*/
|
||||
type SafeClassProvider<
|
||||
A extends AbstractConstructor<any>,
|
||||
I extends Constructor<InstanceType<A>>,
|
||||
A extends AbstractConstructor<any> | SafeInjectionToken<any>,
|
||||
I extends Constructor<ProviderInstanceType<A>>,
|
||||
D extends MapParametersToDeps<ConstructorParameters<I>>,
|
||||
> = {
|
||||
provide: A;
|
||||
@@ -40,42 +50,41 @@ type SafeValueProvider<A extends SafeInjectionToken<any>, V extends SafeInjectio
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a dependency provided with the useFactory option where a SafeInjectionToken is used as the token.
|
||||
* Represents a dependency provided with the useFactory option.
|
||||
*/
|
||||
type SafeFactoryProviderWithToken<
|
||||
A extends SafeInjectionToken<any>,
|
||||
I extends (...args: any) => InstanceType<SafeInjectionTokenType<A>>,
|
||||
D extends MapParametersToDeps<Parameters<I>>,
|
||||
> = {
|
||||
provide: A;
|
||||
useFactory: I;
|
||||
deps: D;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a dependency provided with the useFactory option where an abstract class is used as the token.
|
||||
*/
|
||||
type SafeFactoryProviderWithClass<
|
||||
A extends AbstractConstructor<any>,
|
||||
I extends (...args: any) => InstanceType<A>,
|
||||
type SafeFactoryProvider<
|
||||
A extends AbstractConstructor<any> | SafeInjectionToken<any>,
|
||||
I extends (...args: any) => ProviderInstanceType<A>,
|
||||
D extends MapParametersToDeps<Parameters<I>>,
|
||||
> = {
|
||||
provide: A;
|
||||
useFactory: I;
|
||||
deps: D;
|
||||
multi?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a dependency provided with the useExisting option.
|
||||
*/
|
||||
type SafeExistingProvider<
|
||||
A extends Constructor<any> | AbstractConstructor<any>,
|
||||
I extends Constructor<InstanceType<A>> | AbstractConstructor<InstanceType<A>>,
|
||||
A extends Constructor<any> | AbstractConstructor<any> | SafeInjectionToken<any>,
|
||||
I extends Constructor<ProviderInstanceType<A>> | AbstractConstructor<ProviderInstanceType<A>>,
|
||||
> = {
|
||||
provide: A;
|
||||
useExisting: I;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a dependency where there is no abstract token, the token is the implementation
|
||||
*/
|
||||
type SafeConcreteProvider<
|
||||
I extends Constructor<any>,
|
||||
D extends MapParametersToDeps<ConstructorParameters<I>>,
|
||||
> = {
|
||||
provide: I;
|
||||
deps: D;
|
||||
};
|
||||
|
||||
/**
|
||||
* A factory function that creates a provider for the ngModule providers array.
|
||||
* This guarantees type safety for your provider definition. It does nothing at runtime.
|
||||
@@ -84,31 +93,30 @@ type SafeExistingProvider<
|
||||
*/
|
||||
export const safeProvider = <
|
||||
// types for useClass
|
||||
AClass extends AbstractConstructor<any>,
|
||||
IClass extends Constructor<InstanceType<AClass>>,
|
||||
AClass extends AbstractConstructor<any> | SafeInjectionToken<any>,
|
||||
IClass extends Constructor<ProviderInstanceType<AClass>>,
|
||||
DClass extends MapParametersToDeps<ConstructorParameters<IClass>>,
|
||||
// types for useValue
|
||||
AValue extends SafeInjectionToken<any>,
|
||||
VValue extends SafeInjectionTokenType<AValue>,
|
||||
// types for useFactoryWithToken
|
||||
AFactoryToken extends SafeInjectionToken<any>,
|
||||
IFactoryToken extends (...args: any) => InstanceType<SafeInjectionTokenType<AFactoryToken>>,
|
||||
DFactoryToken extends MapParametersToDeps<Parameters<IFactoryToken>>,
|
||||
// types for useFactoryWithClass
|
||||
AFactoryClass extends AbstractConstructor<any>,
|
||||
IFactoryClass extends (...args: any) => InstanceType<AFactoryClass>,
|
||||
DFactoryClass extends MapParametersToDeps<Parameters<IFactoryClass>>,
|
||||
// types for useFactory
|
||||
AFactory extends AbstractConstructor<any> | SafeInjectionToken<any>,
|
||||
IFactory extends (...args: any) => ProviderInstanceType<AFactory>,
|
||||
DFactory extends MapParametersToDeps<Parameters<IFactory>>,
|
||||
// types for useExisting
|
||||
AExisting extends Constructor<any> | AbstractConstructor<any>,
|
||||
AExisting extends Constructor<any> | AbstractConstructor<any> | SafeInjectionToken<any>,
|
||||
IExisting extends
|
||||
| Constructor<InstanceType<AExisting>>
|
||||
| AbstractConstructor<InstanceType<AExisting>>,
|
||||
| Constructor<ProviderInstanceType<AExisting>>
|
||||
| AbstractConstructor<ProviderInstanceType<AExisting>>,
|
||||
// types for no token
|
||||
IConcrete extends Constructor<any>,
|
||||
DConcrete extends MapParametersToDeps<ConstructorParameters<IConcrete>>,
|
||||
>(
|
||||
provider:
|
||||
| SafeClassProvider<AClass, IClass, DClass>
|
||||
| SafeValueProvider<AValue, VValue>
|
||||
| SafeFactoryProviderWithToken<AFactoryToken, IFactoryToken, DFactoryToken>
|
||||
| SafeFactoryProviderWithClass<AFactoryClass, IFactoryClass, DFactoryClass>
|
||||
| SafeFactoryProvider<AFactory, IFactory, DFactory>
|
||||
| SafeExistingProvider<AExisting, IExisting>
|
||||
| SafeConcreteProvider<IConcrete, DConcrete>
|
||||
| Constructor<unknown>,
|
||||
): SafeProvider => provider as SafeProvider;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { LOCALE_ID, NgModule } from "@angular/core";
|
||||
import { UnwrapOpaque } from "type-fest";
|
||||
import { ErrorHandler, LOCALE_ID, NgModule } from "@angular/core";
|
||||
|
||||
import {
|
||||
AuthRequestServiceAbstraction,
|
||||
@@ -8,6 +7,11 @@ import {
|
||||
PinCryptoService,
|
||||
LoginStrategyServiceAbstraction,
|
||||
LoginStrategyService,
|
||||
LoginEmailServiceAbstraction,
|
||||
LoginEmailService,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
UserDecryptionOptionsService,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
||||
@@ -56,7 +60,6 @@ import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abst
|
||||
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
|
||||
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
@@ -75,7 +78,6 @@ import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device
|
||||
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";
|
||||
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
|
||||
import { LoginService } from "@bitwarden/common/auth/services/login.service";
|
||||
import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation";
|
||||
import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/services/token.service";
|
||||
@@ -108,11 +110,11 @@ import { PaymentMethodWarningsService } from "@bitwarden/common/billing/services
|
||||
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/platform/abstractions/file-upload/file-upload.service";
|
||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
||||
@@ -132,12 +134,12 @@ import { Account } from "@bitwarden/common/platform/models/domain/account";
|
||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
||||
import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/services/config/config.service";
|
||||
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
|
||||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
|
||||
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
|
||||
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
|
||||
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
|
||||
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
|
||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||
@@ -236,6 +238,7 @@ import { UnauthGuard } from "../auth/guards/unauth.guard";
|
||||
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service";
|
||||
import { BroadcasterService } from "../platform/services/broadcaster.service";
|
||||
import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service";
|
||||
import { LoggingErrorHandler } from "../platform/services/logging-error-handler";
|
||||
import { AngularThemingService } from "../platform/services/theming/angular-theming.service";
|
||||
import { AbstractThemingService } from "../platform/services/theming/theming.service.abstraction";
|
||||
import { safeProvider, SafeProvider } from "../platform/utils/safe-provider";
|
||||
@@ -243,8 +246,8 @@ import { safeProvider, SafeProvider } from "../platform/utils/safe-provider";
|
||||
import {
|
||||
LOCALES_DIRECTORY,
|
||||
LOCKED_CALLBACK,
|
||||
LOG_MAC_FAILURES,
|
||||
LOGOUT_CALLBACK,
|
||||
LOG_MAC_FAILURES,
|
||||
MEMORY_STORAGE,
|
||||
OBSERVABLE_DISK_STORAGE,
|
||||
OBSERVABLE_MEMORY_STORAGE,
|
||||
@@ -264,7 +267,7 @@ import { ModalService } from "./modal.service";
|
||||
* Add your provider definition here using the safeProvider function as a wrapper. This will give you type safety.
|
||||
* If you need help please ask for it, do NOT change the type of this array.
|
||||
*/
|
||||
const typesafeProviders: Array<SafeProvider> = [
|
||||
const safeProviders: SafeProvider[] = [
|
||||
safeProvider(AuthGuard),
|
||||
safeProvider(UnauthGuard),
|
||||
safeProvider(ModalService),
|
||||
@@ -342,10 +345,12 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
provide: AuthServiceAbstraction,
|
||||
useClass: AuthService,
|
||||
deps: [
|
||||
AccountServiceAbstraction,
|
||||
MessagingServiceAbstraction,
|
||||
CryptoServiceAbstraction,
|
||||
ApiServiceAbstraction,
|
||||
StateServiceAbstraction,
|
||||
TokenServiceAbstraction,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
@@ -360,7 +365,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
MessagingServiceAbstraction,
|
||||
LogService,
|
||||
KeyConnectorServiceAbstraction,
|
||||
EnvironmentServiceAbstraction,
|
||||
EnvironmentService,
|
||||
StateServiceAbstraction,
|
||||
TwoFactorServiceAbstraction,
|
||||
I18nServiceAbstraction,
|
||||
@@ -369,6 +374,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
PolicyServiceAbstraction,
|
||||
DeviceTrustCryptoServiceAbstraction,
|
||||
AuthRequestServiceAbstraction,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
GlobalStateProvider,
|
||||
BillingAccountProfileStateService,
|
||||
],
|
||||
@@ -395,7 +401,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||
encryptService: EncryptService,
|
||||
fileUploadService: CipherFileUploadServiceAbstraction,
|
||||
configService: ConfigServiceAbstraction,
|
||||
configService: ConfigService,
|
||||
stateProvider: StateProvider,
|
||||
) =>
|
||||
new CipherService(
|
||||
@@ -421,7 +427,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
AutofillSettingsServiceAbstraction,
|
||||
EncryptService,
|
||||
CipherFileUploadServiceAbstraction,
|
||||
ConfigServiceAbstraction,
|
||||
configService,
|
||||
StateProvider,
|
||||
],
|
||||
}),
|
||||
@@ -475,10 +481,19 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
deps: [CryptoServiceAbstraction, I18nServiceAbstraction, StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: EnvironmentServiceAbstraction,
|
||||
useClass: EnvironmentService,
|
||||
provide: EnvironmentService,
|
||||
useClass: DefaultEnvironmentService,
|
||||
deps: [StateProvider, AccountServiceAbstraction],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
useClass: UserDecryptionOptionsService,
|
||||
deps: [StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: UserDecryptionOptionsServiceAbstraction,
|
||||
useExisting: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
}),
|
||||
safeProvider({
|
||||
provide: TotpServiceAbstraction,
|
||||
useClass: TotpService,
|
||||
@@ -491,7 +506,10 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
SingleUserStateProvider,
|
||||
GlobalStateProvider,
|
||||
SUPPORTS_SECURE_STORAGE,
|
||||
AbstractStorageService,
|
||||
SECURE_STORAGE,
|
||||
KeyGenerationServiceAbstraction,
|
||||
EncryptService,
|
||||
LogService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
@@ -534,7 +552,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
deps: [
|
||||
TokenServiceAbstraction,
|
||||
PlatformUtilsServiceAbstraction,
|
||||
EnvironmentServiceAbstraction,
|
||||
EnvironmentService,
|
||||
AppIdServiceAbstraction,
|
||||
StateServiceAbstraction,
|
||||
LOGOUT_CALLBACK,
|
||||
@@ -579,6 +597,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
FolderApiServiceAbstraction,
|
||||
InternalOrganizationServiceAbstraction,
|
||||
SendApiServiceAbstraction,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
AvatarServiceAbstraction,
|
||||
LOGOUT_CALLBACK,
|
||||
BillingAccountProfileStateService,
|
||||
@@ -589,6 +608,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
provide: VaultTimeoutSettingsServiceAbstraction,
|
||||
useClass: VaultTimeoutSettingsService,
|
||||
deps: [
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
CryptoServiceAbstraction,
|
||||
TokenServiceAbstraction,
|
||||
PolicyServiceAbstraction,
|
||||
@@ -634,7 +654,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
LogService,
|
||||
STATE_FACTORY,
|
||||
AccountServiceAbstraction,
|
||||
EnvironmentServiceAbstraction,
|
||||
EnvironmentService,
|
||||
TokenServiceAbstraction,
|
||||
MigrationRunner,
|
||||
STATE_SERVICE_USE_CACHE,
|
||||
@@ -698,7 +718,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
SyncServiceAbstraction,
|
||||
AppIdServiceAbstraction,
|
||||
ApiServiceAbstraction,
|
||||
EnvironmentServiceAbstraction,
|
||||
EnvironmentService,
|
||||
LOGOUT_CALLBACK,
|
||||
StateServiceAbstraction,
|
||||
AuthServiceAbstraction,
|
||||
@@ -749,7 +769,6 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
provide: KeyConnectorServiceAbstraction,
|
||||
useClass: KeyConnectorService,
|
||||
deps: [
|
||||
StateServiceAbstraction,
|
||||
CryptoServiceAbstraction,
|
||||
ApiServiceAbstraction,
|
||||
TokenServiceAbstraction,
|
||||
@@ -757,6 +776,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
OrganizationServiceAbstraction,
|
||||
KeyGenerationServiceAbstraction,
|
||||
LOGOUT_CALLBACK,
|
||||
StateProvider,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
@@ -767,6 +787,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
CryptoServiceAbstraction,
|
||||
I18nServiceAbstraction,
|
||||
UserVerificationApiServiceAbstraction,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
PinCryptoServiceAbstraction,
|
||||
LogService,
|
||||
VaultTimeoutSettingsServiceAbstraction,
|
||||
@@ -833,29 +854,23 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ConfigService,
|
||||
useClass: ConfigService,
|
||||
deps: [
|
||||
StateServiceAbstraction,
|
||||
ConfigApiServiceAbstraction,
|
||||
AuthServiceAbstraction,
|
||||
EnvironmentServiceAbstraction,
|
||||
LogService,
|
||||
],
|
||||
provide: DefaultConfigService,
|
||||
useClass: DefaultConfigService,
|
||||
deps: [ConfigApiServiceAbstraction, EnvironmentService, LogService, StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ConfigServiceAbstraction,
|
||||
useExisting: ConfigService,
|
||||
provide: ConfigService,
|
||||
useExisting: DefaultConfigService,
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ConfigApiServiceAbstraction,
|
||||
useClass: ConfigApiService,
|
||||
deps: [ApiServiceAbstraction, AuthServiceAbstraction],
|
||||
deps: [ApiServiceAbstraction, TokenServiceAbstraction],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: AnonymousHubServiceAbstraction,
|
||||
useClass: AnonymousHubService,
|
||||
deps: [EnvironmentServiceAbstraction, LoginStrategyServiceAbstraction, LogService],
|
||||
deps: [EnvironmentService, AuthRequestServiceAbstraction],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ValidationServiceAbstraction,
|
||||
@@ -863,9 +878,9 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: LoginServiceAbstraction,
|
||||
useClass: LoginService,
|
||||
deps: [StateServiceAbstraction],
|
||||
provide: LoginEmailServiceAbstraction,
|
||||
useClass: LoginEmailService,
|
||||
deps: [StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: OrgDomainInternalServiceAbstraction,
|
||||
@@ -899,11 +914,13 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
CryptoFunctionServiceAbstraction,
|
||||
CryptoServiceAbstraction,
|
||||
EncryptService,
|
||||
StateServiceAbstraction,
|
||||
AppIdServiceAbstraction,
|
||||
DevicesApiServiceAbstraction,
|
||||
I18nServiceAbstraction,
|
||||
PlatformUtilsServiceAbstraction,
|
||||
StateProvider,
|
||||
SECURE_STORAGE,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
@@ -934,7 +951,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
safeProvider({
|
||||
provide: WebAuthnLoginApiServiceAbstraction,
|
||||
useClass: WebAuthnLoginApiService,
|
||||
deps: [ApiServiceAbstraction, EnvironmentServiceAbstraction],
|
||||
deps: [ApiServiceAbstraction, EnvironmentService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: WebAuthnLoginServiceAbstraction,
|
||||
@@ -1050,13 +1067,18 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
safeProvider({
|
||||
provide: BillingAccountProfileStateService,
|
||||
useClass: DefaultBillingAccountProfileStateService,
|
||||
deps: [ActiveUserStateProvider],
|
||||
deps: [StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: OrganizationManagementPreferencesService,
|
||||
useClass: DefaultOrganizationManagementPreferencesService,
|
||||
deps: [StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ErrorHandler,
|
||||
useClass: LoggingErrorHandler,
|
||||
deps: [],
|
||||
}),
|
||||
];
|
||||
|
||||
function encryptServiceFactory(
|
||||
@@ -1072,6 +1094,6 @@ function encryptServiceFactory(
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
// Do not register your dependency here! Add it to the typesafeProviders array using the helper function
|
||||
providers: typesafeProviders as UnwrapOpaque<SafeProvider>[],
|
||||
providers: safeProviders,
|
||||
})
|
||||
export class JslibServicesModule {}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<ng-container *ngIf="show">
|
||||
<app-callout type="info" title="{{ scopeConfig.title | i18n }}">
|
||||
{{ scopeConfig.description | i18n: scopeConfig.scopeIdentifier }}
|
||||
</app-callout>
|
||||
</ng-container>
|
||||
@@ -1,59 +0,0 @@
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-export-scope-callout",
|
||||
templateUrl: "export-scope-callout.component.html",
|
||||
})
|
||||
export class ExportScopeCalloutComponent implements OnInit {
|
||||
show = false;
|
||||
scopeConfig: {
|
||||
title: string;
|
||||
description: string;
|
||||
scopeIdentifier: string;
|
||||
};
|
||||
|
||||
private _organizationId: string;
|
||||
|
||||
get organizationId(): string {
|
||||
return this._organizationId;
|
||||
}
|
||||
|
||||
@Input() set organizationId(value: string) {
|
||||
this._organizationId = value;
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.getScopeMessage(this._organizationId);
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected organizationService: OrganizationService,
|
||||
protected stateService: StateService,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
if (!(await this.organizationService.hasOrganizations())) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.getScopeMessage(this.organizationId);
|
||||
this.show = true;
|
||||
}
|
||||
|
||||
private async getScopeMessage(organizationId: string) {
|
||||
this.scopeConfig =
|
||||
organizationId != null
|
||||
? {
|
||||
title: "exportingOrganizationVaultTitle",
|
||||
description: "exportingOrganizationVaultDesc",
|
||||
scopeIdentifier: (await this.organizationService.get(organizationId)).name,
|
||||
}
|
||||
: {
|
||||
title: "exportingPersonalVaultTitle",
|
||||
description: "exportingIndividualVaultDescription",
|
||||
scopeIdentifier: await this.stateService.getEmail(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
import { Directive, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from "@angular/core";
|
||||
import { UntypedFormBuilder, Validators } from "@angular/forms";
|
||||
import { map, merge, Observable, startWith, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncryptedExportType } from "@bitwarden/common/tools/enums/encrypted-export-type.enum";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
|
||||
|
||||
import { PasswordStrengthComponent } from "../../password-strength/password-strength.component";
|
||||
|
||||
@Directive()
|
||||
export class ExportComponent implements OnInit, OnDestroy {
|
||||
@Output() onSaved = new EventEmitter();
|
||||
@ViewChild(PasswordStrengthComponent) passwordStrengthComponent: PasswordStrengthComponent;
|
||||
|
||||
encryptedExportType = EncryptedExportType;
|
||||
protected showFilePassword: boolean;
|
||||
|
||||
filePasswordValue: string = null;
|
||||
private _disabledByPolicy = false;
|
||||
|
||||
protected organizationId: string = null;
|
||||
organizations$: Observable<Organization[]>;
|
||||
|
||||
protected get disabledByPolicy(): boolean {
|
||||
return this._disabledByPolicy;
|
||||
}
|
||||
|
||||
exportForm = this.formBuilder.group({
|
||||
vaultSelector: [
|
||||
"myVault",
|
||||
{
|
||||
nonNullable: true,
|
||||
validators: [Validators.required],
|
||||
},
|
||||
],
|
||||
format: ["json", Validators.required],
|
||||
secret: [""],
|
||||
filePassword: ["", Validators.required],
|
||||
confirmFilePassword: ["", Validators.required],
|
||||
fileEncryptionType: [EncryptedExportType.AccountEncrypted],
|
||||
});
|
||||
|
||||
formatOptions = [
|
||||
{ name: ".json", value: "json" },
|
||||
{ name: ".csv", value: "csv" },
|
||||
{ name: ".json (Encrypted)", value: "encrypted_json" },
|
||||
];
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected exportService: VaultExportServiceAbstraction,
|
||||
protected eventCollectionService: EventCollectionService,
|
||||
private policyService: PolicyService,
|
||||
private logService: LogService,
|
||||
private userVerificationService: UserVerificationService,
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
protected fileDownloadService: FileDownloadService,
|
||||
protected dialogService: DialogService,
|
||||
protected organizationService: OrganizationService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((policyAppliesToActiveUser) => {
|
||||
this._disabledByPolicy = policyAppliesToActiveUser;
|
||||
if (this.disabledByPolicy) {
|
||||
this.exportForm.disable();
|
||||
}
|
||||
});
|
||||
|
||||
merge(
|
||||
this.exportForm.get("format").valueChanges,
|
||||
this.exportForm.get("fileEncryptionType").valueChanges,
|
||||
)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.pipe(startWith(0))
|
||||
.subscribe(() => this.adjustValidators());
|
||||
|
||||
if (this.organizationId) {
|
||||
this.organizations$ = this.organizationService.memberOrganizations$.pipe(
|
||||
map((orgs) => orgs.filter((org) => org.id == this.organizationId)),
|
||||
);
|
||||
this.exportForm.controls.vaultSelector.patchValue(this.organizationId);
|
||||
this.exportForm.controls.vaultSelector.disable();
|
||||
return;
|
||||
}
|
||||
|
||||
this.organizations$ = this.organizationService.memberOrganizations$.pipe(
|
||||
map((orgs) =>
|
||||
orgs
|
||||
.filter((org) => org.flexibleCollections)
|
||||
.sort(Utils.getSortFunction(this.i18nService, "name")),
|
||||
),
|
||||
);
|
||||
|
||||
this.exportForm.controls.vaultSelector.valueChanges
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((value) => {
|
||||
this.organizationId = value != "myVault" ? value : undefined;
|
||||
});
|
||||
|
||||
this.exportForm.controls.vaultSelector.setValue("myVault");
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
}
|
||||
|
||||
get encryptedFormat() {
|
||||
return this.format === "encrypted_json";
|
||||
}
|
||||
|
||||
get isFileEncryptedExport() {
|
||||
return (
|
||||
this.format === "encrypted_json" &&
|
||||
this.fileEncryptionType === EncryptedExportType.FileEncrypted
|
||||
);
|
||||
}
|
||||
|
||||
get isAccountEncryptedExport() {
|
||||
return (
|
||||
this.format === "encrypted_json" &&
|
||||
this.fileEncryptionType === EncryptedExportType.AccountEncrypted
|
||||
);
|
||||
}
|
||||
|
||||
protected async doExport() {
|
||||
try {
|
||||
const data = await this.getExportData();
|
||||
this.downloadFile(data);
|
||||
this.saved();
|
||||
await this.collectEvent();
|
||||
this.exportForm.get("secret").setValue("");
|
||||
this.exportForm.clearValidators();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.disabledByPolicy) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
this.i18nService.t("personalVaultExportPolicyInEffect"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const acceptedWarning = await this.warningDialog();
|
||||
if (!acceptedWarning) {
|
||||
return;
|
||||
}
|
||||
const secret = this.exportForm.get("secret").value;
|
||||
|
||||
try {
|
||||
await this.userVerificationService.verifyUser(secret);
|
||||
} catch (e) {
|
||||
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.doExport();
|
||||
}
|
||||
|
||||
async warningDialog() {
|
||||
if (this.encryptedFormat) {
|
||||
return await this.dialogService.openSimpleDialog({
|
||||
title: { key: "confirmVaultExport" },
|
||||
content:
|
||||
this.i18nService.t("encExportKeyWarningDesc") +
|
||||
" " +
|
||||
this.i18nService.t("encExportAccountWarningDesc"),
|
||||
acceptButtonText: { key: "exportVault" },
|
||||
type: "warning",
|
||||
});
|
||||
} else {
|
||||
return await this.dialogService.openSimpleDialog({
|
||||
title: { key: "confirmVaultExport" },
|
||||
content: { key: "exportWarningDesc" },
|
||||
acceptButtonText: { key: "exportVault" },
|
||||
type: "warning",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected saved() {
|
||||
this.onSaved.emit();
|
||||
}
|
||||
|
||||
protected async getExportData(): Promise<string> {
|
||||
return Utils.isNullOrWhitespace(this.organizationId)
|
||||
? this.exportService.getExport(this.format, this.filePassword)
|
||||
: this.exportService.getOrganizationExport(
|
||||
this.organizationId,
|
||||
this.format,
|
||||
this.filePassword,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
protected getFileName(prefix?: string) {
|
||||
let extension = this.format;
|
||||
if (this.format === "encrypted_json") {
|
||||
if (prefix == null) {
|
||||
prefix = "encrypted";
|
||||
} else {
|
||||
prefix = "encrypted_" + prefix;
|
||||
}
|
||||
extension = "json";
|
||||
}
|
||||
return this.exportService.getFileName(prefix, extension);
|
||||
}
|
||||
|
||||
protected async collectEvent(): Promise<void> {
|
||||
await this.eventCollectionService.collect(EventType.User_ClientExportedVault);
|
||||
}
|
||||
|
||||
get format() {
|
||||
return this.exportForm.get("format").value;
|
||||
}
|
||||
|
||||
get filePassword() {
|
||||
return this.exportForm.get("filePassword").value;
|
||||
}
|
||||
|
||||
get confirmFilePassword() {
|
||||
return this.exportForm.get("confirmFilePassword").value;
|
||||
}
|
||||
|
||||
get fileEncryptionType() {
|
||||
return this.exportForm.get("fileEncryptionType").value;
|
||||
}
|
||||
|
||||
adjustValidators() {
|
||||
this.exportForm.get("confirmFilePassword").reset();
|
||||
this.exportForm.get("filePassword").reset();
|
||||
|
||||
if (this.encryptedFormat && this.fileEncryptionType == EncryptedExportType.FileEncrypted) {
|
||||
this.exportForm.controls.filePassword.enable();
|
||||
this.exportForm.controls.confirmFilePassword.enable();
|
||||
} else {
|
||||
this.exportForm.controls.filePassword.disable();
|
||||
this.exportForm.controls.confirmFilePassword.disable();
|
||||
}
|
||||
}
|
||||
|
||||
private downloadFile(csv: string): void {
|
||||
const fileName = this.getFileName();
|
||||
this.fileDownloadService.download({
|
||||
fileName: fileName,
|
||||
blobData: csv,
|
||||
blobOptions: { type: "text/plain" },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { BehaviorSubject, Subject, concatMap, firstValueFrom, map, takeUntil } from "rxjs";
|
||||
import { Subject, firstValueFrom, takeUntil, map, BehaviorSubject, concatMap } from "rxjs";
|
||||
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
@@ -123,7 +123,6 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
{ name: i18nService.t("sendTypeFile"), value: SendType.File, premium: true },
|
||||
{ name: i18nService.t("sendTypeText"), value: SendType.Text, premium: false },
|
||||
];
|
||||
this.sendLinkBaseUrl = this.environmentService.getSendUrl();
|
||||
}
|
||||
|
||||
get link(): string {
|
||||
@@ -190,6 +189,9 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
this.sendLinkBaseUrl = env.getSendUrl();
|
||||
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((hasPremiumFromAnySource) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
import { Subject, firstValueFrom, takeUntil } from "rxjs";
|
||||
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
@@ -198,9 +198,9 @@ export class SendComponent implements OnInit, OnDestroy {
|
||||
return true;
|
||||
}
|
||||
|
||||
copy(s: SendView) {
|
||||
const sendLinkBaseUrl = this.environmentService.getSendUrl();
|
||||
const link = sendLinkBaseUrl + s.accessId + "/" + s.urlB64Key;
|
||||
async copy(s: SendView) {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const link = env.getSendUrl() + s.accessId + "/" + s.urlB64Key;
|
||||
this.platformUtilsService.copyToClipboard(link);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
@@ -119,7 +119,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
protected dialogService: DialogService,
|
||||
protected win: Window,
|
||||
protected datePipe: DatePipe,
|
||||
protected configService: ConfigServiceAbstraction,
|
||||
protected configService: ConfigService,
|
||||
) {
|
||||
this.typeOptions = [
|
||||
{ name: i18nService.t("typeLogin"), value: CipherType.Login },
|
||||
@@ -402,6 +402,14 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
removePasskey() {
|
||||
if (this.cipher.type !== CipherType.Login || this.cipher.login.fido2Credentials == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cipher.login.fido2Credentials = null;
|
||||
}
|
||||
|
||||
onCardNumberChange(): void {
|
||||
this.cipher.card.brand = CardView.getCardBrandByPatterns(this.cipher.card.number);
|
||||
}
|
||||
@@ -650,11 +658,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
|
||||
protected saveCipher(cipher: Cipher) {
|
||||
const isNotClone = this.editMode && !this.cloneMode;
|
||||
let orgAdmin = this.organization?.isAdmin;
|
||||
let orgAdmin = this.organization?.canEditAllCiphers(this.flexibleCollectionsV1Enabled);
|
||||
|
||||
if (this.flexibleCollectionsV1Enabled) {
|
||||
// Flexible Collections V1 restricts admins, check the organization setting via canEditAllCiphers
|
||||
orgAdmin = this.organization?.canEditAllCiphers(true);
|
||||
// if a cipher is unassigned we want to check if they are an admin or have permission to edit any collection
|
||||
if (!cipher.collectionIds) {
|
||||
orgAdmin = this.organization?.canEditAnyCollection;
|
||||
}
|
||||
|
||||
return this.cipher.id == null
|
||||
|
||||
@@ -39,11 +39,12 @@ export class IconComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
const iconsUrl = this.environmentService.getIconsUrl();
|
||||
|
||||
this.data$ = combineLatest([
|
||||
this.environmentService.environment$.pipe(map((e) => e.getIconsUrl())),
|
||||
this.domainSettingsService.showFavicons$.pipe(distinctUntilChanged()),
|
||||
this.cipher$.pipe(filter((c) => c !== undefined)),
|
||||
]).pipe(map(([showFavicon, cipher]) => buildCipherIcon(iconsUrl, cipher, showFavicon)));
|
||||
]).pipe(
|
||||
map(([iconsUrl, showFavicon, cipher]) => buildCipherIcon(iconsUrl, cipher, showFavicon)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Directive } from "@angular/core";
|
||||
import { Observable, Subject } from "rxjs";
|
||||
import { OnInit, Directive } from "@angular/core";
|
||||
import { firstValueFrom, Observable } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
@@ -11,12 +11,11 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
@Directive()
|
||||
export class PremiumComponent {
|
||||
export class PremiumComponent implements OnInit {
|
||||
isPremium$: Observable<boolean>;
|
||||
price = 10;
|
||||
refreshPromise: Promise<any>;
|
||||
cloudWebVaultUrl: string;
|
||||
private directiveIsDestroyed$ = new Subject<boolean>();
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
@@ -25,13 +24,16 @@ export class PremiumComponent {
|
||||
private logService: LogService,
|
||||
protected stateService: StateService,
|
||||
protected dialogService: DialogService,
|
||||
environmentService: EnvironmentService,
|
||||
private environmentService: EnvironmentService,
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {
|
||||
this.cloudWebVaultUrl = environmentService.getCloudWebVaultUrl();
|
||||
this.isPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.cloudWebVaultUrl = await firstValueFrom(this.environmentService.cloudWebVaultUrl$);
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
try {
|
||||
this.refreshPromise = this.apiService.refreshIdentityToken();
|
||||
|
||||
Reference in New Issue
Block a user