1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-12 14:34:02 +00:00

Merge branch 'main' into ps/extension-refresh

This commit is contained in:
Victoria League
2024-11-07 10:21:57 -05:00
committed by GitHub
22 changed files with 375 additions and 36 deletions

View File

@@ -16,13 +16,39 @@
"all" | i18n
}}</label>
</th>
<th bitCell [class]="showExtraColumn ? 'lg:tw-w-3/5' : 'tw-w-full'">{{ "name" | i18n }}</th>
<!-- Organization vault -->
<th
*ngIf="showAdminActions"
bitCell
bitSortable="name"
[fn]="sortByName"
[class]="showExtraColumn ? 'lg:tw-w-3/5' : 'tw-w-full'"
>
{{ "name" | i18n }}
</th>
<!-- Individual vault -->
<th
*ngIf="!showAdminActions"
bitCell
[class]="showExtraColumn ? 'lg:tw-w-3/5' : 'tw-w-full'"
>
{{ "name" | i18n }}
</th>
<th bitCell *ngIf="showOwner" class="tw-hidden tw-w-2/5 lg:tw-table-cell">
{{ "owner" | i18n }}
</th>
<th bitCell class="tw-w-2/5" *ngIf="showCollections">{{ "collections" | i18n }}</th>
<th bitCell class="tw-w-2/5" *ngIf="showGroups">{{ "groups" | i18n }}</th>
<th bitCell class="tw-w-2/5" *ngIf="showPermissionsColumn">
<th bitCell bitSortable="groups" [fn]="sortByGroups" class="tw-w-2/5" *ngIf="showGroups">
{{ "groups" | i18n }}
</th>
<th
bitCell
bitSortable="permissions"
default="desc"
[fn]="sortByPermissions"
class="tw-w-2/5"
*ngIf="showPermissionsColumn"
>
{{ "permission" | i18n }}
</th>
<th bitCell class="tw-w-12 tw-text-right">

View File

