mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 18:23:31 +00:00
fixed merge conflict
This commit is contained in:
17
apps/web/src/app/admin-console/icons/devices.ts
Normal file
17
apps/web/src/app/admin-console/icons/devices.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { svgIcon } from "@bitwarden/components";
|
||||
|
||||
export const Devices = svgIcon`
|
||||
<svg width="201" height="201" viewBox="0 0 201 201" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity=".49">
|
||||
<path class="tw-fill-secondary-500" fill-rule="evenodd" clip-rule="evenodd" d="M34.3628 82.0889H10.3628C7.04908 82.0889 4.36279 84.7752 4.36279 88.0889V148.089C4.36279 151.403 7.04909 154.089 10.3628 154.089H34.3628C37.6765 154.089 40.3628 151.403 40.3628 148.089V88.0889C40.3628 84.7752 37.6765 82.0889 34.3628 82.0889ZM10.3628 78.0889C4.83995 78.0889 0.362793 82.566 0.362793 88.0889V148.089C0.362793 153.612 4.83995 158.089 10.3628 158.089H34.3628C39.8856 158.089 44.3628 153.612 44.3628 148.089V88.0889C44.3628 82.566 39.8856 78.0889 34.3628 78.0889H10.3628Z" />
|
||||
<path class="tw-fill-secondary-500" fill-rule="evenodd" clip-rule="evenodd" d="M20.7329 86.8979C20.7329 86.3457 21.1806 85.8979 21.7329 85.8979H22.975C23.5273 85.8979 23.975 86.3457 23.975 86.8979C23.975 87.4502 23.5273 87.8979 22.975 87.8979H21.7329C21.1806 87.8979 20.7329 87.4502 20.7329 86.8979Z" />
|
||||
<path class="tw-fill-secondary-500" fill-rule="evenodd" clip-rule="evenodd" d="M68.3628 159.089C68.3628 158.537 68.8105 158.089 69.3628 158.089H127.363C127.915 158.089 128.363 158.537 128.363 159.089C128.363 159.641 127.915 160.089 127.363 160.089H69.3628C68.8105 160.089 68.3628 159.641 68.3628 159.089Z" />
|
||||
<path class="tw-fill-secondary-500" fill-rule="evenodd" clip-rule="evenodd" d="M88.103 159.089V141.325H90.103V159.089H88.103Z" />
|
||||
<path class="tw-fill-secondary-500" fill-rule="evenodd" clip-rule="evenodd" d="M108.073 159.089V141.325H110.073V159.089H108.073Z" />
|
||||
<path class="tw-fill-secondary-500" fill-rule="evenodd" clip-rule="evenodd" d="M27.3628 64.0889C27.3628 56.3569 33.6308 50.0889 41.3628 50.0889H157.363C165.095 50.0889 171.363 56.3569 171.363 64.0889V70.0889H167.363V64.0889C167.363 58.566 162.886 54.0889 157.363 54.0889H41.3628C35.8399 54.0889 31.3628 58.566 31.3628 64.0889V80.0889H27.3628V64.0889ZM42.3628 138.089H127.363V142.089H42.3628V138.089Z" />
|
||||
<path class="tw-fill-secondary-500" fill-rule="evenodd" clip-rule="evenodd" d="M35.3628 65.0889C35.3628 61.2229 38.4968 58.0889 42.3628 58.0889H156.363C160.229 58.0889 163.363 61.2229 163.363 65.0889V70.0889H161.363V65.0889C161.363 62.3274 159.124 60.0889 156.363 60.0889H42.3628C39.6014 60.0889 37.3628 62.3274 37.3628 65.0889V80.0889H35.3628V65.0889ZM42.3628 132.089H127.363V134.089H42.3628V132.089Z" />
|
||||
<path class="tw-fill-secondary-500" fill-rule="evenodd" clip-rule="evenodd" d="M125.363 78.0889C125.363 72.566 129.84 68.0889 135.363 68.0889H188.363C193.886 68.0889 198.363 72.566 198.363 78.0889V158.089C198.363 163.612 193.886 168.089 188.363 168.089H135.363C129.84 168.089 125.363 163.612 125.363 158.089V78.0889ZM135.363 72.0889C132.049 72.0889 129.363 74.7752 129.363 78.0889V158.089C129.363 161.403 132.049 164.089 135.363 164.089H188.363C191.676 164.089 194.363 161.403 194.363 158.089V78.0889C194.363 74.7752 191.677 72.0889 188.363 72.0889H135.363Z" />
|
||||
<path class="tw-fill-secondary-500" d="M164.363 159.089C164.363 160.193 163.467 161.089 162.363 161.089C161.258 161.089 160.363 160.193 160.363 159.089C160.363 157.984 161.258 157.089 162.363 157.089C163.467 157.089 164.363 157.984 164.363 159.089Z" />
|
||||
</g>
|
||||
</svg>
|
||||
`;
|
||||
1
apps/web/src/app/admin-console/icons/index.ts
Normal file
1
apps/web/src/app/admin-console/icons/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./devices";
|
||||
@@ -83,6 +83,7 @@ export class UserAdminService {
|
||||
}));
|
||||
view.groups = u.groups;
|
||||
view.accessSecretsManager = u.accessSecretsManager;
|
||||
view.hasMasterPassword = u.hasMasterPassword;
|
||||
|
||||
return view;
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ export class OrganizationUserAdminView {
|
||||
accessAll: boolean;
|
||||
permissions: PermissionsApi;
|
||||
resetPasswordEnrolled: boolean;
|
||||
hasMasterPassword: boolean;
|
||||
|
||||
collections: CollectionAccessSelectionView[] = [];
|
||||
groups: string[] = [];
|
||||
|
||||
@@ -20,6 +20,7 @@ export class OrganizationUserView {
|
||||
avatarColor: string;
|
||||
twoFactorEnabled: boolean;
|
||||
usesKeyConnector: boolean;
|
||||
hasMasterPassword: boolean;
|
||||
|
||||
collections: CollectionAccessSelectionView[] = [];
|
||||
groups: string[] = [];
|
||||
|
||||
@@ -28,10 +28,7 @@ export class UserConfirmComponent implements OnInit {
|
||||
async ngOnInit() {
|
||||
try {
|
||||
if (this.publicKey != null) {
|
||||
const fingerprint = await this.cryptoService.getFingerprint(
|
||||
this.userId,
|
||||
this.publicKey.buffer
|
||||
);
|
||||
const fingerprint = await this.cryptoService.getFingerprint(this.userId, this.publicKey);
|
||||
if (fingerprint != null) {
|
||||
this.fingerprint = fingerprint.join("-");
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export class BulkConfirmComponent implements OnInit {
|
||||
|
||||
for (const entry of response.data) {
|
||||
const publicKey = Utils.fromB64ToArray(entry.key);
|
||||
const fingerprint = await this.cryptoService.getFingerprint(entry.userId, publicKey.buffer);
|
||||
const fingerprint = await this.cryptoService.getFingerprint(entry.userId, publicKey);
|
||||
if (fingerprint != null) {
|
||||
this.publicKeys.set(entry.id, publicKey);
|
||||
this.fingerprints.set(entry.id, fingerprint.join("-"));
|
||||
@@ -67,7 +67,7 @@ export class BulkConfirmComponent implements OnInit {
|
||||
if (publicKey == null) {
|
||||
continue;
|
||||
}
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(key.key, publicKey.buffer);
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(key.key, publicKey);
|
||||
userIdsWithKeys.push({
|
||||
id: user.id,
|
||||
key: encryptedKey.encryptedString,
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<bit-dialog dialogSize="large">
|
||||
<span bitDialogTitle>{{ "activateSecretsManager" | i18n }}</span>
|
||||
<span bitDialogContent>
|
||||
<p>{{ "bulkEnableSecretsManagerDescription" | i18n }}</p>
|
||||
<bit-table [dataSource]="dataSource">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell>{{ "member" | i18n }}</th>
|
||||
<th bitCell>{{ "role" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body let-rows$>
|
||||
<tr bitRow *ngFor="let u of rows$ | async">
|
||||
<td bitCell>
|
||||
<div class="tw-flex tw-items-center">
|
||||
<bit-avatar
|
||||
size="small"
|
||||
[text]="u | userName"
|
||||
[id]="u.userId"
|
||||
[color]="u.avatarColor"
|
||||
class="tw-mr-3"
|
||||
></bit-avatar>
|
||||
<div class="tw-flex tw-flex-col">
|
||||
<div>
|
||||
{{ u | userName }}
|
||||
</div>
|
||||
<div class="tw-text-sm tw-text-muted" *ngIf="u.name">
|
||||
{{ u.email }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td bitCell>{{ u.type | userType }}</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button type="button" bitButton buttonType="primary" [bitAction]="submit">
|
||||
{{ "activateAccess" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="secondary" bitDialogClose>
|
||||
{{ "close" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
@@ -0,0 +1,53 @@
|
||||
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
|
||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { TableDataSource } from "@bitwarden/components";
|
||||
|
||||
import { OrganizationUserView } from "../../../core";
|
||||
|
||||
export type BulkEnableSecretsManagerDialogData = {
|
||||
orgId: string;
|
||||
users: OrganizationUserView[];
|
||||
};
|
||||
|
||||
@Component({
|
||||
templateUrl: `bulk-enable-sm-dialog.component.html`,
|
||||
})
|
||||
export class BulkEnableSecretsManagerDialogComponent implements OnInit {
|
||||
protected dataSource = new TableDataSource<OrganizationUserView>();
|
||||
constructor(
|
||||
public dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) private data: BulkEnableSecretsManagerDialogData,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.dataSource.data = this.data.users;
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
await this.organizationUserService.putOrganizationUserBulkEnableSecretsManager(
|
||||
this.data.orgId,
|
||||
this.dataSource.data.map((u) => u.id)
|
||||
);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("activatedAccessToSecretsManager")
|
||||
);
|
||||
this.dialogRef.close();
|
||||
};
|
||||
|
||||
static open(dialogService: DialogServiceAbstraction, data: BulkEnableSecretsManagerDialogData) {
|
||||
return dialogService.open<unknown, BulkEnableSecretsManagerDialogData>(
|
||||
BulkEnableSecretsManagerDialogComponent,
|
||||
{ data }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -23,12 +23,16 @@
|
||||
</app-callout>
|
||||
<ng-container *ngIf="!done">
|
||||
<app-callout type="warning" *ngIf="users.length > 0 && !error">
|
||||
{{ removeUsersWarning }}
|
||||
<p>{{ removeUsersWarning }}</p>
|
||||
<p *ngIf="this.showNoMasterPasswordWarning">
|
||||
{{ "removeMembersWithoutMasterPasswordWarning" | i18n }}
|
||||
</p>
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">{{ "user" | i18n }}</th>
|
||||
<th *ngIf="this.showNoMasterPasswordWarning">{{ "details" | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr *ngFor="let user of users">
|
||||
@@ -39,6 +43,15 @@
|
||||
{{ user.email }}
|
||||
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
|
||||
</td>
|
||||
<td *ngIf="this.showNoMasterPasswordWarning">
|
||||
<span class="text-muted d-block tw-lowercase">
|
||||
<ng-container *ngIf="user.hasMasterPassword === true"> - </ng-container>
|
||||
<ng-container *ngIf="user.hasMasterPassword === false">
|
||||
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{ "noMasterPassword" | i18n }}
|
||||
</ng-container>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</ng-container>
|
||||
|
||||
@@ -12,13 +12,23 @@ import { BulkUserDetails } from "./bulk-status.component";
|
||||
})
|
||||
export class BulkRemoveComponent {
|
||||
@Input() organizationId: string;
|
||||
@Input() users: BulkUserDetails[];
|
||||
@Input() set users(value: BulkUserDetails[]) {
|
||||
this._users = value;
|
||||
this.showNoMasterPasswordWarning = this._users.some((u) => u.hasMasterPassword === false);
|
||||
}
|
||||
|
||||
get users(): BulkUserDetails[] {
|
||||
return this._users;
|
||||
}
|
||||
|
||||
private _users: BulkUserDetails[];
|
||||
|
||||
statuses: Map<string, string> = new Map();
|
||||
|
||||
loading = false;
|
||||
done = false;
|
||||
error: string;
|
||||
showNoMasterPasswordWarning = false;
|
||||
|
||||
constructor(
|
||||
protected apiService: ApiService,
|
||||
|
||||
@@ -23,12 +23,16 @@
|
||||
</app-callout>
|
||||
<ng-container *ngIf="!done">
|
||||
<app-callout type="warning" *ngIf="users.length > 0 && !error && isRevoking">
|
||||
{{ "revokeUsersWarning" | i18n }}
|
||||
<p>{{ "revokeUsersWarning" | i18n }}</p>
|
||||
<p *ngIf="this.showNoMasterPasswordWarning">
|
||||
{{ "removeMembersWithoutMasterPasswordWarning" | i18n }}
|
||||
</p>
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">{{ "user" | i18n }}</th>
|
||||
<th *ngIf="this.showNoMasterPasswordWarning">{{ "details" | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr *ngFor="let user of users">
|
||||
@@ -39,6 +43,15 @@
|
||||
{{ user.email }}
|
||||
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
|
||||
</td>
|
||||
<td *ngIf="this.showNoMasterPasswordWarning">
|
||||
<span class="text-muted d-block tw-lowercase">
|
||||
<ng-container *ngIf="user.hasMasterPassword === true"> - </ng-container>
|
||||
<ng-container *ngIf="user.hasMasterPassword === false">
|
||||
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{ "noMasterPassword" | i18n }}
|
||||
</ng-container>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</ng-container>
|
||||
|
||||
@@ -20,6 +20,7 @@ export class BulkRestoreRevokeComponent {
|
||||
loading = false;
|
||||
done = false;
|
||||
error: string;
|
||||
showNoMasterPasswordWarning = false;
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
@@ -29,6 +30,7 @@ export class BulkRestoreRevokeComponent {
|
||||
this.isRevoking = config.data.isRevoking;
|
||||
this.organizationId = config.data.organizationId;
|
||||
this.users = config.data.users;
|
||||
this.showNoMasterPasswordWarning = this.users.some((u) => u.hasMasterPassword === false);
|
||||
}
|
||||
|
||||
get bulkTitle() {
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface BulkUserDetails {
|
||||
name: string;
|
||||
email: string;
|
||||
status: OrganizationUserStatusType | ProviderUserStatusType;
|
||||
hasMasterPassword?: boolean;
|
||||
}
|
||||
|
||||
type BulkStatusEntry = {
|
||||
|
||||
@@ -246,7 +246,7 @@
|
||||
(change)="handleDependentPermissions()"
|
||||
/>
|
||||
<label class="!tw-font-normal" for="manageResetPassword">
|
||||
{{ "manageResetPassword" | i18n }}
|
||||
{{ "manageAccountRecovery" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -255,7 +255,7 @@
|
||||
</ng-container>
|
||||
<ng-container *ngIf="canUseSecretsManager">
|
||||
<h3 class="mt-4">
|
||||
{{ "secretsManagerBeta" | i18n }}
|
||||
{{ "secretsManager" | i18n }}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
@@ -265,11 +265,11 @@
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</h3>
|
||||
<p class="tw-text-muted">{{ "secretsManagerBetaDesc" | i18n }}</p>
|
||||
<p class="tw-text-muted">{{ "secretsManagerAccessDescription" | i18n }}</p>
|
||||
<bit-form-control>
|
||||
<input type="checkbox" bitCheckbox formControlName="accessSecretsManager" />
|
||||
<bit-label>
|
||||
{{ "userAccessSecretsManager" | i18n }}
|
||||
{{ "userAccessSecretsManagerGA" | i18n }}
|
||||
</bit-label>
|
||||
</bit-form-control>
|
||||
</ng-container>
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
} from "../../../shared/components/access-selector";
|
||||
|
||||
import { commaSeparatedEmails } from "./validators/comma-separated-emails.validator";
|
||||
import { freeOrgSeatLimitReachedValidator } from "./validators/free-org-inv-limit-reached.validator";
|
||||
|
||||
export enum MemberDialogTab {
|
||||
Role = 0,
|
||||
@@ -46,6 +47,7 @@ export interface MemberDialogParams {
|
||||
name: string;
|
||||
organizationId: string;
|
||||
organizationUserId: string;
|
||||
allOrganizationUserEmails: string[];
|
||||
usesKeyConnector: boolean;
|
||||
initialTab?: MemberDialogTab;
|
||||
}
|
||||
@@ -72,13 +74,14 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
canUseCustomPermissions: boolean;
|
||||
PermissionMode = PermissionMode;
|
||||
canUseSecretsManager: boolean;
|
||||
showNoMasterPasswordWarning = false;
|
||||
|
||||
protected organization: Organization;
|
||||
protected collectionAccessItems: AccessItemView[] = [];
|
||||
protected groupAccessItems: AccessItemView[] = [];
|
||||
protected tabIndex: MemberDialogTab;
|
||||
protected formGroup = this.formBuilder.group({
|
||||
emails: ["", [Validators.required, commaSeparatedEmails]],
|
||||
emails: ["", { updateOn: "blur" }],
|
||||
type: OrganizationUserType.User,
|
||||
externalId: this.formBuilder.control({ value: "", disabled: true }),
|
||||
accessAllCollections: false,
|
||||
@@ -166,6 +169,20 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
this.canUseCustomPermissions = organization.useCustomPermissions;
|
||||
this.canUseSecretsManager = organization.useSecretsManager && flagEnabled("secretsManager");
|
||||
|
||||
const emailsControlValidators = [
|
||||
Validators.required,
|
||||
commaSeparatedEmails,
|
||||
freeOrgSeatLimitReachedValidator(
|
||||
this.organization,
|
||||
this.params.allOrganizationUserEmails,
|
||||
this.i18nService.t("subscriptionFreePlan", organization.seats)
|
||||
),
|
||||
];
|
||||
|
||||
const emailsControl = this.formGroup.get("emails");
|
||||
emailsControl.setValidators(emailsControlValidators);
|
||||
emailsControl.updateValueAndValidity();
|
||||
|
||||
this.collectionAccessItems = [].concat(
|
||||
collections.map((c) => mapCollectionToAccessItemView(c))
|
||||
);
|
||||
@@ -179,6 +196,9 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
throw new Error("Could not find user to edit.");
|
||||
}
|
||||
this.isRevoked = userDetails.status === OrganizationUserStatusType.Revoked;
|
||||
this.showNoMasterPasswordWarning =
|
||||
userDetails.status > OrganizationUserStatusType.Invited &&
|
||||
userDetails.hasMasterPassword === false;
|
||||
const assignedCollectionsPermissions = {
|
||||
editAssignedCollections: userDetails.permissions.editAssignedCollections,
|
||||
deleteAssignedCollections: userDetails.permissions.deleteAssignedCollections,
|
||||
@@ -282,7 +302,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
handleDependentPermissions() {
|
||||
// Manage Password Reset must have Manage Users enabled
|
||||
// Manage Password Reset (Account Recovery) must have Manage Users enabled
|
||||
if (
|
||||
this.permissionsGroup.value.manageResetPassword &&
|
||||
!this.permissionsGroup.value.manageUsers
|
||||
@@ -292,7 +312,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
this.platformUtilsService.showToast(
|
||||
"info",
|
||||
null,
|
||||
this.i18nService.t("resetPasswordManageUsers")
|
||||
this.i18nService.t("accountRecoveryManageUsers")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -366,7 +386,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
? "removeUserConfirmationKeyConnector"
|
||||
: "removeOrgUserConfirmation";
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
let confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "removeUserIdAccess", placeholders: [this.params.name] },
|
||||
content: { key: message },
|
||||
type: SimpleDialogType.WARNING,
|
||||
@@ -376,6 +396,14 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.showNoMasterPasswordWarning) {
|
||||
confirmed = await this.noMasterPasswordConfirmationDialog();
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
await this.organizationUserService.deleteOrganizationUser(
|
||||
this.params.organizationId,
|
||||
this.params.organizationUserId
|
||||
@@ -394,7 +422,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
let confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "revokeUserId", placeholders: [this.params.name] },
|
||||
content: { key: "revokeUserConfirmation" },
|
||||
acceptButtonText: { key: "revokeAccess" },
|
||||
@@ -405,6 +433,14 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.showNoMasterPasswordWarning) {
|
||||
confirmed = await this.noMasterPasswordConfirmationDialog();
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
await this.organizationUserService.revokeOrganizationUser(
|
||||
this.params.organizationId,
|
||||
this.params.organizationUserId
|
||||
@@ -450,6 +486,19 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
private close(result: MemberDialogResult) {
|
||||
this.dialogRef.close(result);
|
||||
}
|
||||
|
||||
private noMasterPasswordConfirmationDialog() {
|
||||
return this.dialogService.openSimpleDialog({
|
||||
title: {
|
||||
key: "removeOrgUserNoMasterPasswordTitle",
|
||||
},
|
||||
content: {
|
||||
key: "removeOrgUserNoMasterPasswordDesc",
|
||||
placeholders: [this.params.name],
|
||||
},
|
||||
type: SimpleDialogType.WARNING,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function mapCollectionToAccessItemView(
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { AbstractControl, FormControl, ValidationErrors } from "@angular/forms";
|
||||
|
||||
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ProductType } from "@bitwarden/common/enums";
|
||||
|
||||
import { freeOrgSeatLimitReachedValidator } from "./free-org-inv-limit-reached.validator";
|
||||
|
||||
const orgFactory = (props: Partial<Organization> = {}) =>
|
||||
Object.assign(
|
||||
new Organization(),
|
||||
{
|
||||
id: "myOrgId",
|
||||
enabled: true,
|
||||
type: OrganizationUserType.Admin,
|
||||
},
|
||||
props
|
||||
);
|
||||
|
||||
describe("freeOrgSeatLimitReachedValidator", () => {
|
||||
let organization: Organization;
|
||||
let allOrganizationUserEmails: string[];
|
||||
let validatorFn: (control: AbstractControl) => ValidationErrors | null;
|
||||
|
||||
beforeEach(() => {
|
||||
allOrganizationUserEmails = ["user1@example.com"];
|
||||
});
|
||||
|
||||
it("should return null when control value is empty", () => {
|
||||
validatorFn = freeOrgSeatLimitReachedValidator(
|
||||
organization,
|
||||
allOrganizationUserEmails,
|
||||
"You cannot invite more than 2 members without upgrading your plan."
|
||||
);
|
||||
const control = new FormControl("");
|
||||
|
||||
const result = validatorFn(control);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return null when control value is null", () => {
|
||||
validatorFn = freeOrgSeatLimitReachedValidator(
|
||||
organization,
|
||||
allOrganizationUserEmails,
|
||||
"You cannot invite more than 2 members without upgrading your plan."
|
||||
);
|
||||
const control = new FormControl(null);
|
||||
|
||||
const result = validatorFn(control);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return null when max seats are not exceeded on free plan", () => {
|
||||
organization = orgFactory({
|
||||
planProductType: ProductType.Free,
|
||||
seats: 2,
|
||||
});
|
||||
validatorFn = freeOrgSeatLimitReachedValidator(
|
||||
organization,
|
||||
allOrganizationUserEmails,
|
||||
"You cannot invite more than 2 members without upgrading your plan."
|
||||
);
|
||||
const control = new FormControl("user2@example.com");
|
||||
|
||||
const result = validatorFn(control);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return validation error when max seats are exceeded on free plan", () => {
|
||||
organization = orgFactory({
|
||||
planProductType: ProductType.Free,
|
||||
seats: 2,
|
||||
});
|
||||
const errorMessage = "You cannot invite more than 2 members without upgrading your plan.";
|
||||
validatorFn = freeOrgSeatLimitReachedValidator(
|
||||
organization,
|
||||
allOrganizationUserEmails,
|
||||
"You cannot invite more than 2 members without upgrading your plan."
|
||||
);
|
||||
const control = new FormControl("user2@example.com,user3@example.com");
|
||||
|
||||
const result = validatorFn(control);
|
||||
|
||||
expect(result).toStrictEqual({ freePlanLimitReached: { message: errorMessage } });
|
||||
});
|
||||
|
||||
it("should return null when not on free plan", () => {
|
||||
const control = new FormControl("user2@example.com,user3@example.com");
|
||||
organization = orgFactory({
|
||||
planProductType: ProductType.Enterprise,
|
||||
seats: 100,
|
||||
});
|
||||
validatorFn = freeOrgSeatLimitReachedValidator(
|
||||
organization,
|
||||
allOrganizationUserEmails,
|
||||
"You cannot invite more than 2 members without upgrading your plan."
|
||||
);
|
||||
|
||||
const result = validatorFn(control);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms";
|
||||
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ProductType } from "@bitwarden/common/enums";
|
||||
|
||||
/**
|
||||
* Checks if the limit of free organization seats has been reached when adding new users
|
||||
* @param organization An object representing the organization
|
||||
* @param allOrganizationUserEmails An array of strings with existing user email addresses
|
||||
* @param errorMessage A localized string to display if validation fails
|
||||
* @returns A function that validates an `AbstractControl` and returns `ValidationErrors` or `null`
|
||||
*/
|
||||
export function freeOrgSeatLimitReachedValidator(
|
||||
organization: Organization,
|
||||
allOrganizationUserEmails: string[],
|
||||
errorMessage: string
|
||||
): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
if (control.value === "" || !control.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const newEmailsToAdd = control.value
|
||||
.split(",")
|
||||
.filter(
|
||||
(newEmailToAdd: string) =>
|
||||
newEmailToAdd &&
|
||||
!allOrganizationUserEmails.some((existingEmail) => existingEmail === newEmailToAdd)
|
||||
);
|
||||
|
||||
return organization.planProductType === ProductType.Free &&
|
||||
allOrganizationUserEmails.length + newEmailsToAdd.length > organization.seats
|
||||
? { freePlanLimitReached: { message: errorMessage } }
|
||||
: null;
|
||||
};
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { LooseComponentsModule } from "../../../shared";
|
||||
import { SharedOrganizationModule } from "../shared";
|
||||
|
||||
import { BulkConfirmComponent } from "./components/bulk/bulk-confirm.component";
|
||||
import { BulkEnableSecretsManagerDialogComponent } from "./components/bulk/bulk-enable-sm-dialog.component";
|
||||
import { BulkRemoveComponent } from "./components/bulk/bulk-remove.component";
|
||||
import { BulkRestoreRevokeComponent } from "./components/bulk/bulk-restore-revoke.component";
|
||||
import { BulkStatusComponent } from "./components/bulk/bulk-status.component";
|
||||
@@ -21,6 +22,7 @@ import { PeopleComponent } from "./people.component";
|
||||
],
|
||||
declarations: [
|
||||
BulkConfirmComponent,
|
||||
BulkEnableSecretsManagerDialogComponent,
|
||||
BulkRemoveComponent,
|
||||
BulkRestoreRevokeComponent,
|
||||
BulkStatusComponent,
|
||||
|
||||
@@ -99,6 +99,12 @@
|
||||
></button>
|
||||
|
||||
<bit-menu #headerMenu>
|
||||
<ng-container *ngIf="canUseSecretsManager$ | async">
|
||||
<button type="button" bitMenuItem (click)="bulkEnableSM()">
|
||||
{{ "activateSecretsManager" | i18n }}
|
||||
</button>
|
||||
<bit-menu-divider></bit-menu-divider>
|
||||
</ng-container>
|
||||
<button type="button" bitMenuItem (click)="bulkReinvite()">
|
||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||
{{ "reinviteSelected" | i18n }}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
from,
|
||||
lastValueFrom,
|
||||
map,
|
||||
Observable,
|
||||
shareReplay,
|
||||
Subject,
|
||||
switchMap,
|
||||
@@ -55,12 +56,14 @@ import { CollectionData } from "@bitwarden/common/vault/models/data/collection.d
|
||||
import { Collection } from "@bitwarden/common/vault/models/domain/collection";
|
||||
import { CollectionDetailsResponse } from "@bitwarden/common/vault/models/response/collection.response";
|
||||
|
||||
import { flagEnabled } from "../../../../utils/flags";
|
||||
import { openEntityEventsDialog } from "../../../admin-console/organizations/manage/entity-events.component";
|
||||
import { BasePeopleComponent } from "../../../common/base.people.component";
|
||||
import { GroupService } from "../core";
|
||||
import { OrganizationUserView } from "../core/views/organization-user.view";
|
||||
|
||||
import { BulkConfirmComponent } from "./components/bulk/bulk-confirm.component";
|
||||
import { BulkEnableSecretsManagerDialogComponent } from "./components/bulk/bulk-enable-sm-dialog.component";
|
||||
import { BulkRemoveComponent } from "./components/bulk/bulk-remove.component";
|
||||
import { BulkRestoreRevokeComponent } from "./components/bulk/bulk-restore-revoke.component";
|
||||
import { BulkStatusComponent } from "./components/bulk/bulk-status.component";
|
||||
@@ -100,6 +103,7 @@ export class PeopleComponent
|
||||
status: OrganizationUserStatusType = null;
|
||||
orgResetPasswordPolicyEnabled = false;
|
||||
|
||||
protected canUseSecretsManager$: Observable<boolean>;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
@@ -148,6 +152,10 @@ export class PeopleComponent
|
||||
shareReplay({ refCount: true, bufferSize: 1 })
|
||||
);
|
||||
|
||||
this.canUseSecretsManager$ = organization$.pipe(
|
||||
map((org) => org.useSecretsManager && flagEnabled("secretsManager"))
|
||||
);
|
||||
|
||||
const policies$ = organization$.pipe(
|
||||
switchMap((organization) => {
|
||||
if (organization.isProviderUser) {
|
||||
@@ -294,7 +302,7 @@ export class PeopleComponent
|
||||
|
||||
async confirmUser(user: OrganizationUserView, publicKey: Uint8Array): Promise<void> {
|
||||
const orgKey = await this.cryptoService.getOrgKey(this.organization.id);
|
||||
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
|
||||
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey);
|
||||
const request = new OrganizationUserConfirmRequest();
|
||||
request.key = key.encryptedString;
|
||||
await this.organizationUserService.postOrganizationUserConfirm(
|
||||
@@ -396,6 +404,7 @@ export class PeopleComponent
|
||||
name: this.userNamePipe.transform(user),
|
||||
organizationId: this.organization.id,
|
||||
organizationUserId: user != null ? user.id : null,
|
||||
allOrganizationUserEmails: this.allUsers?.map((user) => user.email) ?? [],
|
||||
usesKeyConnector: user?.usesKeyConnector,
|
||||
initialTab: initialTab,
|
||||
},
|
||||
@@ -510,6 +519,26 @@ export class PeopleComponent
|
||||
await this.load();
|
||||
}
|
||||
|
||||
async bulkEnableSM() {
|
||||
const users = this.getCheckedUsers();
|
||||
if (users.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("noSelectedUsersApplicable")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const dialogRef = BulkEnableSecretsManagerDialogComponent.open(this.dialogService, {
|
||||
orgId: this.organization.id,
|
||||
users,
|
||||
});
|
||||
|
||||
await lastValueFrom(dialogRef.closed);
|
||||
this.selectAll(false);
|
||||
}
|
||||
|
||||
async events(user: OrganizationUserView) {
|
||||
await openEntityEventsDialog(this.dialogService, {
|
||||
data: {
|
||||
@@ -546,7 +575,7 @@ export class PeopleComponent
|
||||
? "removeUserConfirmationKeyConnector"
|
||||
: "removeOrgUserConfirmation";
|
||||
|
||||
return await this.dialogService.openSimpleDialog({
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: {
|
||||
key: "removeUserIdAccess",
|
||||
placeholders: [this.userNamePipe.transform(user)],
|
||||
@@ -554,6 +583,35 @@ export class PeopleComponent
|
||||
content: { key: content },
|
||||
type: SimpleDialogType.WARNING,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user.status > OrganizationUserStatusType.Invited && user.hasMasterPassword === false) {
|
||||
return await this.noMasterPasswordConfirmationDialog(user);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async revokeUserConfirmationDialog(user: OrganizationUserView) {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "revokeAccess", placeholders: [this.userNamePipe.transform(user)] },
|
||||
content: this.revokeWarningMessage(),
|
||||
acceptButtonText: { key: "revokeAccess" },
|
||||
type: SimpleDialogType.WARNING,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user.status > OrganizationUserStatusType.Invited && user.hasMasterPassword === false) {
|
||||
return await this.noMasterPasswordConfirmationDialog(user);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async showBulkStatus(
|
||||
@@ -608,4 +666,17 @@ export class PeopleComponent
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
|
||||
private async noMasterPasswordConfirmationDialog(user: OrganizationUserView) {
|
||||
return this.dialogService.openSimpleDialog({
|
||||
title: {
|
||||
key: "removeOrgUserNoMasterPasswordTitle",
|
||||
},
|
||||
content: {
|
||||
key: "removeOrgUserNoMasterPasswordDesc",
|
||||
placeholders: [this.userNamePipe.transform(user)],
|
||||
},
|
||||
type: SimpleDialogType.WARNING,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,23 @@
|
||||
<app-callout type="info" *ngIf="showKeyConnectorInfo">
|
||||
<bit-callout type="info" *ngIf="showKeyConnectorInfo">
|
||||
{{ "keyConnectorPolicyRestriction" | i18n }}
|
||||
</app-callout>
|
||||
</bit-callout>
|
||||
|
||||
<app-callout type="warning">
|
||||
{{ "resetPasswordPolicyWarning" | i18n }}
|
||||
</app-callout>
|
||||
<bit-callout type="success" [title]="'prerequisite' | i18n" icon="bwi-lightbulb">
|
||||
{{ "accountRecoverySingleOrgRequirementDesc" | i18n }}
|
||||
</bit-callout>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enabled"
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{ "turnOn" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<bit-callout type="warning">
|
||||
{{ "accountRecoveryPolicyWarning" | i18n }}
|
||||
</bit-callout>
|
||||
|
||||
<div [formGroup]="data">
|
||||
<h3 class="mt-4">{{ "resetPasswordPolicyAutoEnroll" | i18n }}</h3>
|
||||
<p>{{ "resetPasswordPolicyAutoEnrollDescription" | i18n }}</p>
|
||||
<app-callout type="warning">
|
||||
{{ "resetPasswordPolicyAutoEnrollWarning" | i18n }}
|
||||
</app-callout>
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="autoEnrollEnabled"
|
||||
name="AutoEnrollEnabled"
|
||||
formControlName="autoEnrollEnabled"
|
||||
/>
|
||||
<label class="form-check-label" for="autoEnrollEnabled">
|
||||
{{ "resetPasswordPolicyAutoEnrollCheckbox" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<bit-form-control class="!tw-mb-1">
|
||||
<input type="checkbox" bitCheckbox [formControl]="enabled" id="enabled" />
|
||||
<bit-label>{{ "turnOn" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
|
||||
<ng-container [formGroup]="data">
|
||||
<bit-form-control class="!tw-mb-1">
|
||||
<input type="checkbox" bitCheckbox formControlName="autoEnrollEnabled" id="autoEnrollEnabled" />
|
||||
<bit-label>{{ "resetPasswordPolicyAutoEnrollCheckbox" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { UntypedFormBuilder } from "@angular/forms";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
@@ -9,7 +9,7 @@ import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
|
||||
export class ResetPasswordPolicy extends BasePolicy {
|
||||
name = "accountRecoveryPolicy";
|
||||
description = "accountRecoveryPolicyDescription";
|
||||
description = "accountRecoveryPolicyDesc";
|
||||
type = PolicyType.ResetPassword;
|
||||
component = ResetPasswordPolicyComponent;
|
||||
|
||||
@@ -26,14 +26,9 @@ export class ResetPasswordPolicyComponent extends BasePolicyComponent {
|
||||
data = this.formBuilder.group({
|
||||
autoEnrollEnabled: false,
|
||||
});
|
||||
|
||||
defaultTypes: { name: string; value: string }[];
|
||||
showKeyConnectorInfo = false;
|
||||
|
||||
constructor(
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private organizationService: OrganizationService
|
||||
) {
|
||||
constructor(private formBuilder: FormBuilder, private organizationService: OrganizationService) {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,95 +1,66 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "organizationInfo" | i18n }}</h1>
|
||||
</div>
|
||||
<h1 bitTypography="h1" class="tw-pb-2.5">{{ "organizationInfo" | i18n }}</h1>
|
||||
<div *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<form
|
||||
*ngIf="org && !loading"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="name">{{ "organizationName" | i18n }}</label>
|
||||
<input
|
||||
id="name"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Name"
|
||||
[(ngModel)]="org.name"
|
||||
[disabled]="selfHosted"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="billingEmail">{{ "billingEmail" | i18n }}</label>
|
||||
<input
|
||||
id="billingEmail"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="BillingEmail"
|
||||
[(ngModel)]="org.billingEmail"
|
||||
[disabled]="selfHosted || !canEditSubscription"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="businessName">{{ "businessName" | i18n }}</label>
|
||||
<input
|
||||
id="businessName"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="BusinessName"
|
||||
[(ngModel)]="org.businessName"
|
||||
[disabled]="selfHosted || !canEditSubscription"
|
||||
/>
|
||||
</div>
|
||||
<form *ngIf="org && !loading" #form [bitSubmit]="submit" [formGroup]="formGroup">
|
||||
<div class="tw-grid tw-grid-cols-2 tw-gap-5">
|
||||
<div>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "organizationName" | i18n }}</bit-label>
|
||||
<input bitInput id="orgName" type="text" formControlName="orgName" />
|
||||
</bit-form-field>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "billingEmail" | i18n }}</bit-label>
|
||||
<input bitInput id="billingEmail" formControlName="billingEmail" type="email" />
|
||||
</bit-form-field>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "businessName" | i18n }}</bit-label>
|
||||
<input bitInput id="businessName" formControlName="businessName" type="text" />
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div>
|
||||
<bit-avatar [text]="org.name" [id]="org.id" size="large"></bit-avatar>
|
||||
<app-account-fingerprint
|
||||
[fingerprintMaterial]="organizationId"
|
||||
[publicKeyBuffer]="publicKeyBuffer"
|
||||
fingerprintLabel="{{ 'yourOrganizationsFingerprint' | i18n }}"
|
||||
>
|
||||
</app-account-fingerprint>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" buttonType="primary" bitButton [loading]="form.loading">
|
||||
<button type="submit" bitButton bitFormButton buttonType="primary">
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
<ng-container *ngIf="canUseApi">
|
||||
<div class="secondary-header border-0 mb-0">
|
||||
<h1>{{ "apiKey" | i18n }}</h1>
|
||||
</div>
|
||||
<h1 bitTypography="h1" class="tw-mt-16 tw-pb-2.5">{{ "apiKey" | i18n }}</h1>
|
||||
<p>
|
||||
{{ "apiKeyDesc" | i18n }}
|
||||
<a href="https://docs.bitwarden.com" target="_blank" rel="noopener">
|
||||
{{ "learnMore" | i18n }}
|
||||
</a>
|
||||
</p>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="viewApiKey()">
|
||||
<button type="button" bitButton buttonType="secondary" (click)="viewApiKey()">
|
||||
{{ "viewApiKey" | i18n }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="rotateApiKey()">
|
||||
<button type="button" bitButton buttonType="secondary" (click)="rotateApiKey()">
|
||||
{{ "rotateApiKey" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
<div class="secondary-header text-danger border-0 mb-0">
|
||||
<h1>{{ "dangerZone" | i18n }}</h1>
|
||||
</div>
|
||||
<div class="card border-danger">
|
||||
<div class="card-body">
|
||||
<p>{{ "dangerZoneDesc" | i18n }}</p>
|
||||
<button type="button" class="btn btn-outline-danger" (click)="deleteOrganization()">
|
||||
{{ "deleteOrganization" | i18n }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger" (click)="purgeVault()">
|
||||
{{ "purgeVault" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<h1 bitTypography="h1" class="tw-mt-16 tw-pb-2.5 !tw-text-danger">{{ "dangerZone" | i18n }}</h1>
|
||||
<div class="tw-rounded tw-border tw-border-solid tw-border-danger-500 tw-bg-background tw-p-5">
|
||||
<p>{{ "dangerZoneDesc" | i18n }}</p>
|
||||
<button type="button" bitButton buttonType="danger" (click)="deleteOrganization()">
|
||||
{{ "deleteOrganization" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="danger" (click)="purgeVault()">
|
||||
{{ "purgeVault" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<ng-template #purgeOrganizationTemplate></ng-template>
|
||||
<ng-template #apiKeyTemplate></ng-template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { combineLatest, lastValueFrom, Subject, switchMap, takeUntil, from } from "rxjs";
|
||||
|
||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
@@ -13,6 +14,7 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
import { ApiKeyComponent } from "../../../settings/api-key.component";
|
||||
import { PurgeVaultComponent } from "../../../settings/purge-vault.component";
|
||||
@@ -23,7 +25,6 @@ import { DeleteOrganizationDialogResult, openDeleteOrganizationDialog } from "./
|
||||
selector: "app-org-account",
|
||||
templateUrl: "account.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class AccountComponent {
|
||||
@ViewChild("purgeOrganizationTemplate", { read: ViewContainerRef, static: true })
|
||||
purgeModalRef: ViewContainerRef;
|
||||
@@ -40,7 +41,29 @@ export class AccountComponent {
|
||||
formPromise: Promise<OrganizationResponse>;
|
||||
taxFormPromise: Promise<unknown>;
|
||||
|
||||
private organizationId: string;
|
||||
// FormGroup validators taken from server Organization domain object
|
||||
protected formGroup = this.formBuilder.group({
|
||||
orgName: this.formBuilder.control(
|
||||
{ value: "", disabled: true },
|
||||
{
|
||||
validators: [Validators.required, Validators.maxLength(50)],
|
||||
updateOn: "change",
|
||||
}
|
||||
),
|
||||
billingEmail: this.formBuilder.control(
|
||||
{ value: "", disabled: true },
|
||||
{ validators: [Validators.required, Validators.email, Validators.maxLength(256)] }
|
||||
),
|
||||
businessName: this.formBuilder.control(
|
||||
{ value: "", disabled: true },
|
||||
{ validators: [Validators.maxLength(50)] }
|
||||
),
|
||||
});
|
||||
|
||||
protected organizationId: string;
|
||||
protected publicKeyBuffer: Uint8Array;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private modalService: ModalService,
|
||||
@@ -52,53 +75,88 @@ export class AccountComponent {
|
||||
private router: Router,
|
||||
private organizationService: OrganizationService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private dialogService: DialogServiceAbstraction
|
||||
private dialogService: DialogServiceAbstraction,
|
||||
private formBuilder: FormBuilder
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.selfHosted = this.platformUtilsService.isSelfHost();
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
this.canEditSubscription = this.organizationService.get(
|
||||
this.organizationId
|
||||
).canEditSubscription;
|
||||
try {
|
||||
this.org = await this.organizationApiService.get(this.organizationId);
|
||||
this.canUseApi = this.org.useApi;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
});
|
||||
this.loading = false;
|
||||
this.route.parent.parent.params
|
||||
.pipe(
|
||||
switchMap((params) => {
|
||||
return combineLatest([
|
||||
// Organization domain
|
||||
this.organizationService.get$(params.organizationId),
|
||||
// OrganizationResponse for form population
|
||||
from(this.organizationApiService.get(params.organizationId)),
|
||||
// Organization Public Key
|
||||
from(this.organizationApiService.getKeys(params.organizationId)),
|
||||
]);
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe(([organization, orgResponse, orgKeys]) => {
|
||||
// Set domain level organization variables
|
||||
this.organizationId = organization.id;
|
||||
this.canEditSubscription = organization.canEditSubscription;
|
||||
this.canUseApi = organization.useApi;
|
||||
|
||||
// Org Response
|
||||
this.org = orgResponse;
|
||||
|
||||
// Public Key Buffer for Org Fingerprint Generation
|
||||
this.publicKeyBuffer = Utils.fromB64ToArray(orgKeys?.publicKey);
|
||||
|
||||
// Patch existing values
|
||||
this.formGroup.patchValue({
|
||||
orgName: this.org.name,
|
||||
billingEmail: this.org.billingEmail,
|
||||
businessName: this.org.businessName,
|
||||
});
|
||||
|
||||
// Update disabled states - reactive forms prefers not using disabled attribute
|
||||
if (!this.selfHosted) {
|
||||
this.formGroup.get("orgName").enable();
|
||||
}
|
||||
|
||||
if (!this.selfHosted || this.canEditSubscription) {
|
||||
this.formGroup.get("billingEmail").enable();
|
||||
this.formGroup.get("businessName").enable();
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
const request = new OrganizationUpdateRequest();
|
||||
request.name = this.org.name;
|
||||
request.businessName = this.org.businessName;
|
||||
request.billingEmail = this.org.billingEmail;
|
||||
ngOnDestroy(): void {
|
||||
// You must first call .next() in order for the notifier to properly close subscriptions using takeUntil
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
// Backfill pub/priv key if necessary
|
||||
if (!this.org.hasPublicAndPrivateKeys) {
|
||||
const orgShareKey = await this.cryptoService.getOrgKey(this.organizationId);
|
||||
const orgKeys = await this.cryptoService.makeKeyPair(orgShareKey);
|
||||
request.keys = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
|
||||
}
|
||||
|
||||
this.formPromise = this.organizationApiService.save(this.organizationId, request);
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("organizationUpdated")
|
||||
);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
submit = async () => {
|
||||
this.formGroup.markAllAsTouched();
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const request = new OrganizationUpdateRequest();
|
||||
request.name = this.formGroup.value.orgName;
|
||||
request.businessName = this.formGroup.value.businessName;
|
||||
request.billingEmail = this.formGroup.value.billingEmail;
|
||||
|
||||
// Backfill pub/priv key if necessary
|
||||
if (!this.org.hasPublicAndPrivateKeys) {
|
||||
const orgShareKey = await this.cryptoService.getOrgKey(this.organizationId);
|
||||
const orgKeys = await this.cryptoService.makeKeyPair(orgShareKey);
|
||||
request.keys = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
|
||||
}
|
||||
|
||||
this.formPromise = this.organizationApiService.save(this.organizationId, request);
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("organizationUpdated"));
|
||||
};
|
||||
|
||||
async deleteOrganization() {
|
||||
const dialog = openDeleteOrganizationDialog(this.dialogService, {
|
||||
|
||||
@@ -4,12 +4,11 @@ import { FormBuilder, FormControl, Validators } from "@angular/forms";
|
||||
import { combineLatest, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { Verification } from "@bitwarden/common/types/verification";
|
||||
@@ -17,7 +16,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
|
||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
import { UserVerificationModule } from "../../../../shared/components/user-verification";
|
||||
import { UserVerificationModule } from "../../../../auth/shared/components/user-verification";
|
||||
import { SharedModule } from "../../../../shared/shared.module";
|
||||
|
||||
class CountBasedLocalizationKey {
|
||||
@@ -36,10 +35,13 @@ class CountBasedLocalizationKey {
|
||||
|
||||
class OrganizationContentSummaryItem {
|
||||
count: number;
|
||||
|
||||
get localizationKey(): string {
|
||||
return this.localizationKeyOptions.getKey(this.count);
|
||||
}
|
||||
|
||||
private localizationKeyOptions: CountBasedLocalizationKey;
|
||||
|
||||
constructor(count: number, localizationKeyOptions: CountBasedLocalizationKey) {
|
||||
this.count = count;
|
||||
this.localizationKeyOptions = localizationKeyOptions;
|
||||
@@ -88,7 +90,6 @@ export class DeleteOrganizationDialogComponent implements OnInit, OnDestroy {
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private userVerificationService: UserVerificationService,
|
||||
private logService: LogService,
|
||||
private cipherService: CipherService,
|
||||
private organizationService: OrganizationService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
@@ -116,20 +117,16 @@ export class DeleteOrganizationDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected submit = async () => {
|
||||
try {
|
||||
this.formPromise = this.userVerificationService
|
||||
.buildRequest(this.formGroup.value.secret)
|
||||
.then((request) => this.organizationApiService.delete(this.organization.id, request));
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
this.i18nService.t("organizationDeleted"),
|
||||
this.i18nService.t("organizationDeletedDesc")
|
||||
);
|
||||
this.dialogRef.close(DeleteOrganizationDialogResult.Deleted);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
await this.userVerificationService
|
||||
.buildRequest(this.formGroup.value.secret)
|
||||
.then((request) => this.organizationApiService.delete(this.organization.id, request));
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
this.i18nService.t("organizationDeleted"),
|
||||
this.i18nService.t("organizationDeletedDesc")
|
||||
);
|
||||
this.dialogRef.close(DeleteOrganizationDialogResult.Deleted);
|
||||
};
|
||||
|
||||
private buildOrganizationContentSummary(ciphers: CipherView[]): OrganizationContentSummary {
|
||||
|
||||
@@ -70,6 +70,9 @@ function getSettingsRoute(organization: Organization) {
|
||||
if (organization.canManageScim) {
|
||||
return "scim";
|
||||
}
|
||||
if (organization.canManageDeviceApprovals) {
|
||||
return "device-approvals";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { LooseComponentsModule, SharedModule } from "../../../shared";
|
||||
import { AccountFingerprintComponent } from "../../../shared/components/account-fingerprint/account-fingerprint.component";
|
||||
import { PoliciesModule } from "../../organizations/policies";
|
||||
|
||||
import { AccountComponent } from "./account.component";
|
||||
@@ -9,7 +10,13 @@ import { SettingsComponent } from "./settings.component";
|
||||
import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, LooseComponentsModule, PoliciesModule, OrganizationSettingsRoutingModule],
|
||||
imports: [
|
||||
SharedModule,
|
||||
LooseComponentsModule,
|
||||
PoliciesModule,
|
||||
OrganizationSettingsRoutingModule,
|
||||
AccountFingerprintComponent,
|
||||
],
|
||||
declarations: [SettingsComponent, AccountComponent, TwoFactorSetupComponent],
|
||||
})
|
||||
export class OrganizationSettingsModule {}
|
||||
|
||||
@@ -60,6 +60,16 @@
|
||||
>
|
||||
{{ "singleSignOn" | i18n }}
|
||||
</a>
|
||||
<ng-container *appIfFeature="FeatureFlag.TrustedDeviceEncryption">
|
||||
<a
|
||||
routerLink="device-approvals"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="org.canManageDeviceApprovals"
|
||||
>
|
||||
{{ "deviceApprovals" | i18n }}
|
||||
</a>
|
||||
</ng-container>
|
||||
<a
|
||||
routerLink="scim"
|
||||
class="list-group-item"
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Observable, switchMap } from "rxjs";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-settings",
|
||||
@@ -11,6 +12,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
})
|
||||
export class SettingsComponent implements OnInit {
|
||||
organization$: Observable<Organization>;
|
||||
FeatureFlag = FeatureFlag;
|
||||
|
||||
constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {}
|
||||
|
||||
|
||||
@@ -3,10 +3,9 @@ import { UntypedFormBuilder } from "@angular/forms";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
@@ -35,7 +34,6 @@ export class OrganizationExportComponent extends ExportComponent {
|
||||
userVerificationService: UserVerificationService,
|
||||
formBuilder: UntypedFormBuilder,
|
||||
fileDownloadService: FileDownloadService,
|
||||
modalService: ModalService,
|
||||
dialogService: DialogServiceAbstraction
|
||||
) {
|
||||
super(
|
||||
@@ -49,7 +47,6 @@ export class OrganizationExportComponent extends ExportComponent {
|
||||
userVerificationService,
|
||||
formBuilder,
|
||||
fileDownloadService,
|
||||
modalService,
|
||||
dialogService
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { switchMap, takeUntil } from "rxjs/operators";
|
||||
|
||||
@@ -13,6 +14,8 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
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 { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { ImportServiceAbstraction } from "@bitwarden/importer";
|
||||
|
||||
@@ -37,11 +40,14 @@ export class OrganizationImportComponent extends ImportComponent {
|
||||
private route: ActivatedRoute,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
policyService: PolicyService,
|
||||
private organizationService: OrganizationService,
|
||||
organizationService: OrganizationService,
|
||||
logService: LogService,
|
||||
modalService: ModalService,
|
||||
syncService: SyncService,
|
||||
dialogService: DialogServiceAbstraction
|
||||
dialogService: DialogServiceAbstraction,
|
||||
folderService: FolderService,
|
||||
collectionService: CollectionService,
|
||||
formBuilder: FormBuilder
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
@@ -52,7 +58,11 @@ export class OrganizationImportComponent extends ImportComponent {
|
||||
logService,
|
||||
modalService,
|
||||
syncService,
|
||||
dialogService
|
||||
dialogService,
|
||||
folderService,
|
||||
collectionService,
|
||||
organizationService,
|
||||
formBuilder
|
||||
);
|
||||
}
|
||||
|
||||
@@ -74,11 +84,10 @@ export class OrganizationImportComponent extends ImportComponent {
|
||||
await this.router.navigate(["organizations", this.organizationId, "vault"]);
|
||||
} else {
|
||||
this.fileSelected = null;
|
||||
this.fileContents = "";
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
protected async performImport() {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "warning" },
|
||||
content: { key: "importWarning", placeholders: [this.organization.name] },
|
||||
@@ -88,6 +97,6 @@ export class OrganizationImportComponent extends ImportComponent {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
super.submit();
|
||||
await super.performImport();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||
import { ModalConfig } from "@bitwarden/angular/services/modal.service";
|
||||
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/abstractions/organization-user/requests";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -59,7 +59,7 @@ export class EnrollMasterPasswordReset {
|
||||
|
||||
// RSA Encrypt user's encKey.key with organization public key
|
||||
const encKey = await this.cryptoService.getEncKey();
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey);
|
||||
keyString = encryptedKey.encryptedString;
|
||||
toastStringRef = "enrollPasswordResetSuccess";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ScrollingModule } from "@angular/cdk/scrolling";
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { UserVerificationModule } from "../../../auth/shared/components/user-verification";
|
||||
import { LooseComponentsModule, SharedModule } from "../../../shared";
|
||||
import { UserVerificationModule } from "../../../shared/components/user-verification";
|
||||
|
||||
import { EnrollMasterPasswordReset } from "./enroll-master-password-reset.component";
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AppComponent } from "./app.component";
|
||||
import { CoreModule } from "./core";
|
||||
import { OssRoutingModule } from "./oss-routing.module";
|
||||
import { OssModule } from "./oss.module";
|
||||
import { SendComponent } from "./tools/send/send.component";
|
||||
import { WildcardRoutingModule } from "./wildcard-routing.module";
|
||||
|
||||
@NgModule({
|
||||
@@ -21,6 +22,7 @@ import { WildcardRoutingModule } from "./wildcard-routing.module";
|
||||
DragDropModule,
|
||||
LayoutModule,
|
||||
OssRoutingModule,
|
||||
SendComponent,
|
||||
WildcardRoutingModule, // Needs to be last to catch all non-existing routes
|
||||
],
|
||||
declarations: [AppComponent],
|
||||
|
||||
@@ -142,7 +142,7 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
|
||||
|
||||
// RSA Encrypt user's encKey.key with organization public key
|
||||
const encKey = await this.cryptoService.getEncKey();
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey);
|
||||
|
||||
// Add reset password key to accept request
|
||||
request.resetPasswordKey = encryptedKey.encryptedString;
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<hr />
|
||||
|
||||
<div class="tw-text-light tw-mt-3">
|
||||
{{ "loginWithDeviceEnabledInfo" | i18n }}
|
||||
{{ "loginWithDeviceEnabledNote" | i18n }}
|
||||
<a routerLink="/login">{{ "viewAllLoginOptions" | i18n }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
|
||||
@@ -33,7 +33,7 @@ export class EmergencyAccessConfirmComponent implements OnInit {
|
||||
const publicKeyResponse = await this.apiService.getUserPublicKey(this.userId);
|
||||
if (publicKeyResponse != null) {
|
||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||
const fingerprint = await this.cryptoService.getFingerprint(this.userId, publicKey.buffer);
|
||||
const fingerprint = await this.cryptoService.getFingerprint(this.userId, publicKey);
|
||||
if (fingerprint != null) {
|
||||
this.fingerprint = fingerprint.join("-");
|
||||
}
|
||||
|
||||
@@ -309,13 +309,13 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
try {
|
||||
this.logService.debug(
|
||||
"User's fingerprint: " +
|
||||
(await this.cryptoService.getFingerprint(details.granteeId, publicKey.buffer)).join("-")
|
||||
(await this.cryptoService.getFingerprint(details.granteeId, publicKey)).join("-")
|
||||
);
|
||||
} catch {
|
||||
// Ignore errors since it's just a debug message
|
||||
}
|
||||
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey);
|
||||
const request = new EmergencyAccessConfirmRequest();
|
||||
request.key = encryptedKey.encryptedString;
|
||||
await this.apiService.postEmergencyAccessConfirm(details.id, request);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
|
||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request";
|
||||
import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Directive, EventEmitter, Output } from "@angular/core";
|
||||
|
||||
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component } from "@angular/core";
|
||||
|
||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request";
|
||||
import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component } from "@angular/core";
|
||||
|
||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
|
||||
import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, NgZone } from "@angular/core";
|
||||
|
||||
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
||||
import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn-delete.request";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component } from "@angular/core";
|
||||
|
||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { UpdateTwoFactorYubioOtpRequest } from "@bitwarden/common/auth/models/request/update-two-factor-yubio-otp.request";
|
||||
import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response";
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from "./user-verification.module";
|
||||
export * from "./user-verification-prompt.component";
|
||||
export * from "./user-verification.component";
|
||||
export * from "./user-verification-prompt.component";
|
||||
@@ -0,0 +1,20 @@
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog>
|
||||
<span bitDialogTitle>{{ modalTitle | i18n }}</span>
|
||||
<ng-container bitDialogContent>
|
||||
<p bitTypography="body1">{{ confirmDescription | i18n }}</p>
|
||||
<app-user-verification
|
||||
[(invalidSecret)]="invalidSecret"
|
||||
formControlName="secret"
|
||||
></app-user-verification>
|
||||
</ng-container>
|
||||
<ng-container bitDialogFooter>
|
||||
<button type="submit" bitButton bitFormButton buttonType="primary">
|
||||
{{ confirmButtonText | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
||||
@@ -0,0 +1,60 @@
|
||||
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { UserVerificationPromptComponent as BaseUserVerificationPrompt } from "@bitwarden/angular/auth/components/user-verification-prompt.component";
|
||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||
import { ModalConfig } from "@bitwarden/angular/services/modal.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
export interface UserVerificationPromptParams {
|
||||
confirmDescription: string;
|
||||
confirmButtonText: string;
|
||||
modalTitle: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: "user-verification-prompt.component.html",
|
||||
})
|
||||
export class UserVerificationPromptComponent extends BaseUserVerificationPrompt {
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) data: UserVerificationPromptParams,
|
||||
private dialogRef: DialogRef<boolean>,
|
||||
userVerificationService: UserVerificationService,
|
||||
formBuilder: FormBuilder,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService
|
||||
) {
|
||||
// TODO: Remove when BaseUserVerificationPrompt has support for CL
|
||||
const modalConfig: ModalConfig = { data };
|
||||
super(
|
||||
null,
|
||||
modalConfig,
|
||||
userVerificationService,
|
||||
formBuilder,
|
||||
platformUtilsService,
|
||||
i18nService
|
||||
);
|
||||
}
|
||||
|
||||
override close(success: boolean) {
|
||||
this.dialogRef.close(success);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strongly typed helper to open a UserVerificationPrompt
|
||||
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||
* @param config Configuration for the dialog
|
||||
*/
|
||||
export const openUserVerificationPrompt = (
|
||||
dialogService: DialogServiceAbstraction,
|
||||
config: DialogConfig<UserVerificationPromptParams>
|
||||
) => {
|
||||
return dialogService.open<boolean, UserVerificationPromptParams>(
|
||||
UserVerificationPromptComponent,
|
||||
config
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
<ng-container *ngIf="!usesKeyConnector">
|
||||
<bit-form-field disableMargin>
|
||||
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
id="masterPassword"
|
||||
type="password"
|
||||
name="MasterPasswordHash"
|
||||
[formControl]="secret"
|
||||
appAutofocus
|
||||
appInputVerbatim
|
||||
/>
|
||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
<bit-hint>{{ "confirmIdentity" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="usesKeyConnector">
|
||||
<div class="tw-mb-6">
|
||||
<label class="tw-block">{{ "sendVerificationCode" | i18n }}</label>
|
||||
<button type="button" bitButton buttonType="secondary" [bitAction]="requestOTP" appAutofocus>
|
||||
{{ "sendCode" | i18n }}
|
||||
</button>
|
||||
<span class="tw-ml-2 tw-text-success" role="alert" @sent *ngIf="sentCode">
|
||||
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
|
||||
{{ "codeSent" | i18n }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<bit-form-field disableMargin>
|
||||
<bit-label>{{ "verificationCode" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="text"
|
||||
id="verificationCode"
|
||||
name="verificationCode"
|
||||
[formControl]="secret"
|
||||
appInputVerbatim
|
||||
/>
|
||||
<bit-hint>{{ "confirmIdentity" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
@@ -1,12 +1,13 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
|
||||
import { SharedModule } from "../../shared.module";
|
||||
import { SharedModule } from "../../../../shared/shared.module";
|
||||
|
||||
import { UserVerificationPromptComponent } from "./user-verification-prompt.component";
|
||||
import { UserVerificationComponent } from "./user-verification.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule],
|
||||
imports: [SharedModule, FormsModule, ReactiveFormsModule],
|
||||
declarations: [UserVerificationComponent, UserVerificationPromptComponent],
|
||||
exports: [UserVerificationComponent, UserVerificationPromptComponent],
|
||||
})
|
||||
@@ -58,10 +58,14 @@
|
||||
</div>
|
||||
<div class="tw-pt-44" *ngIf="useTrialStepper">
|
||||
<div class="tw-rounded tw-border tw-border-solid tw-border-secondary-300 tw-bg-background">
|
||||
<div class="tw-flex tw-h-12 tw-w-full tw-items-center tw-rounded-t tw-bg-secondary-100">
|
||||
<h2 class="tw-mb-0 tw-pl-4 tw-text-base tw-font-bold tw-uppercase">
|
||||
Start your 7-Day free trial of Bitwarden for {{ org }}
|
||||
<div class="tw-flex tw-h-auto tw-w-full tw-gap-5 tw-rounded-t tw-bg-secondary-100">
|
||||
<h2 class="tw-pb-4 tw-pl-4 tw-pt-5 tw-text-base tw-font-bold tw-uppercase">
|
||||
{{ "startYour7DayFreeTrialOfBitwardenFor" | i18n : org }}
|
||||
</h2>
|
||||
<environment-selector
|
||||
[hasFlags]="true"
|
||||
class="tw-mr-4 tw-mt-6 tw-flex-shrink-0 tw-text-end"
|
||||
></environment-selector>
|
||||
</div>
|
||||
<app-vertical-stepper #stepper linear (selectionChange)="stepSelectionChange($event)">
|
||||
<app-vertical-step label="Create Account" [editable]="false" [subLabel]="email">
|
||||
@@ -80,7 +84,7 @@
|
||||
[disabled]="orgInfoFormGroup.get('name').invalid"
|
||||
cdkStepperNext
|
||||
>
|
||||
Next
|
||||
{{ "next" | i18n }}
|
||||
</button>
|
||||
</app-vertical-step>
|
||||
<app-vertical-step label="Billing" [subLabel]="billingSubLabel">
|
||||
@@ -100,7 +104,7 @@
|
||||
></app-trial-confirmation-details>
|
||||
<div class="tw-mb-3 tw-flex">
|
||||
<button type="button" bitButton buttonType="primary" (click)="navigateToOrgVault()">
|
||||
Get Started
|
||||
{{ "getStarted" | i18n | titlecase }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -109,7 +113,7 @@
|
||||
(click)="navigateToOrgInvite()"
|
||||
class="tw-ml-3 tw-inline-flex tw-items-center tw-px-3"
|
||||
>
|
||||
Invite Users
|
||||
{{ "inviteUsers" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</app-vertical-step>
|
||||
@@ -51,7 +51,7 @@ describe("TrialInitiationComponent", () => {
|
||||
component: BlankComponent,
|
||||
},
|
||||
{
|
||||
path: `organizations/${testOrgId}/manage/members`,
|
||||
path: `organizations/${testOrgId}/members`,
|
||||
component: BlankComponent,
|
||||
},
|
||||
]),
|
||||
@@ -301,7 +301,7 @@ describe("TrialInitiationComponent", () => {
|
||||
describe("navigateToOrgVault", () => {
|
||||
it("should call verticalStepper.previous()", fakeAsync(() => {
|
||||
component.navigateToOrgInvite();
|
||||
expect(routerSpy).toHaveBeenCalledWith(["organizations", testOrgId, "manage", "members"]);
|
||||
expect(routerSpy).toHaveBeenCalledWith(["organizations", testOrgId, "members"]);
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -227,7 +227,7 @@ export class TrialInitiationComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
navigateToOrgInvite() {
|
||||
this.router.navigate(["organizations", this.orgId, "manage", "members"]);
|
||||
this.router.navigate(["organizations", this.orgId, "members"]);
|
||||
}
|
||||
|
||||
previousStep() {
|
||||
@@ -7,6 +7,7 @@ import { FormFieldModule } from "@bitwarden/components";
|
||||
import { OrganizationCreateModule } from "../../admin-console/organizations/create/organization-create.module";
|
||||
import { RegisterFormModule } from "../../auth/register-form/register-form.module";
|
||||
import { BillingComponent } from "../../billing/accounts/trial-initiation/billing.component";
|
||||
import { EnvironmentSelectorModule } from "../../components/environment-selector/environment-selector.module";
|
||||
import { LooseComponentsModule, SharedModule } from "../../shared";
|
||||
|
||||
import { ConfirmationDetailsComponent } from "./confirmation-details.component";
|
||||
@@ -37,6 +38,7 @@ import { VerticalStepperModule } from "./vertical-stepper/vertical-stepper.modul
|
||||
RegisterFormModule,
|
||||
OrganizationCreateModule,
|
||||
LooseComponentsModule,
|
||||
EnvironmentSelectorModule,
|
||||
],
|
||||
declarations: [
|
||||
TrialInitiationComponent,
|
||||
@@ -4,8 +4,8 @@ import { Router } from "@angular/router";
|
||||
import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "@bitwarden/angular/auth/components/update-password.component";
|
||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { UntypedFormBuilder, FormGroup } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
import { FormGroup } from "@angular/forms";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { ProductType } from "@bitwarden/common/enums";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
|
||||
import { OrganizationPlansComponent } from "../../settings/organization-plans.component";
|
||||
|
||||
@@ -24,36 +13,6 @@ export class BillingComponent extends OrganizationPlansComponent {
|
||||
@Input() orgInfoForm: FormGroup;
|
||||
@Output() previousStep = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
apiService: ApiService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
cryptoService: CryptoService,
|
||||
router: Router,
|
||||
syncService: SyncService,
|
||||
policyService: PolicyService,
|
||||
organizationService: OrganizationService,
|
||||
logService: LogService,
|
||||
messagingService: MessagingService,
|
||||
formBuilder: UntypedFormBuilder,
|
||||
organizationApiService: OrganizationApiServiceAbstraction
|
||||
) {
|
||||
super(
|
||||
apiService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
cryptoService,
|
||||
router,
|
||||
syncService,
|
||||
policyService,
|
||||
organizationService,
|
||||
logService,
|
||||
messagingService,
|
||||
formBuilder,
|
||||
organizationApiService
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const additionalSeats = this.product == ProductType.Families ? 0 : 1;
|
||||
this.formGroup.patchValue({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<div class="form-group col-8">
|
||||
<label for="newSeatCount">{{ "subscriptionSeats" | i18n }}</label>
|
||||
<input
|
||||
id="newSeatCount"
|
||||
@@ -41,7 +41,7 @@
|
||||
<label for="maxAutoscaleSeats">{{ "maxSeatLimit" | i18n }}</label>
|
||||
<input
|
||||
id="maxAutoscaleSeats"
|
||||
class="form-control col-6"
|
||||
class="form-control col-8"
|
||||
type="number"
|
||||
name="MaxAutoscaleSeats"
|
||||
[(ngModel)]="newMaxSeats"
|
||||
|
||||
@@ -40,7 +40,7 @@ export class AdjustSubscription {
|
||||
try {
|
||||
const seatAdjustment = this.newSeatCount - this.currentSeatCount;
|
||||
const request = new OrganizationSubscriptionUpdateRequest(seatAdjustment, this.newMaxSeats);
|
||||
this.formPromise = this.organizationApiService.updateSubscription(
|
||||
this.formPromise = this.organizationApiService.updatePasswordManagerSeats(
|
||||
this.organizationId,
|
||||
request
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user