1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-11 22:13:32 +00:00

Merge remote-tracking branch 'origin/main' into ps/pm-15333/portable-desktop

This commit is contained in:
Justin Baur
2025-01-21 16:44:39 -05:00
1301 changed files with 52111 additions and 36450 deletions

View File

@@ -1,6 +1,6 @@
const { pathsToModuleNameMapper } = require("ts-jest");
const { compilerOptions } = require("../shared/tsconfig.libs");
const { compilerOptions } = require("../shared/tsconfig.spec");
const sharedConfig = require("../../libs/shared/jest.config.angular");

View File

@@ -10,11 +10,9 @@ import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
PasswordLoginCredentials,
RegisterRouteService,
} from "@bitwarden/auth/common";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
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";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
@@ -56,7 +54,7 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
return this.formGroup.controls.email;
}
formGroup = this.formBuilder.group({
formGroup = this.formBuilder.nonNullable.group({
email: ["", [Validators.required, Validators.email]],
masterPassword: [
"",
@@ -67,14 +65,12 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
protected twoFactorRoute = "2fa";
protected successRoute = "vault";
// TODO: remove when email verification flag is removed
protected registerRoute$ = this.registerRouteService.registerRoute$();
protected forcePasswordResetRoute = "update-temp-password";
protected destroy$ = new Subject<void>();
get loggedEmail() {
return this.formGroup.value.email;
return this.formGroup.controls.email.value;
}
constructor(
@@ -95,8 +91,6 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
protected route: ActivatedRoute,
protected loginEmailService: LoginEmailServiceAbstraction,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected webAuthnLoginService: WebAuthnLoginServiceAbstraction,
protected registerRouteService: RegisterRouteService,
protected toastService: ToastService,
) {
super(environmentService, i18nService, platformUtilsService, toastService);
@@ -146,8 +140,6 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
}
async submit(showToast = true) {
const data = this.formGroup.value;
await this.setupCaptcha();
this.formGroup.markAllAsTouched();
@@ -170,10 +162,10 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
try {
const credentials = new PasswordLoginCredentials(
data.email,
data.masterPassword,
this.formGroup.controls.email.value,
this.formGroup.controls.masterPassword.value,
this.captchaToken,
null,
undefined,
);
this.formPromise = this.loginStrategyService.logIn(credentials);
@@ -397,6 +389,8 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
email,
deviceIdentifier,
);
// FIXME: Remove when updating file. Eslint update
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
this.showLoginWithDevice = false;
}

View File

@@ -19,7 +19,6 @@ import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
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";
@@ -50,7 +49,7 @@ export class SsoComponent implements OnInit {
protected twoFactorRoute = "2fa";
protected successRoute = "lock";
protected trustedDeviceEncRoute = "login-initiated";
protected changePasswordRoute = "set-password";
protected changePasswordRoute = "set-password-jit";
protected forcePasswordResetRoute = "update-temp-password";
protected clientId: string;
protected redirectUri: string;
@@ -339,14 +338,6 @@ export class SsoComponent implements OnInit {
}
private async handleChangePasswordRequired(orgIdentifier: string) {
const emailVerification = await this.configService.getFeatureFlag(
FeatureFlag.EmailVerification,
);
if (emailVerification) {
this.changePasswordRoute = "set-password-jit";
}
await this.navigateViaCallbackOrRoute(
this.onSuccessfulLoginChangePasswordNavigate,
[this.changePasswordRoute],

View File

@@ -157,7 +157,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: error,
message: this.i18nService.t("webauthnCancelOrTimeout"),
});
},
(info: string) => {

View File

@@ -1,7 +1,7 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
<div class="tw-col-span-6">
<bit-form-field [disableMargin]="selectionSupportsAdditionalOptions">
<bit-form-field disableMargin>
<bit-label>{{ "country" | i18n }}</bit-label>
<bit-select formControlName="country">
<bit-option
@@ -14,59 +14,47 @@
</bit-form-field>
</div>
<div class="tw-col-span-6">
<bit-form-field [disableMargin]="selectionSupportsAdditionalOptions">
<bit-form-field disableMargin>
<bit-label>{{ "zipPostalCode" | i18n }}</bit-label>
<input bitInput type="text" formControlName="postalCode" autocomplete="postal-code" />
</bit-form-field>
</div>
<div class="tw-col-span-6" *ngIf="selectionSupportsAdditionalOptions">
<bit-form-control>
<input bitCheckbox type="checkbox" formControlName="includeTaxId" />
<bit-label>{{ "includeVAT" | i18n }}</bit-label>
</bit-form-control>
<ng-container *ngIf="isTaxSupported">
<div class="tw-col-span-6">
<bit-form-field disableMargin>
<bit-label>{{ "address1" | i18n }}</bit-label>
<input bitInput type="text" formControlName="line1" autocomplete="address-line1" />
</bit-form-field>
</div>
<div class="tw-col-span-6">
<bit-form-field disableMargin>
<bit-label>{{ "address2" | i18n }}</bit-label>
<input bitInput type="text" formControlName="line2" autocomplete="address-line2" />
</bit-form-field>
</div>
<div class="tw-col-span-6">
<bit-form-field disableMargin>
<bit-label>{{ "cityTown" | i18n }}</bit-label>
<input bitInput type="text" formControlName="city" autocomplete="address-level2" />
</bit-form-field>
</div>
<div class="tw-col-span-6">
<bit-form-field disableMargin>
<bit-label>{{ "stateProvince" | i18n }}</bit-label>
<input bitInput type="text" formControlName="state" autocomplete="address-level1" />
</bit-form-field>
</div>
<div class="tw-col-span-6" *ngIf="showTaxIdField">
<bit-form-field disableMargin>
<bit-label>{{ "taxIdNumber" | i18n }}</bit-label>
<input bitInput type="text" formControlName="taxId" />
</bit-form-field>
</div>
</ng-container>
<div class="tw-col-span-12" *ngIf="!!onSubmit">
<button bitButton bitFormButton buttonType="primary" type="submit">
{{ "submit" | i18n }}
</button>
</div>
</div>
<div
class="tw-grid tw-grid-cols-12 tw-gap-4"
*ngIf="selectionSupportsAdditionalOptions && includeTaxIdIsSelected"
>
<div class="tw-col-span-6">
<bit-form-field>
<bit-label>{{ "taxIdNumber" | i18n }}</bit-label>
<input bitInput type="text" formControlName="taxId" />
</bit-form-field>
</div>
</div>
<div
class="tw-grid tw-grid-cols-12 tw-gap-4"
*ngIf="selectionSupportsAdditionalOptions && includeTaxIdIsSelected"
>
<div class="tw-col-span-6">
<bit-form-field disableMargin>
<bit-label>{{ "address1" | i18n }}</bit-label>
<input bitInput type="text" formControlName="line1" autocomplete="address-line1" />
</bit-form-field>
</div>
<div class="tw-col-span-6">
<bit-form-field disableMargin>
<bit-label>{{ "address2" | i18n }}</bit-label>
<input bitInput type="text" formControlName="line2" autocomplete="address-line2" />
</bit-form-field>
</div>
<div class="tw-col-span-6">
<bit-form-field>
<bit-label>{{ "cityTown" | i18n }}</bit-label>
<input bitInput type="text" formControlName="city" autocomplete="address-level2" />
</bit-form-field>
</div>
<div class="tw-col-span-6">
<bit-form-field>
<bit-label>{{ "stateProvince" | i18n }}</bit-label>
<input bitInput type="text" formControlName="state" autocomplete="address-level1" />
</bit-form-field>
</div>
</div>
<button *ngIf="!!onSubmit" bitButton bitFormButton buttonType="primary" type="submit">
{{ "submit" | i18n }}
</button>
</form>

View File

@@ -3,14 +3,10 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
import { debounceTime } from "rxjs/operators";
import { TaxInformation } from "@bitwarden/common/billing/models/domain";
type Country = {
name: string;
value: string;
disabled: boolean;
};
import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction";
import { CountryListItem, TaxInformation } from "@bitwarden/common/billing/models/domain";
@Component({
selector: "app-manage-tax-information",
@@ -19,12 +15,23 @@ type Country = {
export class ManageTaxInformationComponent implements OnInit, OnDestroy {
@Input() startWith: TaxInformation;
@Input() onSubmit?: (taxInformation: TaxInformation) => Promise<void>;
@Input() showTaxIdField: boolean = true;
/**
* Emits when the tax information has changed.
*/
@Output() taxInformationChanged = new EventEmitter<TaxInformation>();
/**
* Emits when the tax information has been updated.
*/
@Output() taxInformationUpdated = new EventEmitter();
private taxInformation: TaxInformation;
protected formGroup = this.formBuilder.group({
country: ["", Validators.required],
postalCode: ["", Validators.required],
includeTaxId: false,
taxId: "",
line1: "",
line2: "",
@@ -32,16 +39,20 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy {
state: "",
});
protected isTaxSupported: boolean;
private destroy$ = new Subject<void>();
private taxInformation: TaxInformation;
protected readonly countries: CountryListItem[] = this.taxService.getCountries();
constructor(private formBuilder: FormBuilder) {}
constructor(
private formBuilder: FormBuilder,
private taxService: TaxServiceAbstraction,
) {}
getTaxInformation = (): TaxInformation & { includeTaxId: boolean } => ({
...this.taxInformation,
includeTaxId: this.formGroup.value.includeTaxId,
});
getTaxInformation(): TaxInformation {
return this.taxInformation;
}
submit = async () => {
this.formGroup.markAllAsTouched();
@@ -52,23 +63,36 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy {
this.taxInformationUpdated.emit();
};
touch = (): boolean => {
validate(): boolean {
if (this.formGroup.dirty) {
this.formGroup.markAllAsTouched();
return this.formGroup.valid;
} else {
return this.formGroup.valid;
}
}
markAllAsTouched() {
this.formGroup.markAllAsTouched();
return this.formGroup.valid;
};
}
async ngOnInit() {
if (this.startWith) {
this.formGroup.patchValue({
...this.startWith,
includeTaxId:
this.countrySupportsTax(this.startWith.country) &&
(!!this.startWith.taxId ||
!!this.startWith.line1 ||
!!this.startWith.line2 ||
!!this.startWith.city ||
!!this.startWith.state),
});
this.formGroup.controls.country.setValue(this.startWith.country);
this.formGroup.controls.postalCode.setValue(this.startWith.postalCode);
this.isTaxSupported =
this.startWith && this.startWith.country
? await this.taxService.isCountrySupported(this.startWith.country)
: false;
if (this.isTaxSupported) {
this.formGroup.controls.taxId.setValue(this.startWith.taxId);
this.formGroup.controls.line1.setValue(this.startWith.line1);
this.formGroup.controls.line2.setValue(this.startWith.line2);
this.formGroup.controls.city.setValue(this.startWith.city);
this.formGroup.controls.state.setValue(this.startWith.state);
}
}
this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((values) => {
@@ -82,354 +106,47 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy {
state: values.state,
};
});
this.formGroup.controls.country.valueChanges
.pipe(debounceTime(1000), takeUntil(this.destroy$))
.subscribe((country: string) => {
this.taxService
.isCountrySupported(country)
.then((isSupported) => (this.isTaxSupported = isSupported))
.catch(() => (this.isTaxSupported = false))
.finally(() => {
if (!this.isTaxSupported) {
this.formGroup.controls.taxId.setValue(null);
this.formGroup.controls.line1.setValue(null);
this.formGroup.controls.line2.setValue(null);
this.formGroup.controls.city.setValue(null);
this.formGroup.controls.state.setValue(null);
}
if (this.taxInformationChanged) {
this.taxInformationChanged.emit(this.taxInformation);
}
});
});
this.formGroup.controls.postalCode.valueChanges
.pipe(debounceTime(1000), takeUntil(this.destroy$))
.subscribe(() => {
if (this.taxInformationChanged) {
this.taxInformationChanged.emit(this.taxInformation);
}
});
this.formGroup.controls.taxId.valueChanges
.pipe(debounceTime(1000), takeUntil(this.destroy$))
.subscribe(() => {
if (this.taxInformationChanged) {
this.taxInformationChanged.emit(this.taxInformation);
}
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
protected countrySupportsTax(countryCode: string) {
return this.taxSupportedCountryCodes.includes(countryCode);
}
protected get includeTaxIdIsSelected() {
return this.formGroup.value.includeTaxId;
}
protected get selectionSupportsAdditionalOptions() {
return (
this.formGroup.value.country !== "US" && this.countrySupportsTax(this.formGroup.value.country)
);
}
protected countries: Country[] = [
{ name: "-- Select --", value: "", disabled: false },
{ name: "United States", value: "US", disabled: false },
{ name: "China", value: "CN", disabled: false },
{ name: "France", value: "FR", disabled: false },
{ name: "Germany", value: "DE", disabled: false },
{ name: "Canada", value: "CA", disabled: false },
{ name: "United Kingdom", value: "GB", disabled: false },
{ name: "Australia", value: "AU", disabled: false },
{ name: "India", value: "IN", disabled: false },
{ name: "", value: "-", disabled: true },
{ name: "Afghanistan", value: "AF", disabled: false },
{ name: "Åland Islands", value: "AX", disabled: false },
{ name: "Albania", value: "AL", disabled: false },
{ name: "Algeria", value: "DZ", disabled: false },
{ name: "American Samoa", value: "AS", disabled: false },
{ name: "Andorra", value: "AD", disabled: false },
{ name: "Angola", value: "AO", disabled: false },
{ name: "Anguilla", value: "AI", disabled: false },
{ name: "Antarctica", value: "AQ", disabled: false },
{ name: "Antigua and Barbuda", value: "AG", disabled: false },
{ name: "Argentina", value: "AR", disabled: false },
{ name: "Armenia", value: "AM", disabled: false },
{ name: "Aruba", value: "AW", disabled: false },
{ name: "Austria", value: "AT", disabled: false },
{ name: "Azerbaijan", value: "AZ", disabled: false },
{ name: "Bahamas", value: "BS", disabled: false },
{ name: "Bahrain", value: "BH", disabled: false },
{ name: "Bangladesh", value: "BD", disabled: false },
{ name: "Barbados", value: "BB", disabled: false },
{ name: "Belarus", value: "BY", disabled: false },
{ name: "Belgium", value: "BE", disabled: false },
{ name: "Belize", value: "BZ", disabled: false },
{ name: "Benin", value: "BJ", disabled: false },
{ name: "Bermuda", value: "BM", disabled: false },
{ name: "Bhutan", value: "BT", disabled: false },
{ name: "Bolivia, Plurinational State of", value: "BO", disabled: false },
{ name: "Bonaire, Sint Eustatius and Saba", value: "BQ", disabled: false },
{ name: "Bosnia and Herzegovina", value: "BA", disabled: false },
{ name: "Botswana", value: "BW", disabled: false },
{ name: "Bouvet Island", value: "BV", disabled: false },
{ name: "Brazil", value: "BR", disabled: false },
{ name: "British Indian Ocean Territory", value: "IO", disabled: false },
{ name: "Brunei Darussalam", value: "BN", disabled: false },
{ name: "Bulgaria", value: "BG", disabled: false },
{ name: "Burkina Faso", value: "BF", disabled: false },
{ name: "Burundi", value: "BI", disabled: false },
{ name: "Cambodia", value: "KH", disabled: false },
{ name: "Cameroon", value: "CM", disabled: false },
{ name: "Cape Verde", value: "CV", disabled: false },
{ name: "Cayman Islands", value: "KY", disabled: false },
{ name: "Central African Republic", value: "CF", disabled: false },
{ name: "Chad", value: "TD", disabled: false },
{ name: "Chile", value: "CL", disabled: false },
{ name: "Christmas Island", value: "CX", disabled: false },
{ name: "Cocos (Keeling) Islands", value: "CC", disabled: false },
{ name: "Colombia", value: "CO", disabled: false },
{ name: "Comoros", value: "KM", disabled: false },
{ name: "Congo", value: "CG", disabled: false },
{ name: "Congo, the Democratic Republic of the", value: "CD", disabled: false },
{ name: "Cook Islands", value: "CK", disabled: false },
{ name: "Costa Rica", value: "CR", disabled: false },
{ name: "Côte d'Ivoire", value: "CI", disabled: false },
{ name: "Croatia", value: "HR", disabled: false },
{ name: "Cuba", value: "CU", disabled: false },
{ name: "Curaçao", value: "CW", disabled: false },
{ name: "Cyprus", value: "CY", disabled: false },
{ name: "Czech Republic", value: "CZ", disabled: false },
{ name: "Denmark", value: "DK", disabled: false },
{ name: "Djibouti", value: "DJ", disabled: false },
{ name: "Dominica", value: "DM", disabled: false },
{ name: "Dominican Republic", value: "DO", disabled: false },
{ name: "Ecuador", value: "EC", disabled: false },
{ name: "Egypt", value: "EG", disabled: false },
{ name: "El Salvador", value: "SV", disabled: false },
{ name: "Equatorial Guinea", value: "GQ", disabled: false },
{ name: "Eritrea", value: "ER", disabled: false },
{ name: "Estonia", value: "EE", disabled: false },
{ name: "Ethiopia", value: "ET", disabled: false },
{ name: "Falkland Islands (Malvinas)", value: "FK", disabled: false },
{ name: "Faroe Islands", value: "FO", disabled: false },
{ name: "Fiji", value: "FJ", disabled: false },
{ name: "Finland", value: "FI", disabled: false },
{ name: "French Guiana", value: "GF", disabled: false },
{ name: "French Polynesia", value: "PF", disabled: false },
{ name: "French Southern Territories", value: "TF", disabled: false },
{ name: "Gabon", value: "GA", disabled: false },
{ name: "Gambia", value: "GM", disabled: false },
{ name: "Georgia", value: "GE", disabled: false },
{ name: "Ghana", value: "GH", disabled: false },
{ name: "Gibraltar", value: "GI", disabled: false },
{ name: "Greece", value: "GR", disabled: false },
{ name: "Greenland", value: "GL", disabled: false },
{ name: "Grenada", value: "GD", disabled: false },
{ name: "Guadeloupe", value: "GP", disabled: false },
{ name: "Guam", value: "GU", disabled: false },
{ name: "Guatemala", value: "GT", disabled: false },
{ name: "Guernsey", value: "GG", disabled: false },
{ name: "Guinea", value: "GN", disabled: false },
{ name: "Guinea-Bissau", value: "GW", disabled: false },
{ name: "Guyana", value: "GY", disabled: false },
{ name: "Haiti", value: "HT", disabled: false },
{ name: "Heard Island and McDonald Islands", value: "HM", disabled: false },
{ name: "Holy See (Vatican City State)", value: "VA", disabled: false },
{ name: "Honduras", value: "HN", disabled: false },
{ name: "Hong Kong", value: "HK", disabled: false },
{ name: "Hungary", value: "HU", disabled: false },
{ name: "Iceland", value: "IS", disabled: false },
{ name: "Indonesia", value: "ID", disabled: false },
{ name: "Iran, Islamic Republic of", value: "IR", disabled: false },
{ name: "Iraq", value: "IQ", disabled: false },
{ name: "Ireland", value: "IE", disabled: false },
{ name: "Isle of Man", value: "IM", disabled: false },
{ name: "Israel", value: "IL", disabled: false },
{ name: "Italy", value: "IT", disabled: false },
{ name: "Jamaica", value: "JM", disabled: false },
{ name: "Japan", value: "JP", disabled: false },
{ name: "Jersey", value: "JE", disabled: false },
{ name: "Jordan", value: "JO", disabled: false },
{ name: "Kazakhstan", value: "KZ", disabled: false },
{ name: "Kenya", value: "KE", disabled: false },
{ name: "Kiribati", value: "KI", disabled: false },
{ name: "Korea, Democratic People's Republic of", value: "KP", disabled: false },
{ name: "Korea, Republic of", value: "KR", disabled: false },
{ name: "Kuwait", value: "KW", disabled: false },
{ name: "Kyrgyzstan", value: "KG", disabled: false },
{ name: "Lao People's Democratic Republic", value: "LA", disabled: false },
{ name: "Latvia", value: "LV", disabled: false },
{ name: "Lebanon", value: "LB", disabled: false },
{ name: "Lesotho", value: "LS", disabled: false },
{ name: "Liberia", value: "LR", disabled: false },
{ name: "Libya", value: "LY", disabled: false },
{ name: "Liechtenstein", value: "LI", disabled: false },
{ name: "Lithuania", value: "LT", disabled: false },
{ name: "Luxembourg", value: "LU", disabled: false },
{ name: "Macao", value: "MO", disabled: false },
{ name: "Macedonia, the former Yugoslav Republic of", value: "MK", disabled: false },
{ name: "Madagascar", value: "MG", disabled: false },
{ name: "Malawi", value: "MW", disabled: false },
{ name: "Malaysia", value: "MY", disabled: false },
{ name: "Maldives", value: "MV", disabled: false },
{ name: "Mali", value: "ML", disabled: false },
{ name: "Malta", value: "MT", disabled: false },
{ name: "Marshall Islands", value: "MH", disabled: false },
{ name: "Martinique", value: "MQ", disabled: false },
{ name: "Mauritania", value: "MR", disabled: false },
{ name: "Mauritius", value: "MU", disabled: false },
{ name: "Mayotte", value: "YT", disabled: false },
{ name: "Mexico", value: "MX", disabled: false },
{ name: "Micronesia, Federated States of", value: "FM", disabled: false },
{ name: "Moldova, Republic of", value: "MD", disabled: false },
{ name: "Monaco", value: "MC", disabled: false },
{ name: "Mongolia", value: "MN", disabled: false },
{ name: "Montenegro", value: "ME", disabled: false },
{ name: "Montserrat", value: "MS", disabled: false },
{ name: "Morocco", value: "MA", disabled: false },
{ name: "Mozambique", value: "MZ", disabled: false },
{ name: "Myanmar", value: "MM", disabled: false },
{ name: "Namibia", value: "NA", disabled: false },
{ name: "Nauru", value: "NR", disabled: false },
{ name: "Nepal", value: "NP", disabled: false },
{ name: "Netherlands", value: "NL", disabled: false },
{ name: "New Caledonia", value: "NC", disabled: false },
{ name: "New Zealand", value: "NZ", disabled: false },
{ name: "Nicaragua", value: "NI", disabled: false },
{ name: "Niger", value: "NE", disabled: false },
{ name: "Nigeria", value: "NG", disabled: false },
{ name: "Niue", value: "NU", disabled: false },
{ name: "Norfolk Island", value: "NF", disabled: false },
{ name: "Northern Mariana Islands", value: "MP", disabled: false },
{ name: "Norway", value: "NO", disabled: false },
{ name: "Oman", value: "OM", disabled: false },
{ name: "Pakistan", value: "PK", disabled: false },
{ name: "Palau", value: "PW", disabled: false },
{ name: "Palestinian Territory, Occupied", value: "PS", disabled: false },
{ name: "Panama", value: "PA", disabled: false },
{ name: "Papua New Guinea", value: "PG", disabled: false },
{ name: "Paraguay", value: "PY", disabled: false },
{ name: "Peru", value: "PE", disabled: false },
{ name: "Philippines", value: "PH", disabled: false },
{ name: "Pitcairn", value: "PN", disabled: false },
{ name: "Poland", value: "PL", disabled: false },
{ name: "Portugal", value: "PT", disabled: false },
{ name: "Puerto Rico", value: "PR", disabled: false },
{ name: "Qatar", value: "QA", disabled: false },
{ name: "Réunion", value: "RE", disabled: false },
{ name: "Romania", value: "RO", disabled: false },
{ name: "Russian Federation", value: "RU", disabled: false },
{ name: "Rwanda", value: "RW", disabled: false },
{ name: "Saint Barthélemy", value: "BL", disabled: false },
{ name: "Saint Helena, Ascension and Tristan da Cunha", value: "SH", disabled: false },
{ name: "Saint Kitts and Nevis", value: "KN", disabled: false },
{ name: "Saint Lucia", value: "LC", disabled: false },
{ name: "Saint Martin (French part)", value: "MF", disabled: false },
{ name: "Saint Pierre and Miquelon", value: "PM", disabled: false },
{ name: "Saint Vincent and the Grenadines", value: "VC", disabled: false },
{ name: "Samoa", value: "WS", disabled: false },
{ name: "San Marino", value: "SM", disabled: false },
{ name: "Sao Tome and Principe", value: "ST", disabled: false },
{ name: "Saudi Arabia", value: "SA", disabled: false },
{ name: "Senegal", value: "SN", disabled: false },
{ name: "Serbia", value: "RS", disabled: false },
{ name: "Seychelles", value: "SC", disabled: false },
{ name: "Sierra Leone", value: "SL", disabled: false },
{ name: "Singapore", value: "SG", disabled: false },
{ name: "Sint Maarten (Dutch part)", value: "SX", disabled: false },
{ name: "Slovakia", value: "SK", disabled: false },
{ name: "Slovenia", value: "SI", disabled: false },
{ name: "Solomon Islands", value: "SB", disabled: false },
{ name: "Somalia", value: "SO", disabled: false },
{ name: "South Africa", value: "ZA", disabled: false },
{ name: "South Georgia and the South Sandwich Islands", value: "GS", disabled: false },
{ name: "South Sudan", value: "SS", disabled: false },
{ name: "Spain", value: "ES", disabled: false },
{ name: "Sri Lanka", value: "LK", disabled: false },
{ name: "Sudan", value: "SD", disabled: false },
{ name: "Suriname", value: "SR", disabled: false },
{ name: "Svalbard and Jan Mayen", value: "SJ", disabled: false },
{ name: "Swaziland", value: "SZ", disabled: false },
{ name: "Sweden", value: "SE", disabled: false },
{ name: "Switzerland", value: "CH", disabled: false },
{ name: "Syrian Arab Republic", value: "SY", disabled: false },
{ name: "Taiwan", value: "TW", disabled: false },
{ name: "Tajikistan", value: "TJ", disabled: false },
{ name: "Tanzania, United Republic of", value: "TZ", disabled: false },
{ name: "Thailand", value: "TH", disabled: false },
{ name: "Timor-Leste", value: "TL", disabled: false },
{ name: "Togo", value: "TG", disabled: false },
{ name: "Tokelau", value: "TK", disabled: false },
{ name: "Tonga", value: "TO", disabled: false },
{ name: "Trinidad and Tobago", value: "TT", disabled: false },
{ name: "Tunisia", value: "TN", disabled: false },
{ name: "Turkey", value: "TR", disabled: false },
{ name: "Turkmenistan", value: "TM", disabled: false },
{ name: "Turks and Caicos Islands", value: "TC", disabled: false },
{ name: "Tuvalu", value: "TV", disabled: false },
{ name: "Uganda", value: "UG", disabled: false },
{ name: "Ukraine", value: "UA", disabled: false },
{ name: "United Arab Emirates", value: "AE", disabled: false },
{ name: "United States Minor Outlying Islands", value: "UM", disabled: false },
{ name: "Uruguay", value: "UY", disabled: false },
{ name: "Uzbekistan", value: "UZ", disabled: false },
{ name: "Vanuatu", value: "VU", disabled: false },
{ name: "Venezuela, Bolivarian Republic of", value: "VE", disabled: false },
{ name: "Viet Nam", value: "VN", disabled: false },
{ name: "Virgin Islands, British", value: "VG", disabled: false },
{ name: "Virgin Islands, U.S.", value: "VI", disabled: false },
{ name: "Wallis and Futuna", value: "WF", disabled: false },
{ name: "Western Sahara", value: "EH", disabled: false },
{ name: "Yemen", value: "YE", disabled: false },
{ name: "Zambia", value: "ZM", disabled: false },
{ name: "Zimbabwe", value: "ZW", disabled: false },
];
private taxSupportedCountryCodes: string[] = [
"CN",
"FR",
"DE",
"CA",
"GB",
"AU",
"IN",
"AD",
"AR",
"AT",
"BE",
"BO",
"BR",
"BG",
"CL",
"CO",
"CR",
"HR",
"CY",
"CZ",
"DK",
"DO",
"EC",
"EG",
"SV",
"EE",
"FI",
"GE",
"GR",
"HK",
"HU",
"IS",
"ID",
"IQ",
"IE",
"IL",
"IT",
"JP",
"KE",
"KR",
"LV",
"LI",
"LT",
"LU",
"MY",
"MT",
"MX",
"NL",
"NZ",
"NO",
"PE",
"PH",
"PL",
"PT",
"RO",
"RU",
"SA",
"RS",
"SG",
"SK",
"SI",
"ZA",
"ES",
"SE",
"CH",
"TW",
"TH",
"TR",
"UA",
"AE",
"UY",
"VE",
"VN",
];
}

View File

@@ -1,37 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core";
@Directive({
selector: "[appA11yTitle]",
})
export class A11yTitleDirective implements OnInit {
@Input() set appA11yTitle(title: string) {
this.title = title;
this.setAttributes();
}
private title: string;
private originalTitle: string | null;
private originalAriaLabel: string | null;
constructor(
private el: ElementRef,
private renderer: Renderer2,
) {}
ngOnInit() {
this.originalTitle = this.el.nativeElement.getAttribute("title");
this.originalAriaLabel = this.el.nativeElement.getAttribute("aria-label");
this.setAttributes();
}
private setAttributes() {
if (this.originalTitle === null) {
this.renderer.setAttribute(this.el.nativeElement, "title", this.title);
}
if (this.originalAriaLabel === null) {
this.renderer.setAttribute(this.el.nativeElement, "aria-label", this.title);
}
}
}

View File

@@ -1,114 +0,0 @@
import { Component, ElementRef, ViewChild } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
import { CopyClickDirective } from "./copy-click.directive";
@Component({
template: `
<button appCopyClick="no toast shown" #noToast></button>
<button appCopyClick="info toast shown" showToast="info" #infoToast></button>
<button appCopyClick="success toast shown" showToast #successToast></button>
<button appCopyClick="toast with label" showToast valueLabel="Content" #toastWithLabel></button>
`,
})
class TestCopyClickComponent {
@ViewChild("noToast") noToastButton: ElementRef<HTMLButtonElement>;
@ViewChild("infoToast") infoToastButton: ElementRef<HTMLButtonElement>;
@ViewChild("successToast") successToastButton: ElementRef<HTMLButtonElement>;
@ViewChild("toastWithLabel") toastWithLabelButton: ElementRef<HTMLButtonElement>;
}
describe("CopyClickDirective", () => {
let fixture: ComponentFixture<TestCopyClickComponent>;
const copyToClipboard = jest.fn();
const showToast = jest.fn();
beforeEach(async () => {
copyToClipboard.mockClear();
showToast.mockClear();
await TestBed.configureTestingModule({
declarations: [CopyClickDirective, TestCopyClickComponent],
providers: [
{
provide: I18nService,
useValue: {
t: (key: string, ...rest: string[]) => {
if (rest?.length) {
return `${key} ${rest.join("")}`;
}
return key;
},
},
},
{ provide: PlatformUtilsService, useValue: { copyToClipboard } },
{ provide: ToastService, useValue: { showToast } },
],
}).compileComponents();
fixture = TestBed.createComponent(TestCopyClickComponent);
fixture.detectChanges();
});
it("copies the the value for all variants of toasts ", () => {
const noToastButton = fixture.componentInstance.noToastButton.nativeElement;
noToastButton.click();
expect(copyToClipboard).toHaveBeenCalledWith("no toast shown");
const infoToastButton = fixture.componentInstance.infoToastButton.nativeElement;
infoToastButton.click();
expect(copyToClipboard).toHaveBeenCalledWith("info toast shown");
const successToastButton = fixture.componentInstance.successToastButton.nativeElement;
successToastButton.click();
expect(copyToClipboard).toHaveBeenCalledWith("success toast shown");
});
it("does not show a toast when showToast is not present", () => {
const noToastButton = fixture.componentInstance.noToastButton.nativeElement;
noToastButton.click();
expect(showToast).not.toHaveBeenCalled();
});
it("shows a success toast when showToast is present", () => {
const successToastButton = fixture.componentInstance.successToastButton.nativeElement;
successToastButton.click();
expect(showToast).toHaveBeenCalledWith({
message: "copySuccessful",
title: null,
variant: "success",
});
});
it("shows the toast variant when set with showToast", () => {
const infoToastButton = fixture.componentInstance.infoToastButton.nativeElement;
infoToastButton.click();
expect(showToast).toHaveBeenCalledWith({
message: "copySuccessful",
title: null,
variant: "info",
});
});
it('includes label in toast message when "copyLabel" is set', () => {
const toastWithLabelButton = fixture.componentInstance.toastWithLabelButton.nativeElement;
toastWithLabelButton.click();
expect(showToast).toHaveBeenCalledWith({
message: "valueCopied Content",
title: null,
variant: "success",
});
});
});

View File

@@ -1,69 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, HostListener, Input } from "@angular/core";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
import { ToastVariant } from "@bitwarden/components/src/toast/toast.component";
@Directive({
selector: "[appCopyClick]",
})
export class CopyClickDirective {
private _showToast = false;
private toastVariant: ToastVariant = "success";
constructor(
private platformUtilsService: PlatformUtilsService,
private toastService: ToastService,
private i18nService: I18nService,
) {}
@Input("appCopyClick") valueToCopy = "";
/**
* When set, the toast displayed will show `<valueLabel> copied`
* instead of the default messaging.
*/
@Input() valueLabel: string;
/**
* When set without a value, a success toast will be shown when the value is copied
* @example
* ```html
* <app-component [appCopyClick]="value to copy" showToast/></app-component>
* ```
* When set with a value, a toast with the specified variant will be shown when the value is copied
*
* @example
* ```html
* <app-component [appCopyClick]="value to copy" showToast="info"/></app-component>
* ```
*/
@Input() set showToast(value: ToastVariant | "") {
// When the `showToast` is set without a value, an empty string will be passed
if (value === "") {
this._showToast = true;
} else {
this._showToast = true;
this.toastVariant = value;
}
}
@HostListener("click") onClick() {
this.platformUtilsService.copyToClipboard(this.valueToCopy);
if (this._showToast) {
const message = this.valueLabel
? this.i18nService.t("valueCopied", this.valueLabel)
: this.i18nService.t("copySuccessful");
this.toastService.showToast({
variant: this.toastVariant,
title: null,
message,
});
}
}
}

View File

@@ -1,6 +1,7 @@
import { Directive, OnInit, TemplateRef, ViewContainerRef } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
/**
@@ -14,11 +15,19 @@ export class NotPremiumDirective implements OnInit {
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private billingAccountProfileStateService: BillingAccountProfileStateService,
private accountService: AccountService,
) {}
async ngOnInit(): Promise<void> {
const account = await firstValueFrom(this.accountService.activeAccount$);
if (!account) {
this.viewContainer.createEmbeddedView(this.templateRef);
return;
}
const premium = await firstValueFrom(
this.billingAccountProfileStateService.hasPremiumFromAnySource$,
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
);
if (premium) {

View File

@@ -1,6 +1,7 @@
import { Directive, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { of, Subject, switchMap, takeUntil } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
/**
@@ -16,16 +17,24 @@ export class PremiumDirective implements OnInit, OnDestroy {
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private billingAccountProfileStateService: BillingAccountProfileStateService,
private accountService: AccountService,
) {}
async ngOnInit(): Promise<void> {
this.billingAccountProfileStateService.hasPremiumFromAnySource$
.pipe(takeUntil(this.directiveIsDestroyed$))
this.accountService.activeAccount$
.pipe(
switchMap((account) =>
account
? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id)
: of(false),
),
takeUntil(this.directiveIsDestroyed$),
)
.subscribe((premium: boolean) => {
if (premium) {
this.viewContainer.clear();
} else {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
});
}

View File

@@ -25,15 +25,15 @@ import {
TableModule,
ToastModule,
TypographyModule,
CopyClickDirective,
A11yTitleDirective,
} from "@bitwarden/components";
import { TwoFactorIconComponent } from "./auth/components/two-factor-icon.component";
import { DeprecatedCalloutComponent } from "./components/callout.component";
import { A11yInvalidDirective } from "./directives/a11y-invalid.directive";
import { A11yTitleDirective } from "./directives/a11y-title.directive";
import { ApiActionDirective } from "./directives/api-action.directive";
import { BoxRowDirective } from "./directives/box-row.directive";
import { CopyClickDirective } from "./directives/copy-click.directive";
import { CopyTextDirective } from "./directives/copy-text.directive";
import { FallbackSrcDirective } from "./directives/fallback-src.directive";
import { IfFeatureDirective } from "./directives/if-feature.directive";
@@ -83,10 +83,11 @@ import { IconComponent } from "./vault/components/icon.component";
LinkModule,
IconModule,
TextDragDirective,
CopyClickDirective,
A11yTitleDirective,
],
declarations: [
A11yInvalidDirective,
A11yTitleDirective,
ApiActionDirective,
AutofocusDirective,
BoxRowDirective,
@@ -105,7 +106,6 @@ import { IconComponent } from "./vault/components/icon.component";
StopClickDirective,
StopPropDirective,
TrueFalseValueDirective,
CopyClickDirective,
LaunchClickDirective,
UserNamePipe,
PasswordStrengthComponent,

View File

@@ -2,6 +2,9 @@ import { Pipe, PipeTransform } from "@angular/core";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
/**
* @deprecated: Please use the I18nPipe from @bitwarden/ui-common
*/
@Pipe({
name: "i18n",
})

View File

@@ -1,138 +1,4 @@
import { Provider } from "@angular/core";
import { Constructor, Opaque } from "type-fest";
import { SafeInjectionToken } from "../../services/injection-tokens";
/**
* The return type of the {@link safeProvider} helper function.
* Used to distinguish a type safe provider definition from a non-type safe provider definition.
* @deprecated: Please use the SafeProvider & safeProvider from @bitwarden/ui-common
*/
export type SafeProvider = Opaque<Provider>;
// TODO: type-fest also provides a type like this when we upgrade >= 3.7.0
type AbstractConstructor<T> = abstract new (...args: any) => T;
type MapParametersToDeps<T> = {
[K in keyof T]: AbstractConstructor<T[K]> | SafeInjectionToken<T[K]>;
};
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> | SafeInjectionToken<any>,
I extends Constructor<ProviderInstanceType<A>>,
D extends MapParametersToDeps<ConstructorParameters<I>>,
> = {
provide: A;
useClass: I;
deps: D;
};
/**
* Represents a dependency provided with the useValue option.
*/
type SafeValueProvider<A extends SafeInjectionToken<any>, V extends SafeInjectionTokenType<A>> = {
provide: A;
useValue: V;
};
/**
* Represents a dependency provided with the useFactory option.
*/
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> | 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;
};
/**
* If useAngularDecorators: true is specified, do not require a deps array.
* This is a manual override for where @Injectable decorators are used
*/
type UseAngularDecorators<T extends { deps: any }> = Omit<T, "deps"> & {
useAngularDecorators: true;
};
/**
* Represents a type with a deps array that may optionally be overridden with useAngularDecorators
*/
type AllowAngularDecorators<T extends { deps: any }> = T | UseAngularDecorators<T>;
/**
* A factory function that creates a provider for the ngModule providers array.
* This (almost) guarantees type safety for your provider definition. It does nothing at runtime.
* Warning: the useAngularDecorators option provides an override where your class uses the Injectable decorator,
* however this cannot be enforced by the type system and will not cause an error if the decorator is not used.
* @example safeProvider({ provide: MyService, useClass: DefaultMyService, deps: [AnotherService] })
* @param provider Your provider object in the usual shape (e.g. using useClass, useValue, useFactory, etc.)
* @returns The exact same object without modification (pass-through).
*/
export const safeProvider = <
// types for useClass
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 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> | SafeInjectionToken<any>,
IExisting extends
| Constructor<ProviderInstanceType<AExisting>>
| AbstractConstructor<ProviderInstanceType<AExisting>>,
// types for no token
IConcrete extends Constructor<any>,
DConcrete extends MapParametersToDeps<ConstructorParameters<IConcrete>>,
>(
provider:
| AllowAngularDecorators<SafeClassProvider<AClass, IClass, DClass>>
| SafeValueProvider<AValue, VValue>
| AllowAngularDecorators<SafeFactoryProvider<AFactory, IFactory, DFactory>>
| SafeExistingProvider<AExisting, IExisting>
| AllowAngularDecorators<SafeConcreteProvider<IConcrete, DConcrete>>
| Constructor<unknown>,
): SafeProvider => provider as SafeProvider;
export { SafeProvider, safeProvider } from "@bitwarden/ui-common";

View File

@@ -1,111 +0,0 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
// This rule bans @ts-expect-error comments without explanation. In this file, we use it to test our types, and
// explanation is provided in header comments before each test.
import { safeProvider } from "./safe-provider";
class FooFactory {
create() {
return "thing";
}
}
abstract class FooService {
createFoo: (str: string) => string;
}
class DefaultFooService implements FooService {
constructor(private factory: FooFactory) {}
createFoo(str: string) {
return str ?? this.factory.create();
}
}
class BarFactory {
create() {
return 5;
}
}
abstract class BarService {
createBar: (num: number) => number;
}
class DefaultBarService implements BarService {
constructor(private factory: BarFactory) {}
createBar(num: number) {
return num ?? this.factory.create();
}
}
abstract class FooBarService {}
class DefaultFooBarService {
constructor(
private fooFactory: FooFactory,
private barFactory: BarFactory,
) {}
}
// useClass happy path with deps
safeProvider({
provide: FooService,
useClass: DefaultFooService,
deps: [FooFactory],
});
// useClass happy path with useAngularDecorators
safeProvider({
provide: FooService,
useClass: DefaultFooService,
useAngularDecorators: true,
});
// useClass: expect error if implementation does not match abstraction
safeProvider({
provide: FooService,
// @ts-expect-error
useClass: DefaultBarService,
deps: [BarFactory],
});
// useClass: expect error if deps type does not match
safeProvider({
provide: FooService,
useClass: DefaultFooService,
// @ts-expect-error
deps: [BarFactory],
});
// useClass: expect error if not enough deps specified
safeProvider({
provide: FooService,
useClass: DefaultFooService,
// @ts-expect-error
deps: [],
});
// useClass: expect error if too many deps specified
safeProvider({
provide: FooService,
useClass: DefaultFooService,
// @ts-expect-error
deps: [FooFactory, BarFactory],
});
// useClass: expect error if deps are in the wrong order
safeProvider({
provide: FooBarService,
useClass: DefaultFooBarService,
// @ts-expect-error
deps: [BarFactory, FooFactory],
});
// useClass: expect error if no deps specified and not using Angular decorators
// @ts-expect-error
safeProvider({
provide: FooService,
useClass: DefaultFooService,
});

View File

@@ -1,6 +1,5 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { InjectionToken } from "@angular/core";
import { Observable, Subject } from "rxjs";
import { LogoutReason } from "@bitwarden/auth/common";
@@ -14,17 +13,9 @@ import { Theme } from "@bitwarden/common/platform/enums";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { Message } from "@bitwarden/common/platform/messaging";
import { VaultTimeout } from "@bitwarden/common/types/vault-timeout.type";
declare const tag: unique symbol;
/**
* A (more) typesafe version of InjectionToken which will more strictly enforce the generic type parameter.
* @remarks The default angular implementation does not use the generic type to define the structure of the object,
* so the structural type system will not complain about a mismatch in the type parameter.
* This is solved by assigning T to an arbitrary private property.
*/
export class SafeInjectionToken<T> extends InjectionToken<T> {
private readonly [tag]: T;
}
import { SafeInjectionToken } from "@bitwarden/ui-common";
// Re-export the SafeInjectionToken from ui-common
export { SafeInjectionToken } from "@bitwarden/ui-common";
export const WINDOW = new SafeInjectionToken<Window>("WINDOW");
export const OBSERVABLE_MEMORY_STORAGE = new SafeInjectionToken<

View File

@@ -20,6 +20,7 @@ import {
DefaultLoginComponentService,
LoginDecryptionOptionsService,
DefaultLoginDecryptionOptionsService,
DefaultLoginApprovalComponentService,
} from "@bitwarden/auth/angular";
import {
AuthRequestServiceAbstraction,
@@ -34,11 +35,11 @@ import {
UserDecryptionOptionsService,
UserDecryptionOptionsServiceAbstraction,
LogoutReason,
RegisterRouteService,
AuthRequestApiService,
DefaultAuthRequestApiService,
DefaultLoginSuccessHandlerService,
LoginSuccessHandlerService,
LoginApprovalComponentServiceAbstraction,
} from "@bitwarden/auth/common";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
@@ -138,11 +139,13 @@ import {
import { AccountBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/account/account-billing-api.service.abstraction";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { OrganizationBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-billing-api.service.abstraction";
import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction";
import { AccountBillingApiService } from "@bitwarden/common/billing/services/account/account-billing-api.service";
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
import { BillingApiService } from "@bitwarden/common/billing/services/billing-api.service";
import { OrganizationBillingApiService } from "@bitwarden/common/billing/services/organization/organization-billing-api.service";
import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
import { TaxService } from "@bitwarden/common/billing/services/tax.service";
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service";
@@ -277,17 +280,19 @@ import {
ImportServiceAbstraction,
} from "@bitwarden/importer/core";
import {
KeyService as KeyServiceAbstraction,
DefaultKeyService as KeyService,
KeyService,
DefaultKeyService,
BiometricStateService,
DefaultBiometricStateService,
KdfConfigService,
BiometricsService,
DefaultKdfConfigService,
KdfConfigService,
UserAsymmetricKeysRegenerationService,
DefaultUserAsymmetricKeysRegenerationService,
UserAsymmetricKeysRegenerationApiService,
DefaultUserAsymmetricKeysRegenerationApiService,
} from "@bitwarden/key-management";
import { SafeInjectionToken } from "@bitwarden/ui-common";
import { PasswordRepromptService } from "@bitwarden/vault";
import {
VaultExportService,
@@ -298,6 +303,8 @@ import {
IndividualVaultExportServiceAbstraction,
} from "@bitwarden/vault-export-core";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { NewDeviceVerificationNoticeService } from "../../../vault/src/services/new-device-verification-notice.service";
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service";
import { ViewCacheService } from "../platform/abstractions/view-cache.service";
@@ -316,7 +323,6 @@ import {
MEMORY_STORAGE,
OBSERVABLE_DISK_STORAGE,
OBSERVABLE_MEMORY_STORAGE,
SafeInjectionToken,
SECURE_STORAGE,
STATE_FACTORY,
SYSTEM_LANGUAGE,
@@ -407,7 +413,7 @@ const safeProviders: SafeProvider[] = [
deps: [
AccountServiceAbstraction,
MessagingServiceAbstraction,
KeyServiceAbstraction,
KeyService,
ApiServiceAbstraction,
StateServiceAbstraction,
TokenServiceAbstraction,
@@ -419,7 +425,7 @@ const safeProviders: SafeProvider[] = [
deps: [
AccountServiceAbstraction,
InternalMasterPasswordServiceAbstraction,
KeyServiceAbstraction,
KeyService,
ApiServiceAbstraction,
TokenServiceAbstraction,
AppIdServiceAbstraction,
@@ -454,10 +460,15 @@ const safeProviders: SafeProvider[] = [
useClass: CipherFileUploadService,
deps: [ApiServiceAbstraction, FileUploadServiceAbstraction],
}),
safeProvider({
provide: DomainSettingsService,
useClass: DefaultDomainSettingsService,
deps: [StateProvider, ConfigService],
}),
safeProvider({
provide: CipherServiceAbstraction,
useFactory: (
keyService: KeyServiceAbstraction,
keyService: KeyService,
domainSettingsService: DomainSettingsService,
apiService: ApiServiceAbstraction,
i18nService: I18nServiceAbstraction,
@@ -487,7 +498,7 @@ const safeProviders: SafeProvider[] = [
accountService,
),
deps: [
KeyServiceAbstraction,
KeyService,
DomainSettingsService,
ApiServiceAbstraction,
I18nServiceAbstraction,
@@ -506,7 +517,7 @@ const safeProviders: SafeProvider[] = [
provide: InternalFolderService,
useClass: FolderService,
deps: [
KeyServiceAbstraction,
KeyService,
EncryptService,
I18nServiceAbstraction,
CipherServiceAbstraction,
@@ -551,7 +562,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: CollectionService,
useClass: DefaultCollectionService,
deps: [KeyServiceAbstraction, EncryptService, I18nServiceAbstraction, StateProvider],
deps: [KeyService, EncryptService, I18nServiceAbstraction, StateProvider],
}),
safeProvider({
provide: ENV_ADDITIONAL_REGIONS,
@@ -596,8 +607,8 @@ const safeProviders: SafeProvider[] = [
deps: [CryptoFunctionServiceAbstraction],
}),
safeProvider({
provide: KeyServiceAbstraction,
useClass: KeyService,
provide: KeyService,
useClass: DefaultKeyService,
deps: [
PinServiceAbstraction,
InternalMasterPasswordServiceAbstraction,
@@ -622,7 +633,7 @@ const safeProviders: SafeProvider[] = [
useFactory: legacyPasswordGenerationServiceFactory,
deps: [
EncryptService,
KeyServiceAbstraction,
KeyService,
PolicyServiceAbstraction,
AccountServiceAbstraction,
StateProvider,
@@ -631,7 +642,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: GeneratorHistoryService,
useClass: LocalGeneratorHistoryService,
deps: [EncryptService, KeyServiceAbstraction, StateProvider],
deps: [EncryptService, KeyService, StateProvider],
}),
safeProvider({
provide: UsernameGenerationServiceAbstraction,
@@ -639,7 +650,7 @@ const safeProviders: SafeProvider[] = [
deps: [
ApiServiceAbstraction,
I18nServiceAbstraction,
KeyServiceAbstraction,
KeyService,
EncryptService,
PolicyServiceAbstraction,
AccountServiceAbstraction,
@@ -679,7 +690,7 @@ const safeProviders: SafeProvider[] = [
provide: InternalSendService,
useClass: SendService,
deps: [
KeyServiceAbstraction,
KeyService,
I18nServiceAbstraction,
KeyGenerationServiceAbstraction,
SendStateProviderAbstraction,
@@ -706,7 +717,7 @@ const safeProviders: SafeProvider[] = [
DomainSettingsService,
InternalFolderService,
CipherServiceAbstraction,
KeyServiceAbstraction,
KeyService,
CollectionService,
MessagingServiceAbstraction,
InternalPolicyService,
@@ -739,7 +750,7 @@ const safeProviders: SafeProvider[] = [
AccountServiceAbstraction,
PinServiceAbstraction,
UserDecryptionOptionsServiceAbstraction,
KeyServiceAbstraction,
KeyService,
TokenServiceAbstraction,
PolicyServiceAbstraction,
BiometricStateService,
@@ -766,6 +777,7 @@ const safeProviders: SafeProvider[] = [
StateEventRunnerService,
TaskSchedulerService,
LogService,
BiometricsService,
LOCKED_CALLBACK,
LOGOUT_CALLBACK,
],
@@ -812,7 +824,7 @@ const safeProviders: SafeProvider[] = [
ImportApiServiceAbstraction,
I18nServiceAbstraction,
CollectionService,
KeyServiceAbstraction,
KeyService,
EncryptService,
PinServiceAbstraction,
AccountServiceAbstraction,
@@ -825,7 +837,7 @@ const safeProviders: SafeProvider[] = [
FolderServiceAbstraction,
CipherServiceAbstraction,
PinServiceAbstraction,
KeyServiceAbstraction,
KeyService,
EncryptService,
CryptoFunctionServiceAbstraction,
KdfConfigService,
@@ -839,7 +851,7 @@ const safeProviders: SafeProvider[] = [
CipherServiceAbstraction,
ApiServiceAbstraction,
PinServiceAbstraction,
KeyServiceAbstraction,
KeyService,
EncryptService,
CryptoFunctionServiceAbstraction,
CollectionService,
@@ -946,7 +958,7 @@ const safeProviders: SafeProvider[] = [
deps: [
AccountServiceAbstraction,
InternalMasterPasswordServiceAbstraction,
KeyServiceAbstraction,
KeyService,
ApiServiceAbstraction,
TokenServiceAbstraction,
LogService,
@@ -960,17 +972,15 @@ const safeProviders: SafeProvider[] = [
provide: UserVerificationServiceAbstraction,
useClass: UserVerificationService,
deps: [
KeyServiceAbstraction,
KeyService,
AccountServiceAbstraction,
InternalMasterPasswordServiceAbstraction,
I18nServiceAbstraction,
UserVerificationApiServiceAbstraction,
UserDecryptionOptionsServiceAbstraction,
PinServiceAbstraction,
LogService,
VaultTimeoutSettingsServiceAbstraction,
PlatformUtilsServiceAbstraction,
KdfConfigService,
BiometricsService,
],
}),
safeProvider({
@@ -993,7 +1003,7 @@ const safeProviders: SafeProvider[] = [
deps: [
OrganizationApiServiceAbstraction,
AccountServiceAbstraction,
KeyServiceAbstraction,
KeyService,
EncryptService,
OrganizationUserApiService,
I18nServiceAbstraction,
@@ -1095,7 +1105,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: DevicesServiceAbstraction,
useClass: DevicesServiceImplementation,
deps: [DevicesApiServiceAbstraction],
deps: [DevicesApiServiceAbstraction, AppIdServiceAbstraction],
}),
safeProvider({
provide: DeviceTrustServiceAbstraction,
@@ -1103,7 +1113,7 @@ const safeProviders: SafeProvider[] = [
deps: [
KeyGenerationServiceAbstraction,
CryptoFunctionServiceAbstraction,
KeyServiceAbstraction,
KeyService,
EncryptService,
AppIdServiceAbstraction,
DevicesApiServiceAbstraction,
@@ -1123,7 +1133,7 @@ const safeProviders: SafeProvider[] = [
AppIdServiceAbstraction,
AccountServiceAbstraction,
InternalMasterPasswordServiceAbstraction,
KeyServiceAbstraction,
KeyService,
EncryptService,
ApiServiceAbstraction,
StateProvider,
@@ -1217,7 +1227,7 @@ const safeProviders: SafeProvider[] = [
ApiServiceAbstraction,
BillingApiServiceAbstraction,
ConfigService,
KeyServiceAbstraction,
KeyService,
EncryptService,
I18nServiceAbstraction,
OrganizationApiServiceAbstraction,
@@ -1234,11 +1244,6 @@ const safeProviders: SafeProvider[] = [
useClass: BadgeSettingsService,
deps: [StateProvider],
}),
safeProvider({
provide: DomainSettingsService,
useClass: DefaultDomainSettingsService,
deps: [StateProvider],
}),
safeProvider({
provide: BiometricStateService,
useClass: DefaultBiometricStateService,
@@ -1264,10 +1269,15 @@ const safeProviders: SafeProvider[] = [
useClass: BillingApiService,
deps: [ApiServiceAbstraction, LogService, ToastService],
}),
safeProvider({
provide: TaxServiceAbstraction,
useClass: TaxService,
deps: [ApiServiceAbstraction],
}),
safeProvider({
provide: BillingAccountProfileStateService,
useClass: DefaultBillingAccountProfileStateService,
deps: [StateProvider],
deps: [StateProvider, PlatformUtilsServiceAbstraction, ApiServiceAbstraction],
}),
safeProvider({
provide: OrganizationManagementPreferencesService,
@@ -1277,7 +1287,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: UserAutoUnlockKeyService,
useClass: UserAutoUnlockKeyService,
deps: [KeyServiceAbstraction],
deps: [KeyService],
}),
safeProvider({
provide: ErrorHandler,
@@ -1321,7 +1331,7 @@ const safeProviders: SafeProvider[] = [
useClass: DefaultSetPasswordJitService,
deps: [
ApiServiceAbstraction,
KeyServiceAbstraction,
KeyService,
EncryptService,
I18nServiceAbstraction,
KdfConfigService,
@@ -1336,11 +1346,6 @@ const safeProviders: SafeProvider[] = [
useClass: DefaultServerSettingsService,
deps: [ConfigService],
}),
safeProvider({
provide: RegisterRouteService,
useClass: RegisterRouteService,
deps: [ConfigService],
}),
safeProvider({
provide: AnonLayoutWrapperDataService,
useClass: DefaultAnonLayoutWrapperDataService,
@@ -1349,7 +1354,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: RegistrationFinishServiceAbstraction,
useClass: DefaultRegistrationFinishService,
deps: [KeyServiceAbstraction, AccountApiServiceAbstraction],
deps: [KeyService, AccountApiServiceAbstraction],
}),
safeProvider({
provide: ViewCacheService,
@@ -1376,7 +1381,7 @@ const safeProviders: SafeProvider[] = [
PlatformUtilsServiceAbstraction,
AccountServiceAbstraction,
KdfConfigService,
KeyServiceAbstraction,
KeyService,
],
}),
safeProvider({
@@ -1389,6 +1394,11 @@ const safeProviders: SafeProvider[] = [
useClass: DefaultAuthRequestApiService,
deps: [ApiServiceAbstraction, LogService],
}),
safeProvider({
provide: LoginApprovalComponentServiceAbstraction,
useClass: DefaultLoginApprovalComponentService,
deps: [],
}),
safeProvider({
provide: LoginDecryptionOptionsService,
useClass: DefaultLoginDecryptionOptionsService,
@@ -1404,7 +1414,7 @@ const safeProviders: SafeProvider[] = [
provide: UserAsymmetricKeysRegenerationService,
useClass: DefaultUserAsymmetricKeysRegenerationService,
deps: [
KeyServiceAbstraction,
KeyService,
CipherServiceAbstraction,
UserAsymmetricKeysRegenerationApiService,
LogService,

View File

@@ -3,7 +3,15 @@
import { DatePipe } from "@angular/common";
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Subject, firstValueFrom, takeUntil, map, BehaviorSubject, concatMap } from "rxjs";
import {
Subject,
firstValueFrom,
takeUntil,
map,
BehaviorSubject,
concatMap,
switchMap,
} from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
@@ -197,8 +205,13 @@ export class AddEditComponent implements OnInit, OnDestroy {
const env = await firstValueFrom(this.environmentService.environment$);
this.sendLinkBaseUrl = env.getSendUrl();
this.billingAccountProfileStateService.hasPremiumFromAnySource$
.pipe(takeUntil(this.destroy$))
this.accountService.activeAccount$
.pipe(
switchMap((account) =>
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
),
takeUntil(this.destroy$),
)
.subscribe((hasPremiumFromAnySource) => {
this.canAccessPremium = hasPremiumFromAnySource;
});

View File

@@ -16,7 +16,7 @@ import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils";
import { ClientType, EventType } from "@bitwarden/common/enums";
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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@@ -24,6 +24,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CollectionId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -40,7 +41,8 @@ import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view";
import { SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view";
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { generate_ssh_key } from "@bitwarden/sdk-internal";
import { PasswordRepromptService } from "@bitwarden/vault";
@Directive()
@@ -102,6 +104,8 @@ export class AddEditComponent implements OnInit, OnDestroy {
private personalOwnershipPolicyAppliesToActiveUser: boolean;
private previousCipherId: string;
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
get fido2CredentialCreationDateValue(): string {
const dateCreated = this.i18nService.t("dateCreated");
const creationDate = this.datePipe.transform(
@@ -124,12 +128,14 @@ export class AddEditComponent implements OnInit, OnDestroy {
protected policyService: PolicyService,
protected logService: LogService,
protected passwordRepromptService: PasswordRepromptService,
private organizationService: OrganizationService,
protected organizationService: OrganizationService,
protected dialogService: DialogService,
protected win: Window,
protected datePipe: DatePipe,
protected configService: ConfigService,
protected cipherAuthorizationService: CipherAuthorizationService,
protected toastService: ToastService,
private sdkService: SdkService,
) {
this.typeOptions = [
{ name: i18nService.t("typeLogin"), value: CipherType.Login },
@@ -206,7 +212,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.canUseReprompt = await this.passwordRepromptService.enabled();
const sshKeysEnabled = await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem);
if (this.platformUtilsService.getClientType() == ClientType.Desktop && sshKeysEnabled) {
if (sshKeysEnabled) {
this.typeOptions.push({ name: this.i18nService.t("typeSshKey"), value: CipherType.SshKey });
}
}
@@ -259,12 +265,10 @@ export class AddEditComponent implements OnInit, OnDestroy {
const loadedAddEditCipherInfo = await this.loadAddEditCipherInfo();
const activeUserId = await firstValueFrom(this.activeUserId$);
if (this.cipher == null) {
if (this.editMode) {
const cipher = await this.loadCipher();
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.cipher = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
@@ -323,7 +327,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.cipher.login.fido2Credentials = null;
}
this.folders$ = this.folderService.folderViews$;
this.folders$ = this.folderService.folderViews$(activeUserId);
if (this.editMode && this.previousCipherId !== this.cipherId) {
void this.eventCollectionService.collectMany(EventType.Cipher_ClientViewed, [this.cipher]);
@@ -339,6 +343,17 @@ export class AddEditComponent implements OnInit, OnDestroy {
[this.collectionId as CollectionId],
this.isAdminConsoleAction,
);
if (!this.editMode || this.cloneMode) {
// Creating an ssh key directly while filtering to the ssh key category
// must force a key to be set. SSH keys must never be created with an empty private key field
if (
this.cipher.type === CipherType.SshKey &&
(this.cipher.sshKey.privateKey == null || this.cipher.sshKey.privateKey === "")
) {
await this.generateSshKey(false);
}
}
}
async submit(): Promise<boolean> {
@@ -357,11 +372,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
}
if (this.cipher.name == null || this.cipher.name === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("nameRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("nameRequired"),
});
return false;
}
@@ -370,11 +385,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
!this.allowPersonal &&
this.cipher.organizationId == null
) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("personalOwnershipSubmitError"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("personalOwnershipSubmitError"),
});
return false;
}
@@ -409,11 +424,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.formPromise = this.saveCipher(cipher);
await this.formPromise;
this.cipher.id = cipher.id;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.editMode && !this.cloneMode ? "editedItem" : "addedItem"),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t(this.editMode && !this.cloneMode ? "editedItem" : "addedItem"),
});
this.onSavedCipher.emit(this.cipher);
this.messagingService.send(this.editMode && !this.cloneMode ? "editedCipher" : "addedCipher");
return true;
@@ -499,11 +514,13 @@ export class AddEditComponent implements OnInit, OnDestroy {
try {
this.deletePromise = this.deleteCipher();
await this.deletePromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem"),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t(
this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem",
),
});
this.onDeletedCipher.emit(this.cipher);
this.messagingService.send(
this.cipher.isDeleted ? "permanentlyDeletedCipher" : "deletedCipher",
@@ -523,7 +540,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
try {
this.restorePromise = this.restoreCipher();
await this.restorePromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("restoredItem"),
});
this.onRestoredCipher.emit(this.cipher);
this.messagingService.send("restoredCipher");
} catch (e) {
@@ -664,13 +685,17 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.checkPasswordPromise = null;
if (matches > 0) {
this.platformUtilsService.showToast(
"warning",
null,
this.i18nService.t("passwordExposed", matches.toString()),
);
this.toastService.showToast({
variant: "warning",
title: null,
message: this.i18nService.t("passwordExposed", matches.toString()),
});
} else {
this.platformUtilsService.showToast("success", null, this.i18nService.t("passwordSafe"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("passwordSafe"),
});
}
}
@@ -764,11 +789,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(value, copyOptions);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)),
);
this.toastService.showToast({
variant: "info",
title: null,
message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)),
});
if (typeI18nKey === "password") {
void this.eventCollectionService.collectMany(EventType.Cipher_ClientCopiedPassword, [
@@ -786,4 +811,26 @@ export class AddEditComponent implements OnInit, OnDestroy {
return true;
}
private async generateSshKey(showNotification: boolean = true) {
await firstValueFrom(this.sdkService.client$);
const sshKey = generate_ssh_key("Ed25519");
this.cipher.sshKey.privateKey = sshKey.private_key;
this.cipher.sshKey.publicKey = sshKey.public_key;
this.cipher.sshKey.keyFingerprint = sshKey.key_fingerprint;
if (showNotification) {
this.toastService.showToast({
variant: "success",
title: "",
message: this.i18nService.t("sshKeyGenerated"),
});
}
}
async typeChange() {
if (this.cipher.type === CipherType.SshKey) {
await this.generateSshKey();
}
}
}

View File

@@ -64,21 +64,21 @@ export class AttachmentsComponent implements OnInit {
const fileEl = document.getElementById("file") as HTMLInputElement;
const files = fileEl.files;
if (files == null || files.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("selectFile"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("selectFile"),
});
return;
}
if (files[0].size > 524288000) {
// 500 MB
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("maxFileSize"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("maxFileSize"),
});
return;
}
@@ -91,7 +91,11 @@ export class AttachmentsComponent implements OnInit {
this.cipher = await this.cipherDomain.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId),
);
this.platformUtilsService.showToast("success", null, this.i18nService.t("attachmentSaved"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("attachmentSaved"),
});
this.onUploadedAttachment.emit();
} catch (e) {
this.logService.error(e);
@@ -122,7 +126,11 @@ export class AttachmentsComponent implements OnInit {
try {
this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id);
await this.deletePromises[attachment.id];
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedAttachment"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("deletedAttachment"),
});
const i = this.cipher.attachments.indexOf(attachment);
if (i > -1) {
this.cipher.attachments.splice(i, 1);
@@ -142,11 +150,11 @@ export class AttachmentsComponent implements OnInit {
}
if (!this.canAccessAttachments) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("premiumRequired"),
this.i18nService.t("premiumRequiredDesc"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("premiumRequired"),
message: this.i18nService.t("premiumRequiredDesc"),
});
return;
}
@@ -171,7 +179,11 @@ export class AttachmentsComponent implements OnInit {
a.downloading = true;
const response = await fetch(new Request(url, { cache: "no-store" }));
if (response.status !== 200) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
a.downloading = false;
return;
}
@@ -192,8 +204,14 @@ export class AttachmentsComponent implements OnInit {
title: null,
message: this.i18nService.t("fileSavedToDevice"),
});
// FIXME: Remove when updating file. Eslint update
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
}
a.downloading = false;
@@ -209,7 +227,7 @@ export class AttachmentsComponent implements OnInit {
);
const canAccessPremium = await firstValueFrom(
this.billingAccountProfileStateService.hasPremiumFromAnySource$,
this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId),
);
this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null;
@@ -241,7 +259,11 @@ export class AttachmentsComponent implements OnInit {
a.downloading = true;
const response = await fetch(new Request(attachment.url, { cache: "no-store" }));
if (response.status !== 200) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
a.downloading = false;
return;
}
@@ -279,14 +301,20 @@ export class AttachmentsComponent implements OnInit {
}
}
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("attachmentSaved"),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("attachmentSaved"),
});
this.onReuploadedAttachment.emit();
// FIXME: Remove when updating file. Eslint update
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
}
a.downloading = false;

View File

@@ -2,7 +2,7 @@
// @ts-strict-ignore
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { Validators, FormBuilder } from "@angular/forms";
import { firstValueFrom } from "rxjs";
import { firstValueFrom, map } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -11,7 +11,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
@Directive()
@@ -27,6 +27,8 @@ export class FolderAddEditComponent implements OnInit {
deletePromise: Promise<any>;
protected componentName = "";
protected activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
formGroup = this.formBuilder.group({
name: ["", [Validators.required]],
});
@@ -41,6 +43,7 @@ export class FolderAddEditComponent implements OnInit {
protected logService: LogService,
protected dialogService: DialogService,
protected formBuilder: FormBuilder,
protected toastService: ToastService,
) {}
async ngOnInit() {
@@ -50,25 +53,25 @@ export class FolderAddEditComponent implements OnInit {
async submit(): Promise<boolean> {
this.folder.name = this.formGroup.controls.name.value;
if (this.folder.name == null || this.folder.name === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("nameRequired"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("nameRequired"),
});
return false;
}
try {
const activeAccountId = await firstValueFrom(this.accountService.activeAccount$);
const userKey = await this.keyService.getUserKeyWithLegacySupport(activeAccountId.id);
const activeUserId = await firstValueFrom(this.activeUserId$);
const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId);
const folder = await this.folderService.encrypt(this.folder, userKey);
this.formPromise = this.folderApiService.save(folder);
this.formPromise = this.folderApiService.save(folder, activeUserId);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder"),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder"),
});
this.onSavedFolder.emit(this.folder);
return true;
} catch (e) {
@@ -90,9 +93,14 @@ export class FolderAddEditComponent implements OnInit {
}
try {
this.deletePromise = this.folderApiService.delete(this.folder.id);
const activeUserId = await firstValueFrom(this.activeUserId$);
this.deletePromise = this.folderApiService.delete(this.folder.id, activeUserId);
await this.deletePromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedFolder"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("deletedFolder"),
});
this.onDeletedFolder.emit(this.folder);
} catch (e) {
this.logService.error(e);
@@ -107,8 +115,10 @@ export class FolderAddEditComponent implements OnInit {
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t("editFolder");
const folder = await this.folderService.get(this.folderId);
this.folder = await folder.decrypt();
const activeUserId = await firstValueFrom(this.activeUserId$);
this.folder = await firstValueFrom(
this.folderService.getDecrypted$(this.folderId, activeUserId),
);
} else {
this.title = this.i18nService.t("addFolder");
}

View File

@@ -4,13 +4,13 @@
[src]="data.image"
[appFallbackSrc]="data.fallbackImage"
*ngIf="data.imageEnabled && data.image"
class="tw-max-h-6 tw-max-w-6 tw-rounded-md"
class="tw-h-6 tw-w-6 tw-rounded-md"
alt=""
decoding="async"
loading="lazy"
/>
<i
class="tw-text-muted bwi bwi-lg {{ data.icon }}"
class="tw-w-6 tw-text-muted bwi bwi-lg {{ data.icon }}"
*ngIf="!data.imageEnabled || !data.image"
></i>
</ng-container>

View File

@@ -8,6 +8,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordHistoryView } from "@bitwarden/common/vault/models/view/password-history.view";
import { ToastService } from "@bitwarden/components";
@Directive()
export class PasswordHistoryComponent implements OnInit {
@@ -20,6 +21,7 @@ export class PasswordHistoryComponent implements OnInit {
protected i18nService: I18nService,
protected accountService: AccountService,
private win: Window,
private toastService: ToastService,
) {}
async ngOnInit() {
@@ -29,11 +31,11 @@ export class PasswordHistoryComponent implements OnInit {
copy(password: string) {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(password, copyOptions);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t("password")),
);
this.toastService.showToast({
variant: "info",
title: null,
message: this.i18nService.t("valueCopied", this.i18nService.t("password")),
});
}
protected async init() {

View File

@@ -1,9 +1,10 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { OnInit, Directive } from "@angular/core";
import { firstValueFrom, Observable } from "rxjs";
import { firstValueFrom, Observable, switchMap } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@@ -11,7 +12,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
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 { DialogService, SimpleDialogOptions } from "@bitwarden/components";
import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components";
@Directive()
export class PremiumComponent implements OnInit {
@@ -30,8 +31,14 @@ export class PremiumComponent implements OnInit {
protected dialogService: DialogService,
private environmentService: EnvironmentService,
billingAccountProfileStateService: BillingAccountProfileStateService,
private toastService: ToastService,
accountService: AccountService,
) {
this.isPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$;
this.isPremium$ = accountService.activeAccount$.pipe(
switchMap((account) =>
billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
),
);
}
async ngOnInit() {
@@ -45,7 +52,11 @@ export class PremiumComponent implements OnInit {
try {
this.refreshPromise = this.apiService.refreshIdentityToken();
await this.refreshPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("refreshComplete"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("refreshComplete"),
});
} catch (e) {
this.logService.error(e);
}

View File

@@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { BehaviorSubject, Subject, from, switchMap, takeUntil } from "rxjs";
import { BehaviorSubject, firstValueFrom, from, Subject, switchMap, takeUntil } from "rxjs";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
@@ -118,6 +118,12 @@ export class VaultItemsComponent implements OnInit, OnDestroy {
protected async doSearch(indexedCiphers?: CipherView[]) {
indexedCiphers = indexedCiphers ?? (await this.cipherService.getAllDecrypted());
const failedCiphers = await firstValueFrom(this.cipherService.failedToDecryptCiphers$);
if (failedCiphers != null && failedCiphers.length > 0) {
indexedCiphers = [...failedCiphers, ...indexedCiphers];
}
this.ciphers = await this.searchService.searchCiphers(
this.searchText,
[this.filter, this.deletedFilter],

View File

@@ -40,7 +40,7 @@ import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.v
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
import { DialogService } from "@bitwarden/components";
import { DialogService, ToastService } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
import { PasswordRepromptService } from "@bitwarden/vault";
@@ -65,6 +65,7 @@ export class ViewComponent implements OnDestroy, OnInit {
showPrivateKey: boolean;
canAccessPremium: boolean;
showPremiumRequiredTotp: boolean;
showUpgradeRequiredTotp: boolean;
totpCode: string;
totpCodeFormatted: string;
totpDash: number;
@@ -79,6 +80,8 @@ export class ViewComponent implements OnDestroy, OnInit {
private previousCipherId: string;
private passwordReprompted = false;
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
get fido2CredentialCreationDateValue(): string {
const dateCreated = this.i18nService.t("dateCreated");
const creationDate = this.datePipe.transform(
@@ -112,6 +115,7 @@ export class ViewComponent implements OnDestroy, OnInit {
protected datePipe: DatePipe,
protected accountService: AccountService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
protected toastService: ToastService,
private cipherAuthorizationService: CipherAuthorizationService,
) {}
@@ -141,32 +145,33 @@ export class ViewComponent implements OnDestroy, OnInit {
this.cleanUp();
const cipher = await this.cipherService.get(this.cipherId);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const activeUserId = await firstValueFrom(this.activeUserId$);
this.cipher = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
this.canAccessPremium = await firstValueFrom(
this.billingAccountProfileStateService.hasPremiumFromAnySource$,
this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId),
);
this.showPremiumRequiredTotp =
this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationUseTotp;
this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationId;
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [
this.collectionId as CollectionId,
]);
this.showUpgradeRequiredTotp =
this.cipher.login.totp && this.cipher.organizationId && !this.cipher.organizationUseTotp;
if (this.cipher.folderId) {
this.folder = await (
await firstValueFrom(this.folderService.folderViews$)
await firstValueFrom(this.folderService.folderViews$(activeUserId))
).find((f) => f.id == this.cipher.folderId);
}
if (
this.cipher.type === CipherType.Login &&
this.cipher.login.totp &&
(cipher.organizationUseTotp || this.canAccessPremium)
) {
const canGenerateTotp = this.cipher.organizationId
? this.cipher.organizationUseTotp
: this.canAccessPremium;
if (this.cipher.type === CipherType.Login && this.cipher.login.totp && canGenerateTotp) {
await this.totpUpdateCode();
const interval = this.totpService.getTimeInterval(this.cipher.login.totp);
await this.totpTick(interval);
@@ -242,11 +247,13 @@ export class ViewComponent implements OnDestroy, OnInit {
try {
await this.deleteCipher();
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem"),
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t(
this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem",
),
});
this.onDeletedCipher.emit(this.cipher);
} catch (e) {
this.logService.error(e);
@@ -262,7 +269,11 @@ export class ViewComponent implements OnDestroy, OnInit {
try {
await this.restoreCipher();
this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("restoredItem"),
});
this.onRestoredCipher.emit(this.cipher);
} catch (e) {
this.logService.error(e);
@@ -345,13 +356,17 @@ export class ViewComponent implements OnDestroy, OnInit {
const matches = await this.checkPasswordPromise;
if (matches > 0) {
this.platformUtilsService.showToast(
"warning",
null,
this.i18nService.t("passwordExposed", matches.toString()),
);
this.toastService.showToast({
variant: "warning",
title: null,
message: this.i18nService.t("passwordExposed", matches.toString()),
});
} else {
this.platformUtilsService.showToast("success", null, this.i18nService.t("passwordSafe"));
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("passwordSafe"),
});
}
}
@@ -381,11 +396,11 @@ export class ViewComponent implements OnDestroy, OnInit {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(value, copyOptions);
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)),
);
this.toastService.showToast({
variant: "info",
title: null,
message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)),
});
if (typeI18nKey === "password") {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
@@ -418,11 +433,11 @@ export class ViewComponent implements OnDestroy, OnInit {
}
if (this.cipher.organizationId == null && !this.canAccessPremium) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("premiumRequired"),
this.i18nService.t("premiumRequiredDesc"),
);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("premiumRequired"),
message: this.i18nService.t("premiumRequiredDesc"),
});
return;
}
@@ -446,7 +461,11 @@ export class ViewComponent implements OnDestroy, OnInit {
a.downloading = true;
const response = await fetch(new Request(url, { cache: "no-store" }));
if (response.status !== 200) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
a.downloading = false;
return;
}
@@ -462,8 +481,14 @@ export class ViewComponent implements OnDestroy, OnInit {
fileName: attachment.fileName,
blobData: decBuf,
});
// FIXME: Remove when updating file. Eslint update
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
}
a.downloading = false;

View File

@@ -9,6 +9,8 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { NewDeviceVerificationNoticeService } from "../../../../vault/src/services/new-device-verification-notice.service";
import { VaultProfileService } from "../services/vault-profile.service";

View File

@@ -9,6 +9,8 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { NewDeviceVerificationNoticeService } from "../../../../vault/src/services/new-device-verification-notice.service";
import { VaultProfileService } from "../services/vault-profile.service";

View File

@@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Injectable } from "@angular/core";
import { firstValueFrom, from, map, mergeMap, Observable } from "rxjs";
import { firstValueFrom, from, map, mergeMap, Observable, switchMap } from "rxjs";
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
import {
@@ -11,6 +11,7 @@ import {
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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
@@ -21,6 +22,8 @@ import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "../../abstractions/deprecated-vault-filter.service";
import { DynamicTreeNode } from "../models/dynamic-tree-node.model";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { COLLAPSED_GROUPINGS } from "./../../../../../common/src/vault/services/key-state/collapsed-groupings.state";
const NestingDelimiter = "/";
@@ -32,6 +35,8 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
private readonly collapsedGroupings$: Observable<Set<string>> =
this.collapsedGroupingsState.state$.pipe(map((c) => new Set(c)));
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
constructor(
protected organizationService: OrganizationService,
protected folderService: FolderService,
@@ -39,6 +44,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
protected collectionService: CollectionService,
protected policyService: PolicyService,
protected stateProvider: StateProvider,
protected accountService: AccountService,
) {}
async storeCollapsedFilterNodes(collapsedFilterNodes: Set<string>): Promise<void> {
@@ -81,7 +87,8 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
});
};
return this.folderService.folderViews$.pipe(
return this.activeUserId$.pipe(
switchMap((userId) => this.folderService.folderViews$(userId)),
mergeMap((folders) => from(transformation(folders))),
);
}
@@ -126,8 +133,9 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
}
async getFolderNested(id: string): Promise<TreeNode<FolderView>> {
const activeUserId = await firstValueFrom(this.activeUserId$);
const folders = await this.getAllFoldersNested(
await firstValueFrom(this.folderService.folderViews$),
await firstValueFrom(this.folderService.folderViews$(activeUserId)),
);
return ServiceUtils.getTreeNodeObjectFromList(folders, id) as TreeNode<FolderView>;
}

View File

@@ -1,5 +1,26 @@
{
"extends": "../shared/tsconfig.libs",
"extends": "../shared/tsconfig",
"compilerOptions": {
"paths": {
"@bitwarden/admin-console/common": ["../admin-console/src/common"],
"@bitwarden/angular/*": ["../angular/src/*"],
"@bitwarden/auth/angular": ["../auth/src/angular"],
"@bitwarden/auth/common": ["../auth/src/common"],
"@bitwarden/common/*": ["../common/src/*"],
"@bitwarden/components": ["../components/src"],
"@bitwarden/generator-components": ["../tools/generator/components/src"],
"@bitwarden/generator-core": ["../tools/generator/core/src"],
"@bitwarden/generator-history": ["../tools/generator/extensions/history/src"],
"@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"],
"@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"],
"@bitwarden/importer/core": ["../importer/src"],
"@bitwarden/key-management": ["../key-management/src"],
"@bitwarden/platform": ["../platform/src"],
"@bitwarden/ui-common": ["../ui/common/src"],
"@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"],
"@bitwarden/vault": ["../vault/src"]
}
},
"include": ["src", "spec"],
"exclude": ["node_modules", "dist"]
}