@@ -1,13 +1,17 @@
import { SelectionModel } from "@angular/cdk/collections";
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { CollectionView, Unassigned } from "@bitwarden/admin-console/common";
import { CollectionView, Unassigned, CollectionAdminView } from "@bitwarden/admin-console/common";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { TableDataSource } from "@bitwarden/components";
import { SortDirection, TableDataSource } from "@bitwarden/components";
import { GroupView } from "../../../admin-console/organizations/core";
import {
CollectionPermission,
convertToPermission,
} from "./../../../admin-console/organizations/shared/components/access-selector/access-selector.models";
import { VaultItem } from "./vault-item";
import { VaultItemEvent } from "./vault-item-event";
@@ -17,6 +21,8 @@ export const RowHeightClass = `tw-h-[75.5px]`;
const MaxSelectionCount = 500;
type ItemPermission = CollectionPermission | "NoAccess";
@Component({
selector: "app-vault-items",
templateUrl: "vault-items.component.html",
@@ -333,6 +339,119 @@ export class VaultItemsComponent {
return (canEditOrManageAllCiphers || this.allCiphersHaveEditAccess()) && collectionNotSelected;
}
/**
* Sorts VaultItems, grouping collections before ciphers, and sorting each group alphabetically by name.
*/
protected sortByName = (a: VaultItem, b: VaultItem, direction: SortDirection) => {
// Collections before ciphers
const collectionCompare = this.prioritizeCollections(a, b, direction);
if (collectionCompare !== 0) {
return collectionCompare;
}
return this.compareNames(a, b);
};
/**
* Sorts VaultItems based on group names
*/
protected sortByGroups = (a: VaultItem, b: VaultItem, direction: SortDirection) => {
if (
!(a.collection instanceof CollectionAdminView) &&
!(b.collection instanceof CollectionAdminView)
) {
return 0;
}
const getFirstGroupName = (collection: CollectionAdminView): string => {
if (collection.groups.length > 0) {
return collection.groups.map((group) => this.getGroupName(group.id) || "").sort()[0];
}
return null;
};
// Collections before ciphers
const collectionCompare = this.prioritizeCollections(a, b, direction);
if (collectionCompare !== 0) {
return collectionCompare;
}
const aGroupName = getFirstGroupName(a.collection as CollectionAdminView);
const bGroupName = getFirstGroupName(b.collection as CollectionAdminView);
// Collections with groups come before collections without groups.
// If a collection has no groups, getFirstGroupName returns null.
if (aGroupName === null) {
return 1;
}
if (bGroupName === null) {
return -1;
}
return aGroupName.localeCompare(bGroupName);
};
/**
* Sorts VaultItems based on their permissions, with higher permissions taking precedence.
* If permissions are equal, it falls back to sorting by name.
*/
protected sortByPermissions = (a: VaultItem, b: VaultItem, direction: SortDirection) => {
const getPermissionPriority = (item: VaultItem): number => {
const permission = item.collection
? this.getCollectionPermission(item.collection)
: this.getCipherPermission(item.cipher);
const priorityMap = {
[CollectionPermission.Manage]: 5,
[CollectionPermission.Edit]: 4,
[CollectionPermission.EditExceptPass]: 3,
[CollectionPermission.View]: 2,
[CollectionPermission.ViewExceptPass]: 1,
NoAccess: 0,
};
return priorityMap[permission] ?? -1;
};
// Collections before ciphers
const collectionCompare = this.prioritizeCollections(a, b, direction);
if (collectionCompare !== 0) {
return collectionCompare;
}
const priorityA = getPermissionPriority(a);
const priorityB = getPermissionPriority(b);
// Higher priority first
if (priorityA !== priorityB) {
return priorityA - priorityB;
}
return this.compareNames(a, b);
};
private compareNames(a: VaultItem, b: VaultItem): number {
const getName = (item: VaultItem) => item.collection?.name || item.cipher?.name;
return getName(a).localeCompare(getName(b));
}
/**
* Sorts VaultItems by prioritizing collections over ciphers.
* Collections are always placed before ciphers, regardless of the sorting direction.
*/
private prioritizeCollections(a: VaultItem, b: VaultItem, direction: SortDirection): number {
if (a.collection && !b.collection) {
return direction === "asc" ? -1 : 1;
}
if (!a.collection && b.collection) {
return direction === "asc" ? 1 : -1;
}
return 0;
}
private hasPersonalItems(): boolean {
return this.selection.selected.some(({ cipher }) => cipher?.organizationId === null);
}
@@ -346,4 +465,58 @@ export class VaultItemsComponent {
private getUniqueOrganizationIds(): Set<string> {
return new Set(this.selection.selected.flatMap((i) => i.cipher?.organizationId ?? []));
}
private getGroupName(groupId: string): string | undefined {
return this.allGroups.find((g) => g.id === groupId)?.name;
}
private getCollectionPermission(collection: CollectionView): ItemPermission {
const organization = this.allOrganizations.find((o) => o.id === collection.organizationId);
if (collection.id == Unassigned && organization?.canEditUnassignedCiphers) {
return CollectionPermission.Edit;
}
if (collection.assigned) {
return convertToPermission(collection);
}
return "NoAccess";
}
private getCipherPermission(cipher: CipherView): ItemPermission {
if (!cipher.organizationId || cipher.collectionIds.length === 0) {
return CollectionPermission.Manage;
}
const filteredCollections = this.allCollections?.filter((collection) => {
if (collection.assigned) {
return cipher.collectionIds.find((id) => {
if (collection.id === id) {
return collection;
}
});
}
});
if (filteredCollections?.length === 1) {
return convertToPermission(filteredCollections[0]);
}
if (filteredCollections?.length > 0) {
const permissions = filteredCollections.map((collection) => convertToPermission(collection));
const orderedPermissions = [
CollectionPermission.Manage,
CollectionPermission.Edit,
CollectionPermission.EditExceptPass,
CollectionPermission.View,
CollectionPermission.ViewExceptPass,
];
return orderedPermissions.find((perm) => permissions.includes(perm));
}
return "NoAccess";
}
}

View File

@@ -178,6 +178,7 @@ import { BulkEncryptServiceImplementation } from "@bitwarden/common/platform/ser
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
import { DefaultBroadcasterService } from "@bitwarden/common/platform/services/default-broadcaster.service";
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
import { DefaultServerSettingsService } from "@bitwarden/common/platform/services/default-server-settings.service";
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
@@ -1322,6 +1323,11 @@ const safeProviders: SafeProvider[] = [
InternalUserDecryptionOptionsServiceAbstraction,
],
}),
safeProvider({
provide: DefaultServerSettingsService,
useClass: DefaultServerSettingsService,
deps: [ConfigService],
}),
safeProvider({
provide: RegisterRouteService,
useClass: RegisterRouteService,

View File

@@ -4,13 +4,14 @@ import { RouterModule } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { RegisterRouteService } from "@bitwarden/auth/common";
import { DefaultServerSettingsService } from "@bitwarden/common/platform/services/default-server-settings.service";
import { LinkModule } from "@bitwarden/components";
@Component({
standalone: true,
imports: [CommonModule, JslibModule, LinkModule, RouterModule],
template: `
<div class="tw-text-center">
<div class="tw-text-center" *ngIf="!(isUserRegistrationDisabled$ | async)">
{{ "newToBitwarden" | i18n }}
<a bitLink [routerLink]="registerRoute$ | async">{{ "createAccount" | i18n }}</a>
</div>
@@ -18,7 +19,10 @@ import { LinkModule } from "@bitwarden/components";
})
export class LoginSecondaryContentComponent {
registerRouteService = inject(RegisterRouteService);
serverSettingsService = inject(DefaultServerSettingsService);
// TODO: remove when email verification flag is removed
protected registerRoute$ = this.registerRouteService.registerRoute$();
protected isUserRegistrationDisabled$ = this.serverSettingsService.isUserRegistrationDisabled$;
}

View File

@@ -11,7 +11,7 @@
-->
<form [bitSubmit]="submit" [formGroup]="formGroup">
<ng-container *ngIf="loginUiState === LoginUiState.EMAIL_ENTRY">
<div [ngClass]="{ 'tw-invisible tw-h-0': loginUiState !== LoginUiState.EMAIL_ENTRY }">
<!-- Email Address input -->
<bit-form-field>
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
@@ -82,9 +82,9 @@
</button>
</ng-container>
</div>
</ng-container>
</div>
<ng-container *ngIf="loginUiState === LoginUiState.MASTER_PASSWORD_ENTRY">
<div [ngClass]="{ 'tw-invisible tw-h-0': loginUiState !== LoginUiState.MASTER_PASSWORD_ENTRY }">
<!-- Master Password input -->
<bit-form-field class="!tw-mb-1">
<bit-label>{{ "masterPass" | i18n }}</bit-label>
@@ -140,5 +140,5 @@
</button>
</ng-container>
</div>
</ng-container>
</div>
</form>

View File

@@ -3,6 +3,7 @@ import { SemVer } from "semver";
import { FeatureFlag, FeatureFlagValueType } from "../../../enums/feature-flag.enum";
import { UserId } from "../../../types/guid";
import { ServerSettings } from "../../models/domain/server-settings";
import { Region } from "../environment.service";
import { ServerConfig } from "./server-config";
@@ -10,6 +11,8 @@ import { ServerConfig } from "./server-config";
export abstract class ConfigService {
/** The server config of the currently active user */
serverConfig$: Observable<ServerConfig | null>;
/** The server settings of the currently active user */
serverSettings$: Observable<ServerSettings | null>;
/** The cloud region of the currently active user */
cloudRegion$: Observable<Region>;
/**

View File

@@ -6,6 +6,7 @@ import {
ThirdPartyServerConfigData,
EnvironmentServerConfigData,
} from "../../models/data/server-config.data";
import { ServerSettings } from "../../models/domain/server-settings";
const dayInMilliseconds = 24 * 3600 * 1000;
@@ -16,6 +17,7 @@ export class ServerConfig {
environment?: EnvironmentServerConfigData;
utcDate: Date;
featureStates: { [key: string]: AllowedFeatureFlagTypes } = {};
settings: ServerSettings;
constructor(serverConfigData: ServerConfigData) {
this.version = serverConfigData.version;
@@ -24,6 +26,7 @@ export class ServerConfig {
this.utcDate = new Date(serverConfigData.utcDate);
this.environment = serverConfigData.environment;
this.featureStates = serverConfigData.featureStates;
this.settings = serverConfigData.settings;
if (this.server?.name == null && this.server?.url == null) {
this.server = null;

View File

@@ -16,6 +16,9 @@ describe("ServerConfigData", () => {
name: "test",
url: "https://test.com",
},
settings: {
disableUserRegistration: false,
},
environment: {
cloudRegion: Region.EU,
vault: "https://vault.com",

View File

@@ -2,6 +2,7 @@ import { Jsonify } from "type-fest";
import { AllowedFeatureFlagTypes } from "../../../enums/feature-flag.enum";
import { Region } from "../../abstractions/environment.service";
import { ServerSettings } from "../domain/server-settings";
import {
ServerConfigResponse,
ThirdPartyServerConfigResponse,
@@ -15,6 +16,7 @@ export class ServerConfigData {
environment?: EnvironmentServerConfigData;
utcDate: string;
featureStates: { [key: string]: AllowedFeatureFlagTypes } = {};
settings: ServerSettings;
constructor(serverConfigResponse: Partial<ServerConfigResponse>) {
this.version = serverConfigResponse?.version;
@@ -27,6 +29,7 @@ export class ServerConfigData {
? new EnvironmentServerConfigData(serverConfigResponse.environment)
: null;
this.featureStates = serverConfigResponse?.featureStates;
this.settings = new ServerSettings(serverConfigResponse.settings);
}
static fromJSON(obj: Jsonify<ServerConfigData>): ServerConfigData {

View File

@@ -0,0 +1,20 @@
import { ServerSettings } from "./server-settings";
describe("ServerSettings", () => {
describe("disableUserRegistration", () => {
it("defaults disableUserRegistration to false", () => {
const settings = new ServerSettings();
expect(settings.disableUserRegistration).toBe(false);
});
it("sets disableUserRegistration to true when provided", () => {
const settings = new ServerSettings({ disableUserRegistration: true });
expect(settings.disableUserRegistration).toBe(true);
});
it("sets disableUserRegistration to false when provided", () => {
const settings = new ServerSettings({ disableUserRegistration: false });
expect(settings.disableUserRegistration).toBe(false);
});
});
});

View File

@@ -0,0 +1,7 @@
export class ServerSettings {
disableUserRegistration: boolean;
constructor(data?: ServerSettings) {
this.disableUserRegistration = data?.disableUserRegistration ?? false;
}
}

View File

@@ -1,6 +1,7 @@
import { AllowedFeatureFlagTypes } from "../../../enums/feature-flag.enum";
import { BaseResponse } from "../../../models/response/base.response";
import { Region } from "../../abstractions/environment.service";
import { ServerSettings } from "../domain/server-settings";
export class ServerConfigResponse extends BaseResponse {
version: string;
@@ -8,6 +9,7 @@ export class ServerConfigResponse extends BaseResponse {
server: ThirdPartyServerConfigResponse;
environment: EnvironmentServerConfigResponse;
featureStates: { [key: string]: AllowedFeatureFlagTypes } = {};
settings: ServerSettings;
constructor(response: any) {
super(response);
@@ -21,6 +23,7 @@ export class ServerConfigResponse extends BaseResponse {
this.server = new ThirdPartyServerConfigResponse(this.getResponseProperty("Server"));
this.environment = new EnvironmentServerConfigResponse(this.getResponseProperty("Environment"));
this.featureStates = this.getResponseProperty("FeatureStates");
this.settings = new ServerSettings(this.getResponseProperty("Settings"));
}
}

View File

@@ -28,6 +28,7 @@ import { Environment, EnvironmentService, Region } from "../../abstractions/envi
import { LogService } from "../../abstractions/log.service";
import { devFlagEnabled, devFlagValue } from "../../misc/flags";
import { ServerConfigData } from "../../models/data/server-config.data";
import { ServerSettings } from "../../models/domain/server-settings";
import { CONFIG_DISK, KeyDefinition, StateProvider, UserKeyDefinition } from "../../state";
export const RETRIEVAL_INTERVAL = devFlagEnabled("configRetrievalIntervalMs")
@@ -57,6 +58,8 @@ export class DefaultConfigService implements ConfigService {
serverConfig$: Observable<ServerConfig>;
serverSettings$: Observable<ServerSettings>;
cloudRegion$: Observable<Region>;
constructor(
@@ -111,6 +114,10 @@ export class DefaultConfigService implements ConfigService {
this.cloudRegion$ = this.serverConfig$.pipe(
map((config) => config?.environment?.cloudRegion ?? Region.US),
);
this.serverSettings$ = this.serverConfig$.pipe(
map((config) => config?.settings ?? new ServerSettings()),
);
}
getFeatureFlag$<Flag extends FeatureFlag>(key: Flag) {

View File

@@ -0,0 +1,47 @@
import { of } from "rxjs";
import { ConfigService } from "../abstractions/config/config.service";
import { ServerSettings } from "../models/domain/server-settings";
import { DefaultServerSettingsService } from "./default-server-settings.service";
describe("DefaultServerSettingsService", () => {
let service: DefaultServerSettingsService;
let configServiceMock: { serverSettings$: any };
beforeEach(() => {
configServiceMock = { serverSettings$: of() };
service = new DefaultServerSettingsService(configServiceMock as ConfigService);
});
describe("getSettings$", () => {
it("returns server settings", () => {
const mockSettings = new ServerSettings({ disableUserRegistration: true });
configServiceMock.serverSettings$ = of(mockSettings);
service.getSettings$().subscribe((settings) => {
expect(settings).toEqual(mockSettings);
});
});
});
describe("isUserRegistrationDisabled$", () => {
it("returns true when user registration is disabled", () => {
const mockSettings = new ServerSettings({ disableUserRegistration: true });
configServiceMock.serverSettings$ = of(mockSettings);
service.isUserRegistrationDisabled$.subscribe((isDisabled: boolean) => {
expect(isDisabled).toBe(true);
});
});
it("returns false when user registration is enabled", () => {
const mockSettings = new ServerSettings({ disableUserRegistration: false });
configServiceMock.serverSettings$ = of(mockSettings);
service.isUserRegistrationDisabled$.subscribe((isDisabled: boolean) => {
expect(isDisabled).toBe(false);
});
});
});
});

View File

@@ -0,0 +1,19 @@
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
import { ConfigService } from "../abstractions/config/config.service";
import { ServerSettings } from "../models/domain/server-settings";
export class DefaultServerSettingsService {
constructor(private configService: ConfigService) {}
getSettings$(): Observable<ServerSettings> {
return this.configService.serverSettings$;
}
get isUserRegistrationDisabled$(): Observable<boolean> {
return this.getSettings$().pipe(
map((settings: ServerSettings) => settings.disableUserRegistration),
);
}
}

View File

@@ -1,7 +1,7 @@
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import { Component, HostBinding, Input, OnInit } from "@angular/core";
import type { SortFn } from "./table-data-source";
import type { SortDirection, SortFn } from "./table-data-source";
import { TableComponent } from "./table.component";
@Component({
@@ -19,12 +19,16 @@ export class SortableComponent implements OnInit {
*/
@Input() bitSortable: string;
private _default: boolean;
private _default: SortDirection | boolean = false;
/**
* Mark the column as the default sort column
*/
@Input() set default(value: boolean | "") {
this._default = coerceBooleanProperty(value);
@Input() set default(value: SortDirection | boolean | "") {
if (value === "desc" || value === "asc") {
this._default = value;
} else {
this._default = coerceBooleanProperty(value) ? "asc" : false;
}
}
/**
@@ -32,6 +36,11 @@ export class SortableComponent implements OnInit {
*
* @example
* fn = (a, b) => a.name.localeCompare(b.name)
*
* fn = (a, b, direction) => {
* const result = a.name.localeCompare(b.name)
* return direction === 'asc' ? result : -result;
* }
*/
@Input() fn: SortFn;
@@ -52,8 +61,18 @@ export class SortableComponent implements OnInit {
protected setActive() {
if (this.table.dataSource) {
const direction = this.isActive && this.direction === "asc" ? "desc" : "asc";
this.table.dataSource.sort = { column: this.bitSortable, direction: direction, fn: this.fn };
const defaultDirection = this._default === "desc" ? "desc" : "asc";
const direction = this.isActive
? this.direction === "asc"
? "desc"
: "asc"
: defaultDirection;
this.table.dataSource.sort = {
column: this.bitSortable,
direction: direction,
fn: this.fn,
};
}
}

View File

@@ -3,7 +3,7 @@ import { DataSource } from "@angular/cdk/collections";
import { BehaviorSubject, combineLatest, map, Observable, Subscription } from "rxjs";
export type SortDirection = "asc" | "desc";
export type SortFn = (a: any, b: any) => number;
export type SortFn = (a: any, b: any, direction?: SortDirection) => number;
export type Sort = {
column?: string;
direction: SortDirection;
@@ -166,7 +166,7 @@ export class TableDataSource<T> extends DataSource<T> {
return data.sort((a, b) => {
// If a custom sort function is provided, use it instead of the default.
if (sort.fn) {
return sort.fn(a, b) * directionModifier;
return sort.fn(a, b, sort.direction) * directionModifier;
}
let valueA = this.sortingDataAccessor(a, column);

View File

@@ -105,7 +105,7 @@ within the `ng-template`which provides access to the rows using `let-rows$`.
We provide a simple component for displaying sortable column headers. The `bitSortable` component
wires up to the `TableDataSource` and will automatically sort the data when clicked and display an
indicator for which column is currently sorted. The dafault sorting can be specified by setting the
indicator for which column is currently sorted. The default sorting can be specified by setting the
`default`.
```html
@@ -113,10 +113,23 @@ indicator for which column is currently sorted. The dafault sorting can be speci
<th bitCell bitSortable="name" default>Name</th>
```
For default sorting in descending order, set default="desc"
```html
<th bitCell bitSortable="name" default="desc">Name</th>
```
It's also possible to define a custom sorting function by setting the `fn` input.
```ts
// Basic sort function
const sortFn = (a: T, b: T) => (a.id > b.id ? 1 : -1);
// Direction aware sort function
const sortByName = (a: T, b: T, direction?: SortDirection) => {
const result = a.name.localeCompare(b.name);
return direction === "asc" ? result : -result;
};
```
### Filtering

View File

@@ -12,11 +12,6 @@ import {
import { completeOnAccountSwitch } from "./util";
/** Splits an email into a username, subaddress, and domain named group.
* Subaddress is optional.
*/
export const DOMAIN_PARSER = new RegExp("[^@]+@(?<domain>.+)");
/** Options group for catchall emails */
@Component({
selector: "tools-catchall-settings",

View File

@@ -39,14 +39,12 @@
</div>
</bit-card>
<tools-password-settings
#passwordSettings
class="tw-mt-6"
*ngIf="(showAlgorithm$ | async)?.id === 'password'"
[userId]="userId$ | async"
(onUpdated)="generate('password settings')"
/>
<tools-passphrase-settings
#passphraseSettings
class="tw-mt-6"
*ngIf="(showAlgorithm$ | async)?.id === 'passphrase'"
[userId]="userId$ | async"
@@ -84,25 +82,21 @@
</bit-form-field>
</form>
<tools-catchall-settings
#catchallSettings
*ngIf="(showAlgorithm$ | async)?.id === 'catchall'"
[userId]="userId$ | async"
(onUpdated)="generate('catchall settings')"
/>
<tools-forwarder-settings
#forwarderSettings
*ngIf="!!(forwarderId$ | async)"
[forwarder]="forwarderId$ | async"
[userId]="this.userId$ | async"
/>
<tools-subaddress-settings
#subaddressSettings
*ngIf="(showAlgorithm$ | async)?.id === 'subaddress'"
[userId]="userId$ | async"
(onUpdated)="generate('subaddress settings')"
/>
<tools-username-settings
#usernameSettings
*ngIf="(showAlgorithm$ | async)?.id === 'username'"
[userId]="userId$ | async"
(onUpdated)="generate('username settings')"

View File

@@ -37,7 +37,6 @@
</div>
</bit-card>
<tools-password-settings
#passwordSettings
class="tw-mt-6"
*ngIf="(algorithm$ | async)?.id === 'password'"
[userId]="this.userId$ | async"
@@ -45,7 +44,6 @@
(onUpdated)="generate('password settings')"
/>
<tools-passphrase-settings
#passphraseSettings
class="tw-mt-6"
*ngIf="(algorithm$ | async)?.id === 'passphrase'"
[userId]="this.userId$ | async"

View File

@@ -59,25 +59,21 @@
</bit-form-field>
</form>
<tools-catchall-settings
#catchallSettings
*ngIf="(algorithm$ | async)?.id === 'catchall'"
[userId]="this.userId$ | async"
(onUpdated)="generate('catchall settings')"
/>
<tools-forwarder-settings
#forwarderSettings
*ngIf="!!(forwarderId$ | async)"
[forwarder]="forwarderId$ | async"
[userId]="this.userId$ | async"
/>
<tools-subaddress-settings
#subaddressSettings
*ngIf="(algorithm$ | async)?.id === 'subaddress'"
[userId]="this.userId$ | async"
(onUpdated)="generate('subaddress settings')"
/>
<tools-username-settings
#usernameSettings
*ngIf="(algorithm$ | async)?.id === 'username'"
[userId]="this.userId$ | async"
(onUpdated)="generate('username settings')"