mirror of
https://github.com/bitwarden/browser
synced 2025-12-23 03:33:54 +00:00
merge main, fix conflicts
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserInviteRequest,
|
||||
OrganizationUserUpdateRequest,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||
import { OrganizationUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
|
||||
OrganizationUserDetailsResponse,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
import { CoreOrganizationModule } from "../core-organization.module";
|
||||
@@ -15,14 +15,14 @@ import { OrganizationUserAdminView } from "../views/organization-user-admin-view
|
||||
export class UserAdminService {
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
) {}
|
||||
|
||||
async get(
|
||||
organizationId: string,
|
||||
organizationUserId: string,
|
||||
): Promise<OrganizationUserAdminView | undefined> {
|
||||
const userResponse = await this.organizationUserService.getOrganizationUser(
|
||||
const userResponse = await this.organizationUserApiService.getOrganizationUser(
|
||||
organizationId,
|
||||
organizationUserId,
|
||||
{
|
||||
@@ -47,7 +47,11 @@ export class UserAdminService {
|
||||
request.groups = user.groups;
|
||||
request.accessSecretsManager = user.accessSecretsManager;
|
||||
|
||||
await this.organizationUserService.putOrganizationUser(user.organizationId, user.id, request);
|
||||
await this.organizationUserApiService.putOrganizationUser(
|
||||
user.organizationId,
|
||||
user.id,
|
||||
request,
|
||||
);
|
||||
}
|
||||
|
||||
async invite(emails: string[], user: OrganizationUserAdminView): Promise<void> {
|
||||
@@ -59,7 +63,7 @@ export class UserAdminService {
|
||||
request.groups = user.groups;
|
||||
request.accessSecretsManager = user.accessSecretsManager;
|
||||
|
||||
await this.organizationUserService.postOrganizationUserInvite(user.organizationId, request);
|
||||
await this.organizationUserApiService.postOrganizationUserInvite(user.organizationId, request);
|
||||
}
|
||||
|
||||
private async decryptMany(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
|
||||
import { OrganizationUserUserDetailsResponse } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
OrganizationUserStatusType,
|
||||
OrganizationUserType,
|
||||
|
||||
@@ -6,31 +6,27 @@
|
||||
<div bitDialogContent>
|
||||
<form [formGroup]="filterFormGroup" [bitSubmit]="refreshEvents">
|
||||
<div class="tw-flex tw-items-center tw-space-x-2">
|
||||
<div>
|
||||
<label class="tw-sr-only" for="start">{{ "startDate" | i18n }}</label>
|
||||
<span>
|
||||
<input
|
||||
bitInput
|
||||
type="datetime-local"
|
||||
id="start"
|
||||
placeholder="{{ 'startDate' | i18n }}"
|
||||
formControlName="start"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "from" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="datetime-local"
|
||||
id="start"
|
||||
placeholder="{{ 'startDate' | i18n }}"
|
||||
formControlName="start"
|
||||
/>
|
||||
</bit-form-field>
|
||||
<span class="tw-mx-2">-</span>
|
||||
<div>
|
||||
<label class="tw-sr-only" for="end">{{ "endDate" | i18n }}</label>
|
||||
<span>
|
||||
<input
|
||||
bitInput
|
||||
type="datetime-local"
|
||||
id="end"
|
||||
placeholder="{{ 'endDate' | i18n }}"
|
||||
formControlName="end"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "to" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="datetime-local"
|
||||
id="end"
|
||||
placeholder="{{ 'endDate' | i18n }}"
|
||||
formControlName="end"
|
||||
/>
|
||||
</bit-form-field>
|
||||
<button type="submit" bitButton buttonType="primary" bitFormButton>
|
||||
<i class="bwi bwi-refresh bwi-fw" aria-hidden="true"></i>
|
||||
{{ "refresh" | i18n }}
|
||||
|
||||
@@ -2,9 +2,9 @@ import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { EventResponse } from "@bitwarden/common/models/response/event.response";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { EventView } from "@bitwarden/common/models/view/event.view";
|
||||
@@ -60,7 +60,7 @@ export class EntityEventsComponent implements OnInit {
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private userNamePipe: UserNamePipe,
|
||||
private logService: LogService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private formBuilder: FormBuilder,
|
||||
private validationService: ValidationService,
|
||||
private toastService: ToastService,
|
||||
@@ -78,7 +78,9 @@ export class EntityEventsComponent implements OnInit {
|
||||
async load() {
|
||||
try {
|
||||
if (this.showUser) {
|
||||
const response = await this.organizationUserService.getAllUsers(this.params.organizationId);
|
||||
const response = await this.organizationUserApiService.getAllUsers(
|
||||
this.params.organizationId,
|
||||
);
|
||||
response.data.forEach((u) => {
|
||||
const name = this.userNamePipe.transform(u);
|
||||
this.orgUsersIdMap.set(u.id, { name: name, email: u.email });
|
||||
|
||||
@@ -2,10 +2,10 @@ import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { concatMap, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { EventSystemUser } from "@bitwarden/common/enums";
|
||||
@@ -49,7 +49,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe
|
||||
logService: LogService,
|
||||
private userNamePipe: UserNamePipe,
|
||||
private organizationService: OrganizationService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private providerService: ProviderService,
|
||||
fileDownloadService: FileDownloadService,
|
||||
toastService: ToastService,
|
||||
@@ -83,7 +83,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe
|
||||
}
|
||||
|
||||
async load() {
|
||||
const response = await this.organizationUserService.getAllUsers(this.organizationId);
|
||||
const response = await this.organizationUserApiService.getAllUsers(this.organizationId);
|
||||
response.data.forEach((u) => {
|
||||
const name = this.userNamePipe.transform(u);
|
||||
this.orgUsersUserIdMap.set(u.userId, { name: name, email: u.email });
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
takeUntil,
|
||||
} from "rxjs";
|
||||
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
@@ -131,7 +131,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
private get orgMembers$(): Observable<Array<AccessItemView & { userId: UserId }>> {
|
||||
return from(this.organizationUserService.getAllUsers(this.organizationId)).pipe(
|
||||
return from(this.organizationUserApiService.getAllUsers(this.organizationId)).pipe(
|
||||
map((response) =>
|
||||
response.data.map((m) => ({
|
||||
id: m.id,
|
||||
@@ -202,7 +202,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
|
||||
@Inject(DIALOG_DATA) private params: GroupAddEditDialogParams,
|
||||
private dialogRef: DialogRef<GroupAddEditDialogResultType>,
|
||||
private apiService: ApiService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private groupService: GroupService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Directive, OnInit } from "@angular/core";
|
||||
import {
|
||||
OrganizationUserBulkPublicKeyResponse,
|
||||
OrganizationUserBulkResponse,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { ProviderUserBulkPublicKeyResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk-public-key.response";
|
||||
import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Directive } from "@angular/core";
|
||||
|
||||
import { OrganizationUserBulkResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
|
||||
import { OrganizationUserBulkResponse } from "@bitwarden/admin-console/common";
|
||||
import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserBulkConfirmRequest,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserBulkConfirmRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -40,7 +42,7 @@ export class BulkConfirmComponent implements OnInit {
|
||||
@Inject(DIALOG_DATA) protected data: BulkConfirmDialogData,
|
||||
protected cryptoService: CryptoService,
|
||||
protected apiService: ApiService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private i18nService: I18nService,
|
||||
) {
|
||||
this.organizationId = data.organizationId;
|
||||
@@ -104,7 +106,7 @@ export class BulkConfirmComponent implements OnInit {
|
||||
}
|
||||
|
||||
protected async getPublicKeys() {
|
||||
return await this.organizationUserService.postOrganizationUsersPublicKey(
|
||||
return await this.organizationUserApiService.postOrganizationUsersPublicKey(
|
||||
this.organizationId,
|
||||
this.filteredUsers.map((user) => user.id),
|
||||
);
|
||||
@@ -116,7 +118,7 @@ export class BulkConfirmComponent implements OnInit {
|
||||
|
||||
protected async postConfirmRequest(userIdsWithKeys: any[]) {
|
||||
const request = new OrganizationUserBulkConfirmRequest(userIdsWithKeys);
|
||||
return await this.organizationUserService.postOrganizationUserBulkConfirm(
|
||||
return await this.organizationUserApiService.postOrganizationUserBulkConfirm(
|
||||
this.organizationId,
|
||||
request,
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService, TableDataSource, ToastService } from "@bitwarden/components";
|
||||
@@ -21,7 +21,7 @@ export class BulkEnableSecretsManagerDialogComponent implements OnInit {
|
||||
constructor(
|
||||
public dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) private data: BulkEnableSecretsManagerDialogData,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private toastService: ToastService,
|
||||
@@ -32,7 +32,7 @@ export class BulkEnableSecretsManagerDialogComponent implements OnInit {
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
await this.organizationUserService.putOrganizationUserBulkEnableSecretsManager(
|
||||
await this.organizationUserApiService.putOrganizationUserBulkEnableSecretsManager(
|
||||
this.data.orgId,
|
||||
this.dataSource.data.map((u) => u.id),
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
@@ -33,7 +33,7 @@ export class BulkRemoveComponent {
|
||||
@Inject(DIALOG_DATA) protected data: BulkRemoveDialogData,
|
||||
protected apiService: ApiService,
|
||||
protected i18nService: I18nService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
) {
|
||||
this.organizationId = data.organizationId;
|
||||
this.users = data.users;
|
||||
@@ -60,7 +60,7 @@ export class BulkRemoveComponent {
|
||||
};
|
||||
|
||||
protected async removeUsers() {
|
||||
return await this.organizationUserService.removeManyOrganizationUsers(
|
||||
return await this.organizationUserApiService.removeManyOrganizationUsers(
|
||||
this.organizationId,
|
||||
this.users.map((user) => user.id),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
@@ -32,7 +32,7 @@ export class BulkRestoreRevokeComponent {
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
@Inject(DIALOG_DATA) protected data: BulkRestoreDialogParams,
|
||||
) {
|
||||
this.isRevoking = data.isRevoking;
|
||||
@@ -66,12 +66,12 @@ export class BulkRestoreRevokeComponent {
|
||||
protected async performBulkUserAction() {
|
||||
const userIds = this.users.map((user) => user.id);
|
||||
if (this.isRevoking) {
|
||||
return await this.organizationUserService.revokeManyOrganizationUsers(
|
||||
return await this.organizationUserApiService.revokeManyOrganizationUsers(
|
||||
this.organizationId,
|
||||
userIds,
|
||||
);
|
||||
} else {
|
||||
return await this.organizationUserService.restoreManyOrganizationUsers(
|
||||
return await this.organizationUserApiService.restoreManyOrganizationUsers(
|
||||
this.organizationId,
|
||||
userIds,
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
|
||||
import { OrganizationUserBulkResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
|
||||
import { OrganizationUserBulkResponse } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
OrganizationUserStatusType,
|
||||
ProviderUserStatusType,
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
takeUntil,
|
||||
} from "rxjs";
|
||||
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import {
|
||||
OrganizationUserStatusType,
|
||||
OrganizationUserType,
|
||||
@@ -139,7 +139,7 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
private collectionAdminService: CollectionAdminService,
|
||||
private groupService: GroupService,
|
||||
private userService: UserAdminService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private dialogService: DialogService,
|
||||
private accountService: AccountService,
|
||||
organizationService: OrganizationService,
|
||||
@@ -491,7 +491,7 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
await this.organizationUserService.removeOrganizationUser(
|
||||
await this.organizationUserApiService.removeOrganizationUser(
|
||||
this.params.organizationId,
|
||||
this.params.organizationUserId,
|
||||
);
|
||||
@@ -528,7 +528,7 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
await this.organizationUserService.revokeOrganizationUser(
|
||||
await this.organizationUserApiService.revokeOrganizationUser(
|
||||
this.params.organizationId,
|
||||
this.params.organizationUserId,
|
||||
);
|
||||
@@ -547,7 +547,7 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.organizationUserService.restoreOrganizationUser(
|
||||
await this.organizationUserApiService.restoreOrganizationUser(
|
||||
this.params.organizationId,
|
||||
this.params.organizationUserId,
|
||||
);
|
||||
|
||||
@@ -13,15 +13,17 @@ import {
|
||||
switchMap,
|
||||
} from "rxjs";
|
||||
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserConfirmRequest,
|
||||
OrganizationUserUserDetailsResponse,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
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 { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserConfirmRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||
import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
|
||||
import { PolicyApiServiceAbstraction as PolicyApiService } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import {
|
||||
@@ -116,7 +118,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
private syncService: SyncService,
|
||||
private organizationService: OrganizationService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private router: Router,
|
||||
private groupService: GroupService,
|
||||
private collectionService: CollectionService,
|
||||
@@ -213,7 +215,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
let collectionsPromise: Promise<Map<string, string>>;
|
||||
|
||||
// We don't need both groups and collections for the table, so only load one
|
||||
const userPromise = this.organizationUserService.getAllUsers(this.organization.id, {
|
||||
const userPromise = this.organizationUserApiService.getAllUsers(this.organization.id, {
|
||||
includeGroups: this.organization.useGroups,
|
||||
includeCollections: !this.organization.useGroups,
|
||||
});
|
||||
@@ -270,19 +272,19 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
}
|
||||
|
||||
removeUser(id: string): Promise<void> {
|
||||
return this.organizationUserService.removeOrganizationUser(this.organization.id, id);
|
||||
return this.organizationUserApiService.removeOrganizationUser(this.organization.id, id);
|
||||
}
|
||||
|
||||
revokeUser(id: string): Promise<void> {
|
||||
return this.organizationUserService.revokeOrganizationUser(this.organization.id, id);
|
||||
return this.organizationUserApiService.revokeOrganizationUser(this.organization.id, id);
|
||||
}
|
||||
|
||||
restoreUser(id: string): Promise<void> {
|
||||
return this.organizationUserService.restoreOrganizationUser(this.organization.id, id);
|
||||
return this.organizationUserApiService.restoreOrganizationUser(this.organization.id, id);
|
||||
}
|
||||
|
||||
reinviteUser(id: string): Promise<void> {
|
||||
return this.organizationUserService.postOrganizationUserReinvite(this.organization.id, id);
|
||||
return this.organizationUserApiService.postOrganizationUserReinvite(this.organization.id, id);
|
||||
}
|
||||
|
||||
async confirmUser(user: OrganizationUserView, publicKey: Uint8Array): Promise<void> {
|
||||
@@ -290,7 +292,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey);
|
||||
const request = new OrganizationUserConfirmRequest();
|
||||
request.key = key.encryptedString;
|
||||
await this.organizationUserService.postOrganizationUserConfirm(
|
||||
await this.organizationUserApiService.postOrganizationUserConfirm(
|
||||
this.organization.id,
|
||||
user.id,
|
||||
request,
|
||||
@@ -585,7 +587,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
}
|
||||
|
||||
try {
|
||||
const response = this.organizationUserService.postManyOrganizationUserReinvite(
|
||||
const response = this.organizationUserApiService.postManyOrganizationUserReinvite(
|
||||
this.organization.id,
|
||||
filteredUsers.map((user) => user.id),
|
||||
);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserResetPasswordDetailsResponse,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserResetPasswordDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response";
|
||||
import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service";
|
||||
@@ -24,7 +26,7 @@ describe("OrganizationUserResetPasswordService", () => {
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
let organizationService: MockProxy<OrganizationService>;
|
||||
let organizationUserService: MockProxy<OrganizationUserService>;
|
||||
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
|
||||
let organizationApiService: MockProxy<OrganizationApiService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
|
||||
@@ -32,7 +34,7 @@ describe("OrganizationUserResetPasswordService", () => {
|
||||
cryptoService = mock<CryptoService>();
|
||||
encryptService = mock<EncryptService>();
|
||||
organizationService = mock<OrganizationService>();
|
||||
organizationUserService = mock<OrganizationUserService>();
|
||||
organizationUserApiService = mock<OrganizationUserApiService>();
|
||||
organizationApiService = mock<OrganizationApiService>();
|
||||
i18nService = mock<I18nService>();
|
||||
|
||||
@@ -40,7 +42,7 @@ describe("OrganizationUserResetPasswordService", () => {
|
||||
cryptoService,
|
||||
encryptService,
|
||||
organizationService,
|
||||
organizationUserService,
|
||||
organizationUserApiService,
|
||||
organizationApiService,
|
||||
i18nService,
|
||||
);
|
||||
@@ -112,7 +114,7 @@ describe("OrganizationUserResetPasswordService", () => {
|
||||
const mockOrgId = "test-org-id";
|
||||
|
||||
beforeEach(() => {
|
||||
organizationUserService.getOrganizationUserResetPasswordDetails.mockResolvedValue(
|
||||
organizationUserApiService.getOrganizationUserResetPasswordDetails.mockResolvedValue(
|
||||
new OrganizationUserResetPasswordDetailsResponse({
|
||||
kdf: KdfType.PBKDF2_SHA256,
|
||||
kdfIterations: 5000,
|
||||
@@ -140,11 +142,11 @@ describe("OrganizationUserResetPasswordService", () => {
|
||||
|
||||
it("should reset the user's master password", async () => {
|
||||
await sut.resetMasterPassword(mockNewMP, mockEmail, mockOrgUserId, mockOrgId);
|
||||
expect(organizationUserService.putOrganizationUserResetPassword).toHaveBeenCalled();
|
||||
expect(organizationUserApiService.putOrganizationUserResetPassword).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should throw an error if the user details are null", async () => {
|
||||
organizationUserService.getOrganizationUserResetPasswordDetails.mockResolvedValue(null);
|
||||
organizationUserApiService.getOrganizationUserResetPasswordDetails.mockResolvedValue(null);
|
||||
await expect(
|
||||
sut.resetMasterPassword(mockNewMP, mockEmail, mockOrgUserId, mockOrgId),
|
||||
).rejects.toThrow();
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserResetPasswordRequest,
|
||||
OrganizationUserResetPasswordWithIdRequest,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { UserKeyRotationDataProvider } from "@bitwarden/auth/common";
|
||||
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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import {
|
||||
OrganizationUserResetPasswordRequest,
|
||||
OrganizationUserResetPasswordWithIdRequest,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||
import {
|
||||
Argon2KdfConfig,
|
||||
KdfConfig,
|
||||
@@ -33,7 +33,7 @@ export class OrganizationUserResetPasswordService
|
||||
private cryptoService: CryptoService,
|
||||
private encryptService: EncryptService,
|
||||
private organizationService: OrganizationService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private i18nService: I18nService,
|
||||
) {}
|
||||
@@ -76,7 +76,7 @@ export class OrganizationUserResetPasswordService
|
||||
orgUserId: string,
|
||||
orgId: string,
|
||||
): Promise<void> {
|
||||
const response = await this.organizationUserService.getOrganizationUserResetPasswordDetails(
|
||||
const response = await this.organizationUserApiService.getOrganizationUserResetPasswordDetails(
|
||||
orgId,
|
||||
orgUserId,
|
||||
);
|
||||
@@ -128,7 +128,11 @@ export class OrganizationUserResetPasswordService
|
||||
request.newMasterPasswordHash = newMasterKeyHash;
|
||||
|
||||
// Change user's password
|
||||
await this.organizationUserService.putOrganizationUserResetPassword(orgId, orgUserId, request);
|
||||
await this.organizationUserApiService.putOrganizationUserResetPassword(
|
||||
orgId,
|
||||
orgUserId,
|
||||
request,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Meta, StoryObj } from "@storybook/angular";
|
||||
|
||||
import { AccessSelectorComponent, PermissionMode } from "./access-selector.component";
|
||||
import { AccessItemType, AccessItemValue } from "./access-selector.models";
|
||||
import { default as baseComponentDefinition } from "./access-selector.stories";
|
||||
import { actionsData, itemsFactory } from "./storybook-utils";
|
||||
|
||||
/**
|
||||
* Displays the Access Selector in a dialog.
|
||||
*/
|
||||
export default {
|
||||
title: "Web/Organizations/Access Selector/Dialog",
|
||||
decorators: baseComponentDefinition.decorators,
|
||||
} as Meta;
|
||||
|
||||
type Story = StoryObj<AccessSelectorComponent & { initialValue: AccessItemValue[] }>;
|
||||
|
||||
const render: Story["render"] = (args) => ({
|
||||
props: {
|
||||
items: [],
|
||||
valueChanged: actionsData.onValueChanged,
|
||||
initialValue: [],
|
||||
...args,
|
||||
},
|
||||
template: `
|
||||
<bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding">
|
||||
<span bitDialogTitle>Access selector</span>
|
||||
<span bitDialogContent>
|
||||
<bit-access-selector
|
||||
(ngModelChange)="valueChanged($event)"
|
||||
[ngModel]="initialValue"
|
||||
[items]="items"
|
||||
[disabled]="disabled"
|
||||
[columnHeader]="columnHeader"
|
||||
[showGroupColumn]="showGroupColumn"
|
||||
[selectorLabelText]="selectorLabelText"
|
||||
[selectorHelpText]="selectorHelpText"
|
||||
[emptySelectionText]="emptySelectionText"
|
||||
[permissionMode]="permissionMode"
|
||||
[showMemberRoles]="showMemberRoles"
|
||||
></bit-access-selector>
|
||||
</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary">Save</button>
|
||||
<button bitButton buttonType="secondary">Cancel</button>
|
||||
<button
|
||||
class="tw-ml-auto"
|
||||
bitIconButton="bwi-trash"
|
||||
buttonType="danger"
|
||||
size="default"
|
||||
title="Delete"
|
||||
aria-label="Delete"></button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
`,
|
||||
});
|
||||
|
||||
const dialogAccessItems = itemsFactory(10, AccessItemType.Collection);
|
||||
|
||||
export const Dialog: Story = {
|
||||
args: {
|
||||
permissionMode: PermissionMode.Edit,
|
||||
showMemberRoles: false,
|
||||
showGroupColumn: true,
|
||||
columnHeader: "Collection",
|
||||
selectorLabelText: "Select Collections",
|
||||
selectorHelpText: "Some helper text describing what this does",
|
||||
emptySelectionText: "No collections added",
|
||||
disabled: false,
|
||||
initialValue: [] as any[],
|
||||
items: dialogAccessItems,
|
||||
},
|
||||
render,
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import { FormBuilder, FormControl, FormGroup } from "@angular/forms";
|
||||
import { Meta, StoryObj } from "@storybook/angular";
|
||||
|
||||
import { AccessSelectorComponent, PermissionMode } from "./access-selector.component";
|
||||
import { AccessItemType, AccessItemValue } from "./access-selector.models";
|
||||
import { default as baseComponentDefinition } from "./access-selector.stories";
|
||||
import { actionsData, itemsFactory } from "./storybook-utils";
|
||||
|
||||
/**
|
||||
* Displays the Access Selector embedded in a reactive form.
|
||||
*/
|
||||
export default {
|
||||
title: "Web/Organizations/Access Selector/Reactive form",
|
||||
decorators: baseComponentDefinition.decorators,
|
||||
argTypes: {
|
||||
formObj: { table: { disable: true } },
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
type FormObj = { formObj: FormGroup<{ formItems: FormControl<AccessItemValue[]> }> };
|
||||
type Story = StoryObj<AccessSelectorComponent & FormObj>;
|
||||
|
||||
const fb = new FormBuilder();
|
||||
|
||||
const render: Story["render"] = (args) => ({
|
||||
props: {
|
||||
items: [],
|
||||
onSubmit: actionsData.onSubmit,
|
||||
...args,
|
||||
},
|
||||
template: `
|
||||
<form [formGroup]="formObj" (ngSubmit)="onSubmit(formObj.controls.formItems.value)">
|
||||
<bit-access-selector
|
||||
formControlName="formItems"
|
||||
[items]="items"
|
||||
[columnHeader]="columnHeader"
|
||||
[selectorLabelText]="selectorLabelText"
|
||||
[selectorHelpText]="selectorHelpText"
|
||||
[emptySelectionText]="emptySelectionText"
|
||||
[permissionMode]="permissionMode"
|
||||
[showMemberRoles]="showMemberRoles"
|
||||
></bit-access-selector>
|
||||
<button type="submit" bitButton buttonType="primary" class="tw-mt-5">Submit</button>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
|
||||
const sampleMembers = itemsFactory(10, AccessItemType.Member);
|
||||
const sampleGroups = itemsFactory(6, AccessItemType.Group);
|
||||
|
||||
export const ReactiveForm: Story = {
|
||||
args: {
|
||||
formObj: fb.group({ formItems: [[{ id: "1g", type: AccessItemType.Group }]] }),
|
||||
permissionMode: PermissionMode.Edit,
|
||||
showMemberRoles: false,
|
||||
columnHeader: "Groups/Members",
|
||||
selectorLabelText: "Select groups and members",
|
||||
selectorHelpText:
|
||||
"Permissions set for a member will replace permissions set by that member's group",
|
||||
emptySelectionText: "No members or groups added",
|
||||
items: sampleGroups.concat(sampleMembers),
|
||||
},
|
||||
render,
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
|
||||
import { OrganizationUserUserDetailsResponse } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
OrganizationUserStatusType,
|
||||
OrganizationUserType,
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { importProvidersFrom } from "@angular/core";
|
||||
import { FormBuilder, FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import {
|
||||
OrganizationUserStatusType,
|
||||
OrganizationUserType,
|
||||
} from "@bitwarden/common/admin-console/enums";
|
||||
import {
|
||||
AvatarModule,
|
||||
BadgeModule,
|
||||
@@ -21,10 +16,20 @@ import {
|
||||
|
||||
import { PreloadedEnglishI18nModule } from "../../../../../core/tests";
|
||||
|
||||
import { AccessSelectorComponent } from "./access-selector.component";
|
||||
import { AccessItemType, AccessItemView, CollectionPermission } from "./access-selector.models";
|
||||
import { AccessSelectorComponent, PermissionMode } from "./access-selector.component";
|
||||
import { AccessItemType, AccessItemValue, CollectionPermission } from "./access-selector.models";
|
||||
import { actionsData, itemsFactory } from "./storybook-utils";
|
||||
import { UserTypePipe } from "./user-type.pipe";
|
||||
|
||||
/**
|
||||
* The Access Selector is used to view and edit:
|
||||
* - member and group access to collections
|
||||
* - members assigned to groups
|
||||
*
|
||||
* It is highly configurable in order to display these relationships from each perspective. For example, you can
|
||||
* manage member-group relationships from the perspective of a particular member (showing all their groups) or a
|
||||
* particular group (showing all its members).
|
||||
*/
|
||||
export default {
|
||||
title: "Web/Organizations/Access Selector",
|
||||
decorators: [
|
||||
@@ -49,65 +54,16 @@ export default {
|
||||
providers: [importProvidersFrom(PreloadedEnglishI18nModule)],
|
||||
}),
|
||||
],
|
||||
parameters: {},
|
||||
argTypes: {
|
||||
formObj: { table: { disable: true } },
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
// TODO: This is a workaround since this story does weird things.
|
||||
type Story = StoryObj<any>;
|
||||
|
||||
const actionsData = {
|
||||
onValueChanged: action("onValueChanged"),
|
||||
onSubmit: action("onSubmit"),
|
||||
};
|
||||
|
||||
/**
|
||||
* Factory to help build semi-realistic looking items
|
||||
* @param n - The number of items to build
|
||||
* @param type - Which type to build
|
||||
*/
|
||||
const itemsFactory = (n: number, type: AccessItemType) => {
|
||||
return [...Array(n)].map((_: unknown, id: number) => {
|
||||
const item: AccessItemView = {
|
||||
id: id.toString(),
|
||||
type: type,
|
||||
} as AccessItemView;
|
||||
|
||||
switch (item.type) {
|
||||
case AccessItemType.Collection:
|
||||
item.labelName = item.listName = `Collection ${id}`;
|
||||
item.id = item.id + "c";
|
||||
item.parentGrouping = "Collection Parent Group " + ((id % 2) + 1);
|
||||
break;
|
||||
case AccessItemType.Group:
|
||||
item.labelName = item.listName = `Group ${id}`;
|
||||
item.id = item.id + "g";
|
||||
break;
|
||||
case AccessItemType.Member:
|
||||
item.id = item.id + "m";
|
||||
item.email = `member${id}@email.com`;
|
||||
item.status = id % 3 == 0 ? 0 : 2;
|
||||
item.labelName = item.status == 2 ? `Member ${id}` : item.email;
|
||||
item.listName = item.status == 2 ? `${item.labelName} (${item.email})` : item.email;
|
||||
item.role = id % 5;
|
||||
break;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
};
|
||||
type Story = StoryObj<AccessSelectorComponent & { initialValue: AccessItemValue[] }>;
|
||||
|
||||
const sampleMembers = itemsFactory(10, AccessItemType.Member);
|
||||
const sampleGroups = itemsFactory(6, AccessItemType.Group);
|
||||
|
||||
// TODO: These renders are badly handled but storybook has made it more difficult to use multiple renders in a single story.
|
||||
const StandaloneAccessSelectorRender = (args: any) => ({
|
||||
const render: Story["render"] = (args) => ({
|
||||
props: {
|
||||
items: [],
|
||||
valueChanged: actionsData.onValueChanged,
|
||||
initialValue: [],
|
||||
...args,
|
||||
},
|
||||
template: `
|
||||
@@ -127,49 +83,8 @@ const StandaloneAccessSelectorRender = (args: any) => ({
|
||||
`,
|
||||
});
|
||||
|
||||
const DialogAccessSelectorRender = (args: any) => ({
|
||||
props: {
|
||||
items: [],
|
||||
valueChanged: actionsData.onValueChanged,
|
||||
initialValue: [],
|
||||
...args,
|
||||
},
|
||||
template: `
|
||||
<bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding">
|
||||
<span bitDialogTitle>Access selector</span>
|
||||
<span bitDialogContent>
|
||||
<bit-access-selector
|
||||
(ngModelChange)="valueChanged($event)"
|
||||
[ngModel]="initialValue"
|
||||
[items]="items"
|
||||
[disabled]="disabled"
|
||||
[columnHeader]="columnHeader"
|
||||
[showGroupColumn]="showGroupColumn"
|
||||
[selectorLabelText]="selectorLabelText"
|
||||
[selectorHelpText]="selectorHelpText"
|
||||
[emptySelectionText]="emptySelectionText"
|
||||
[permissionMode]="permissionMode"
|
||||
[showMemberRoles]="showMemberRoles"
|
||||
></bit-access-selector>
|
||||
</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary">Save</button>
|
||||
<button bitButton buttonType="secondary">Cancel</button>
|
||||
<button
|
||||
class="tw-ml-auto"
|
||||
bitIconButton="bwi-trash"
|
||||
buttonType="danger"
|
||||
size="default"
|
||||
title="Delete"
|
||||
aria-label="Delete"></button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
`,
|
||||
});
|
||||
|
||||
const dialogAccessItems = itemsFactory(10, AccessItemType.Collection);
|
||||
|
||||
const memberCollectionAccessItems = itemsFactory(3, AccessItemType.Collection).concat([
|
||||
const memberCollectionAccessItems = itemsFactory(5, AccessItemType.Collection).concat([
|
||||
// These represent collection access via a group
|
||||
{
|
||||
id: "c1-group1",
|
||||
type: AccessItemType.Collection,
|
||||
@@ -190,25 +105,25 @@ const memberCollectionAccessItems = itemsFactory(3, AccessItemType.Collection).c
|
||||
},
|
||||
]);
|
||||
|
||||
export const Dialog: Story = {
|
||||
args: {
|
||||
permissionMode: "edit",
|
||||
showMemberRoles: false,
|
||||
showGroupColumn: true,
|
||||
columnHeader: "Collection",
|
||||
selectorLabelText: "Select Collections",
|
||||
selectorHelpText: "Some helper text describing what this does",
|
||||
emptySelectionText: "No collections added",
|
||||
disabled: false,
|
||||
initialValue: [] as any[],
|
||||
items: dialogAccessItems,
|
||||
},
|
||||
render: DialogAccessSelectorRender,
|
||||
};
|
||||
// Simulate the current user not having permission to change access to this collection
|
||||
// TODO: currently the member dialog duplicates the AccessItemValue.permission on the
|
||||
// AccessItemView.readonlyPermission, this will be refactored to reduce this duplication:
|
||||
// https://bitwarden.atlassian.net/browse/PM-11590
|
||||
memberCollectionAccessItems[4].readonly = true;
|
||||
memberCollectionAccessItems[4].readonlyPermission = CollectionPermission.Manage;
|
||||
|
||||
/**
|
||||
* Displays a member's collection access.
|
||||
*
|
||||
* This is currently used in the **Member dialog -> Collections tab**. Note that it includes collection access that the
|
||||
* member has via a group.
|
||||
*
|
||||
* This is also used in the **Groups dialog -> Collections tab** to show a group's collection access and in this
|
||||
* case the Group column is hidden.
|
||||
*/
|
||||
export const MemberCollectionAccess: Story = {
|
||||
args: {
|
||||
permissionMode: "edit",
|
||||
permissionMode: PermissionMode.Edit,
|
||||
showMemberRoles: false,
|
||||
showGroupColumn: true,
|
||||
columnHeader: "Collection",
|
||||
@@ -216,22 +131,41 @@ export const MemberCollectionAccess: Story = {
|
||||
selectorHelpText: "Some helper text describing what this does",
|
||||
emptySelectionText: "No collections added",
|
||||
disabled: false,
|
||||
initialValue: [],
|
||||
initialValue: [
|
||||
{
|
||||
id: "4c",
|
||||
type: AccessItemType.Collection,
|
||||
permission: CollectionPermission.Manage,
|
||||
},
|
||||
{
|
||||
id: "2c",
|
||||
type: AccessItemType.Collection,
|
||||
permission: CollectionPermission.Edit,
|
||||
},
|
||||
],
|
||||
items: memberCollectionAccessItems,
|
||||
},
|
||||
render: StandaloneAccessSelectorRender,
|
||||
render,
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays the groups a member is assigned to.
|
||||
*
|
||||
* This is currently used in the **Member dialog -> Groups tab**.
|
||||
*/
|
||||
export const MemberGroupAccess: Story = {
|
||||
args: {
|
||||
permissionMode: "readonly",
|
||||
permissionMode: PermissionMode.Hidden,
|
||||
showMemberRoles: false,
|
||||
columnHeader: "Groups",
|
||||
selectorLabelText: "Select Groups",
|
||||
selectorHelpText: "Some helper text describing what this does",
|
||||
emptySelectionText: "No groups added",
|
||||
disabled: false,
|
||||
initialValue: [{ id: "3g" }, { id: "0g" }],
|
||||
initialValue: [
|
||||
{ id: "3g", type: AccessItemType.Group },
|
||||
{ id: "0g", type: AccessItemType.Group },
|
||||
],
|
||||
items: itemsFactory(4, AccessItemType.Group).concat([
|
||||
{
|
||||
id: "admin",
|
||||
@@ -241,27 +175,40 @@ export const MemberGroupAccess: Story = {
|
||||
},
|
||||
]),
|
||||
},
|
||||
render: StandaloneAccessSelectorRender,
|
||||
render,
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays the members assigned to a group.
|
||||
*
|
||||
* This is currently used in the **Group dialog -> Members tab**.
|
||||
*/
|
||||
export const GroupMembersAccess: Story = {
|
||||
args: {
|
||||
permissionMode: "hidden",
|
||||
permissionMode: PermissionMode.Hidden,
|
||||
showMemberRoles: true,
|
||||
columnHeader: "Members",
|
||||
selectorLabelText: "Select Members",
|
||||
selectorHelpText: "Some helper text describing what this does",
|
||||
emptySelectionText: "No members added",
|
||||
disabled: false,
|
||||
initialValue: [{ id: "2m" }, { id: "0m" }],
|
||||
initialValue: [
|
||||
{ id: "2m", type: AccessItemType.Member },
|
||||
{ id: "0m", type: AccessItemType.Member },
|
||||
],
|
||||
items: sampleMembers,
|
||||
},
|
||||
render: StandaloneAccessSelectorRender,
|
||||
render,
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays the members and groups assigned to a collection.
|
||||
*
|
||||
* This is currently used in the **Collection dialog -> Access tab**.
|
||||
*/
|
||||
export const CollectionAccess: Story = {
|
||||
args: {
|
||||
permissionMode: "edit",
|
||||
permissionMode: PermissionMode.Edit,
|
||||
showMemberRoles: false,
|
||||
columnHeader: "Groups/Members",
|
||||
selectorLabelText: "Select groups and members",
|
||||
@@ -270,68 +217,38 @@ export const CollectionAccess: Story = {
|
||||
emptySelectionText: "No members or groups added",
|
||||
disabled: false,
|
||||
initialValue: [
|
||||
{ id: "3g", permission: CollectionPermission.EditExceptPass },
|
||||
{ id: "0m", permission: CollectionPermission.View },
|
||||
{ id: "3g", type: AccessItemType.Group, permission: CollectionPermission.EditExceptPass },
|
||||
{ id: "0m", type: AccessItemType.Member, permission: CollectionPermission.View },
|
||||
{ id: "7m", type: AccessItemType.Member, permission: CollectionPermission.Manage },
|
||||
],
|
||||
items: sampleGroups.concat(sampleMembers).concat([
|
||||
{
|
||||
id: "admin-group",
|
||||
type: AccessItemType.Group,
|
||||
listName: "Admin Group",
|
||||
labelName: "Admin Group",
|
||||
readonly: true,
|
||||
},
|
||||
{
|
||||
id: "admin-member",
|
||||
type: AccessItemType.Member,
|
||||
listName: "Admin Member (admin@email.com)",
|
||||
labelName: "Admin Member",
|
||||
status: OrganizationUserStatusType.Confirmed,
|
||||
role: OrganizationUserType.Admin,
|
||||
email: "admin@email.com",
|
||||
readonly: true,
|
||||
},
|
||||
]),
|
||||
},
|
||||
render: StandaloneAccessSelectorRender,
|
||||
};
|
||||
|
||||
const fb = new FormBuilder();
|
||||
|
||||
const ReactiveFormAccessSelectorRender = (args: any) => ({
|
||||
props: {
|
||||
items: [],
|
||||
onSubmit: actionsData.onSubmit,
|
||||
...args,
|
||||
},
|
||||
template: `
|
||||
<form [formGroup]="formObj" (ngSubmit)="onSubmit(formObj.controls.formItems.value)">
|
||||
<bit-access-selector
|
||||
formControlName="formItems"
|
||||
[items]="items"
|
||||
[columnHeader]="columnHeader"
|
||||
[selectorLabelText]="selectorLabelText"
|
||||
[selectorHelpText]="selectorHelpText"
|
||||
[emptySelectionText]="emptySelectionText"
|
||||
[permissionMode]="permissionMode"
|
||||
[showMemberRoles]="showMemberRoles"
|
||||
></bit-access-selector>
|
||||
<button type="submit" bitButton buttonType="primary" class="tw-mt-5">Submit</button>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
|
||||
export const ReactiveForm: Story = {
|
||||
args: {
|
||||
formObj: fb.group({ formItems: [[{ id: "1g" }]] }),
|
||||
permissionMode: "edit",
|
||||
showMemberRoles: false,
|
||||
columnHeader: "Groups/Members",
|
||||
selectorLabelText: "Select groups and members",
|
||||
selectorHelpText:
|
||||
"Permissions set for a member will replace permissions set by that member's group",
|
||||
emptySelectionText: "No members or groups added",
|
||||
items: sampleGroups.concat(sampleMembers),
|
||||
},
|
||||
render: ReactiveFormAccessSelectorRender,
|
||||
render,
|
||||
};
|
||||
|
||||
// TODO: currently the collection dialog duplicates the AccessItemValue.permission on the
|
||||
// AccessItemView.readonlyPermission, this will be refactored to reduce this duplication:
|
||||
// https://bitwarden.atlassian.net/browse/PM-11590
|
||||
const disabledMembers = itemsFactory(3, AccessItemType.Member);
|
||||
disabledMembers[1].readonlyPermission = CollectionPermission.Manage;
|
||||
disabledMembers[2].readonlyPermission = CollectionPermission.View;
|
||||
|
||||
const disabledGroups = itemsFactory(2, AccessItemType.Group);
|
||||
disabledGroups[0].readonlyPermission = CollectionPermission.ViewExceptPass;
|
||||
|
||||
/**
|
||||
* Displays the members and groups assigned to a collection when the control is in a disabled state.
|
||||
*/
|
||||
export const DisabledCollectionAccess: Story = {
|
||||
args: {
|
||||
...CollectionAccess.args,
|
||||
disabled: true,
|
||||
items: disabledGroups.concat(disabledMembers),
|
||||
initialValue: [
|
||||
{ id: "1m", type: AccessItemType.Member, permission: CollectionPermission.Manage },
|
||||
{ id: "2m", type: AccessItemType.Member, permission: CollectionPermission.View },
|
||||
{ id: "0g", type: AccessItemType.Group, permission: CollectionPermission.ViewExceptPass },
|
||||
],
|
||||
},
|
||||
render,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { action } from "@storybook/addon-actions";
|
||||
|
||||
import { AccessItemType, AccessItemView } from "./access-selector.models";
|
||||
|
||||
export const actionsData = {
|
||||
onValueChanged: action("onValueChanged"),
|
||||
onSubmit: action("onSubmit"),
|
||||
};
|
||||
|
||||
/**
|
||||
* Factory to help build semi-realistic looking items
|
||||
* @param n - The number of items to build
|
||||
* @param type - Which type to build
|
||||
*/
|
||||
export const itemsFactory = (n: number, type: AccessItemType) => {
|
||||
return [...Array(n)].map((_: unknown, id: number) => {
|
||||
const item: AccessItemView = {
|
||||
id: id.toString(),
|
||||
type: type,
|
||||
} as AccessItemView;
|
||||
|
||||
switch (item.type) {
|
||||
case AccessItemType.Collection:
|
||||
item.labelName = item.listName = `Collection ${id}`;
|
||||
item.id = item.id + "c";
|
||||
item.parentGrouping = "Collection Parent Group " + ((id % 2) + 1);
|
||||
break;
|
||||
case AccessItemType.Group:
|
||||
item.labelName = item.listName = `Group ${id}`;
|
||||
item.id = item.id + "g";
|
||||
break;
|
||||
case AccessItemType.Member:
|
||||
item.id = item.id + "m";
|
||||
item.email = `member${id}@email.com`;
|
||||
item.status = id % 3 == 0 ? 0 : 2;
|
||||
item.labelName = item.status == 2 ? `Member ${id}` : item.email;
|
||||
item.listName = item.status == 2 ? `${item.labelName} (${item.email})` : item.email;
|
||||
item.role = id % 5;
|
||||
break;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserResetPasswordEnrollmentRequest,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { UserVerificationDialogComponent } from "@bitwarden/auth/angular";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification";
|
||||
@@ -23,7 +25,7 @@ export class EnrollMasterPasswordReset {
|
||||
dialogService: DialogService,
|
||||
data: EnrollMasterPasswordResetData,
|
||||
resetPasswordService: OrganizationUserResetPasswordService,
|
||||
organizationUserService: OrganizationUserService,
|
||||
organizationUserApiService: OrganizationUserApiService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
syncService: SyncService,
|
||||
@@ -50,7 +52,7 @@ export class EnrollMasterPasswordReset {
|
||||
|
||||
// Process the enrollment request, which is an endpoint that is
|
||||
// gated by a server-side check of the master password hash
|
||||
await organizationUserService.putOrganizationUserResetPasswordEnrollment(
|
||||
await organizationUserApiService.putOrganizationUserResetPasswordEnrollment(
|
||||
data.organization.id,
|
||||
data.organization.userId,
|
||||
request,
|
||||
|
||||
@@ -44,8 +44,8 @@ export class HintComponent extends BaseHintComponent implements OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
async ngOnInit(): Promise<void> {
|
||||
await super.ngOnInit();
|
||||
this.emailFormControl.setValue(this.email);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||
import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/admin-console/common";
|
||||
import { WebauthnRotateCredentialRequest } from "@bitwarden/common/auth/models/request/webauthn-rotate-credential.request";
|
||||
import { SendWithIdRequest } from "@bitwarden/common/src/tools/send/models/request/send-with-id.request";
|
||||
import { CipherWithIdRequest } from "@bitwarden/common/src/vault/models/request/cipher-with-id.request";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||
import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/admin-console/common";
|
||||
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { WebauthnRotateCredentialRequest } from "@bitwarden/common/auth/models/request/webauthn-rotate-credential.request";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { FakeGlobalStateProvider } from "@bitwarden/common/../spec/fake-state-provider";
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
@@ -35,7 +35,7 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
let policyService: MockProxy<PolicyService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>;
|
||||
let organizationUserService: MockProxy<OrganizationUserService>;
|
||||
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
let globalStateProvider: FakeGlobalStateProvider;
|
||||
let globalState: FakeGlobalState<OrganizationInvite>;
|
||||
@@ -49,7 +49,7 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
policyService = mock();
|
||||
logService = mock();
|
||||
organizationApiService = mock();
|
||||
organizationUserService = mock();
|
||||
organizationUserApiService = mock();
|
||||
i18nService = mock();
|
||||
globalStateProvider = new FakeGlobalStateProvider();
|
||||
globalState = globalStateProvider.getFake(ORGANIZATION_INVITE);
|
||||
@@ -63,7 +63,7 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
policyService,
|
||||
logService,
|
||||
organizationApiService,
|
||||
organizationUserService,
|
||||
organizationUserApiService,
|
||||
i18nService,
|
||||
globalStateProvider,
|
||||
);
|
||||
@@ -85,10 +85,10 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
const result = await sut.validateAndAcceptInvite(invite);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(organizationUserService.postOrganizationUserAcceptInit).toHaveBeenCalled();
|
||||
expect(organizationUserApiService.postOrganizationUserAcceptInit).toHaveBeenCalled();
|
||||
expect(apiService.refreshIdentityToken).toHaveBeenCalled();
|
||||
expect(globalState.nextMock).toHaveBeenCalledWith(null);
|
||||
expect(organizationUserService.postOrganizationUserAccept).not.toHaveBeenCalled();
|
||||
expect(organizationUserApiService.postOrganizationUserAccept).not.toHaveBeenCalled();
|
||||
expect(authService.logOut).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -133,10 +133,10 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
const result = await sut.validateAndAcceptInvite(invite);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(organizationUserService.postOrganizationUserAccept).toHaveBeenCalled();
|
||||
expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled();
|
||||
expect(apiService.refreshIdentityToken).toHaveBeenCalled();
|
||||
expect(globalState.nextMock).toHaveBeenCalledWith(null);
|
||||
expect(organizationUserService.postOrganizationUserAcceptInit).not.toHaveBeenCalled();
|
||||
expect(organizationUserApiService.postOrganizationUserAcceptInit).not.toHaveBeenCalled();
|
||||
expect(authService.logOut).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -161,8 +161,8 @@ describe("AcceptOrganizationInviteService", () => {
|
||||
const result = await sut.validateAndAcceptInvite(invite);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(organizationUserService.postOrganizationUserAccept).toHaveBeenCalled();
|
||||
expect(organizationUserService.postOrganizationUserAcceptInit).not.toHaveBeenCalled();
|
||||
expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled();
|
||||
expect(organizationUserApiService.postOrganizationUserAcceptInit).not.toHaveBeenCalled();
|
||||
expect(authService.logOut).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { BehaviorSubject, firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserAcceptRequest,
|
||||
OrganizationUserAcceptInitRequest,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
@@ -58,7 +58,7 @@ export class AcceptOrganizationInviteService {
|
||||
private readonly policyService: PolicyService,
|
||||
private readonly logService: LogService,
|
||||
private readonly organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private readonly organizationUserService: OrganizationUserService,
|
||||
private readonly organizationUserApiService: OrganizationUserApiService,
|
||||
private readonly i18nService: I18nService,
|
||||
private readonly globalStateProvider: GlobalStateProvider,
|
||||
) {
|
||||
@@ -121,7 +121,7 @@ export class AcceptOrganizationInviteService {
|
||||
|
||||
private async acceptAndInitOrganization(invite: OrganizationInvite): Promise<void> {
|
||||
await this.prepareAcceptAndInitRequest(invite).then((request) =>
|
||||
this.organizationUserService.postOrganizationUserAcceptInit(
|
||||
this.organizationUserApiService.postOrganizationUserAcceptInit(
|
||||
invite.organizationId,
|
||||
invite.organizationUserId,
|
||||
request,
|
||||
@@ -156,7 +156,7 @@ export class AcceptOrganizationInviteService {
|
||||
|
||||
private async accept(invite: OrganizationInvite): Promise<void> {
|
||||
await this.prepareAcceptRequest(invite).then((request) =>
|
||||
this.organizationUserService.postOrganizationUserAccept(
|
||||
this.organizationUserApiService.postOrganizationUserAccept(
|
||||
invite.organizationId,
|
||||
invite.organizationUserId,
|
||||
request,
|
||||
|
||||
@@ -220,7 +220,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
|
||||
this.dialogService,
|
||||
{ data: result },
|
||||
);
|
||||
this.twoFactorSetupSubscription = webAuthnComp.componentInstance.onChangeStatus
|
||||
this.twoFactorSetupSubscription = webAuthnComp.componentInstance.onUpdated
|
||||
.pipe(first(), takeUntil(this.destroy$))
|
||||
.subscribe((enabled: boolean) => {
|
||||
webAuthnComp.close();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, EventEmitter, Inject, NgZone, Output } from "@angular/core";
|
||||
import { Component, Inject, NgZone } from "@angular/core";
|
||||
import { FormControl, FormGroup } from "@angular/forms";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
@@ -33,7 +33,6 @@ interface Key {
|
||||
templateUrl: "two-factor-webauthn.component.html",
|
||||
})
|
||||
export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent {
|
||||
@Output() onChangeStatus = new EventEmitter<boolean>();
|
||||
type = TwoFactorProviderType.WebAuthn;
|
||||
name: string;
|
||||
keys: Key[];
|
||||
@@ -85,34 +84,33 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent {
|
||||
// Should never happen.
|
||||
return Promise.reject();
|
||||
}
|
||||
return this.enable();
|
||||
};
|
||||
|
||||
protected async enable() {
|
||||
const request = await this.buildRequestModel(UpdateTwoFactorWebAuthnRequest);
|
||||
request.deviceResponse = this.webAuthnResponse;
|
||||
request.id = this.keyIdAvailable;
|
||||
request.name = this.formGroup.value.name;
|
||||
|
||||
return this.enableWebAuth(request);
|
||||
};
|
||||
|
||||
private enableWebAuth(request: any) {
|
||||
return super.enable(async () => {
|
||||
this.formPromise = this.apiService.putTwoFactorWebAuthn(request);
|
||||
const response = await this.formPromise;
|
||||
this.processResponse(response);
|
||||
const response = await this.apiService.putTwoFactorWebAuthn(request);
|
||||
this.processResponse(response);
|
||||
this.toastService.showToast({
|
||||
title: this.i18nService.t("success"),
|
||||
message: this.i18nService.t("twoFactorProviderEnabled"),
|
||||
variant: "success",
|
||||
});
|
||||
this.onUpdated.emit(response.enabled);
|
||||
}
|
||||
|
||||
disable = async () => {
|
||||
await this.disableWebAuth();
|
||||
await this.disableMethod();
|
||||
if (!this.enabled) {
|
||||
this.onChangeStatus.emit(this.enabled);
|
||||
this.onUpdated.emit(this.enabled);
|
||||
this.dialogRef.close();
|
||||
}
|
||||
};
|
||||
|
||||
private async disableWebAuth() {
|
||||
return super.disable(this.formPromise);
|
||||
}
|
||||
|
||||
async remove(key: Key) {
|
||||
if (this.keysConfiguredCount <= 1 || key.removePromise != null) {
|
||||
return;
|
||||
@@ -208,7 +206,7 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent {
|
||||
}
|
||||
}
|
||||
this.enabled = response.enabled;
|
||||
this.onChangeStatus.emit(this.enabled);
|
||||
this.onUpdated.emit(this.enabled);
|
||||
}
|
||||
|
||||
static open(
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<img src="../../images/yubikey.jpg" class="tw-rounded img-fluid tw-mb-3" alt="" />
|
||||
</picture>
|
||||
<bit-form-field>
|
||||
<bit-label class="tw-sr-only">{{ "verificationCode" | i18n }}</bit-label>
|
||||
<bit-label>{{ "verificationCode" | i18n }}</bit-label>
|
||||
<input type="password" bitInput formControlName="token" appAutofocus appInputVerbatim />
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
import { PaymentMethodComponent } from "../shared";
|
||||
|
||||
import { BillingHistoryViewComponent } from "./billing-history-view.component";
|
||||
import { PremiumComponent } from "./premium.component";
|
||||
import { PremiumV2Component } from "./premium/premium-v2.component";
|
||||
import { PremiumComponent } from "./premium/premium.component";
|
||||
import { SubscriptionComponent } from "./subscription.component";
|
||||
import { UserSubscriptionComponent } from "./user-subscription.component";
|
||||
|
||||
@@ -20,11 +24,15 @@ const routes: Routes = [
|
||||
component: UserSubscriptionComponent,
|
||||
data: { titleId: "premiumMembership" },
|
||||
},
|
||||
{
|
||||
path: "premium",
|
||||
component: PremiumComponent,
|
||||
data: { titleId: "goPremium" },
|
||||
},
|
||||
...featureFlaggedRoute({
|
||||
defaultComponent: PremiumComponent,
|
||||
flaggedComponent: PremiumV2Component,
|
||||
featureFlag: FeatureFlag.AC2476_DeprecateStripeSourcesAPI,
|
||||
routeOptions: {
|
||||
path: "premium",
|
||||
data: { titleId: "goPremium" },
|
||||
},
|
||||
}),
|
||||
{
|
||||
path: "payment-method",
|
||||
component: PaymentMethodComponent,
|
||||
|
||||
@@ -5,7 +5,8 @@ import { BillingSharedModule } from "../shared";
|
||||
|
||||
import { BillingHistoryViewComponent } from "./billing-history-view.component";
|
||||
import { IndividualBillingRoutingModule } from "./individual-billing-routing.module";
|
||||
import { PremiumComponent } from "./premium.component";
|
||||
import { PremiumV2Component } from "./premium/premium-v2.component";
|
||||
import { PremiumComponent } from "./premium/premium.component";
|
||||
import { SubscriptionComponent } from "./subscription.component";
|
||||
import { UserSubscriptionComponent } from "./user-subscription.component";
|
||||
|
||||
@@ -16,6 +17,7 @@ import { UserSubscriptionComponent } from "./user-subscription.component";
|
||||
BillingHistoryViewComponent,
|
||||
UserSubscriptionComponent,
|
||||
PremiumComponent,
|
||||
PremiumV2Component,
|
||||
],
|
||||
})
|
||||
export class IndividualBillingModule {}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
<bit-section>
|
||||
<h2 *ngIf="!isSelfHost" bitTypography="h2">{{ "goPremium" | i18n }}</h2>
|
||||
<bit-callout
|
||||
type="info"
|
||||
*ngIf="hasPremiumFromAnyOrganization$ | async"
|
||||
title="{{ 'youHavePremiumAccess' | i18n }}"
|
||||
icon="bwi bwi-star-f"
|
||||
>
|
||||
{{ "alreadyPremiumFromOrg" | i18n }}
|
||||
</bit-callout>
|
||||
<bit-callout type="success">
|
||||
<p>{{ "premiumUpgradeUnlockFeatures" | i18n }}</p>
|
||||
<ul class="bwi-ul">
|
||||
<li>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpStorage" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpTwoStepOptions" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpEmergency" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpReports" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpTotp" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpSupport" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpFuture" | i18n }}
|
||||
</li>
|
||||
</ul>
|
||||
<p bitTypography="body1" [ngClass]="{ 'tw-mb-0': !isSelfHost }">
|
||||
{{
|
||||
"premiumPriceWithFamilyPlan" | i18n: (premiumPrice | currency: "$") : familyPlanMaxUserCount
|
||||
}}
|
||||
<a
|
||||
bitLink
|
||||
linkType="primary"
|
||||
routerLink="/create-organization"
|
||||
[queryParams]="{ plan: 'families' }"
|
||||
>
|
||||
{{ "bitwardenFamiliesPlan" | i18n }}
|
||||
</a>
|
||||
</p>
|
||||
<a
|
||||
bitButton
|
||||
href="{{ premiumURL }}}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
buttonType="secondary"
|
||||
*ngIf="isSelfHost"
|
||||
>
|
||||
{{ "purchasePremium" | i18n }}
|
||||
</a>
|
||||
</bit-callout>
|
||||
</bit-section>
|
||||
<bit-section *ngIf="isSelfHost">
|
||||
<p bitTypography="body1">{{ "uploadLicenseFilePremium" | i18n }}</p>
|
||||
<form [formGroup]="licenseFormGroup" [bitSubmit]="submitPremiumLicense">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "licenseFile" | i18n }}</bit-label>
|
||||
<div>
|
||||
<button type="button" bitButton buttonType="secondary" (click)="fileSelector.click()">
|
||||
{{ "chooseFile" | i18n }}
|
||||
</button>
|
||||
{{
|
||||
licenseFormGroup.value.file ? licenseFormGroup.value.file.name : ("noFileChosen" | i18n)
|
||||
}}
|
||||
</div>
|
||||
<input
|
||||
bitInput
|
||||
#fileSelector
|
||||
type="file"
|
||||
formControlName="file"
|
||||
(change)="onLicenseFileSelected($event)"
|
||||
hidden
|
||||
class="tw-hidden"
|
||||
/>
|
||||
<bit-hint>{{ "licenseFileDesc" | i18n: "bitwarden_premium_license.json" }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<button type="submit" buttonType="primary" bitButton bitFormButton>
|
||||
{{ "submit" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
</bit-section>
|
||||
<form *ngIf="!isSelfHost" [formGroup]="addOnFormGroup" [bitSubmit]="submitPayment">
|
||||
<bit-section>
|
||||
<h2 bitTypography="h2">{{ "addons" | i18n }}</h2>
|
||||
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
|
||||
<bit-form-field class="tw-col-span-6">
|
||||
<bit-label>{{ "additionalStorageGb" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
formControlName="additionalStorage"
|
||||
type="number"
|
||||
step="1"
|
||||
placeholder="{{ 'additionalStorageGbDesc' | i18n }}"
|
||||
/>
|
||||
<bit-hint>{{
|
||||
"additionalStorageIntervalDesc"
|
||||
| i18n: "1 GB" : (storageGBPrice | currency: "$") : ("year" | i18n)
|
||||
}}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
</bit-section>
|
||||
<bit-section>
|
||||
<h2 bitTypography="h2">{{ "summary" | i18n }}</h2>
|
||||
{{ "premiumMembership" | i18n }}: {{ premiumPrice | currency: "$" }} <br />
|
||||
{{ "additionalStorageGb" | i18n }}: {{ addOnFormGroup.value.additionalStorage || 0 }} GB ×
|
||||
{{ storageGBPrice | currency: "$" }} =
|
||||
{{ additionalStorageCost | currency: "$" }}
|
||||
<hr class="tw-my-3" />
|
||||
</bit-section>
|
||||
<bit-section>
|
||||
<h3 bitTypography="h2">{{ "paymentInformation" | i18n }}</h3>
|
||||
<app-payment-v2 [showBankAccount]="false"></app-payment-v2>
|
||||
<app-tax-info></app-tax-info>
|
||||
<div class="tw-mb-4">
|
||||
<div class="tw-text-muted tw-text-sm tw-flex tw-flex-col">
|
||||
<span>{{ "planPrice" | i18n }}: {{ subtotal | currency: "USD $" }}</span>
|
||||
<!-- TODO: Currently incorrect - https://bitwarden.atlassian.net/browse/PM-11525 -->
|
||||
<span>{{ "estimatedTax" | i18n }}: {{ estimatedTax | currency: "USD $" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="tw-my-1 tw-w-1/4 tw-ml-0" />
|
||||
<p bitTypography="body1">
|
||||
<strong>{{ "total" | i18n }}:</strong> {{ total | currency: "USD $" }}/{{ "year" | i18n }}
|
||||
</p>
|
||||
<button type="submit" buttonType="primary" bitButton bitFormButton>
|
||||
{{ "submit" | i18n }}
|
||||
</button>
|
||||
</bit-section>
|
||||
</form>
|
||||
@@ -0,0 +1,164 @@
|
||||
import { Component, ViewChild } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { combineLatest, concatMap, from, Observable, of } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { PaymentV2Component } from "../../shared/payment/payment-v2.component";
|
||||
import { TaxInfoComponent } from "../../shared/tax-info.component";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./premium-v2.component.html",
|
||||
})
|
||||
export class PremiumV2Component {
|
||||
@ViewChild(PaymentV2Component) paymentComponent: PaymentV2Component;
|
||||
@ViewChild(TaxInfoComponent) taxInfoComponent: TaxInfoComponent;
|
||||
|
||||
protected hasPremiumFromAnyOrganization$: Observable<boolean>;
|
||||
|
||||
protected addOnFormGroup = new FormGroup({
|
||||
additionalStorage: new FormControl<number>(0, [Validators.min(0), Validators.max(99)]),
|
||||
});
|
||||
|
||||
protected licenseFormGroup = new FormGroup({
|
||||
file: new FormControl<File>(null, [Validators.required]),
|
||||
});
|
||||
|
||||
protected cloudWebVaultURL: string;
|
||||
protected isSelfHost = false;
|
||||
|
||||
protected readonly familyPlanMaxUserCount = 6;
|
||||
protected readonly premiumPrice = 10;
|
||||
protected readonly storageGBPrice = 4;
|
||||
|
||||
constructor(
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private apiService: ApiService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private environmentService: EnvironmentService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private router: Router,
|
||||
private syncService: SyncService,
|
||||
private toastService: ToastService,
|
||||
private tokenService: TokenService,
|
||||
) {
|
||||
this.isSelfHost = this.platformUtilsService.isSelfHost();
|
||||
|
||||
this.hasPremiumFromAnyOrganization$ =
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$;
|
||||
|
||||
combineLatest([
|
||||
this.billingAccountProfileStateService.hasPremiumPersonally$,
|
||||
this.environmentService.cloudWebVaultUrl$,
|
||||
])
|
||||
.pipe(
|
||||
takeUntilDestroyed(),
|
||||
concatMap(([hasPremiumPersonally, cloudWebVaultURL]) => {
|
||||
if (hasPremiumPersonally) {
|
||||
return from(this.navigateToSubscriptionPage());
|
||||
}
|
||||
|
||||
this.cloudWebVaultURL = cloudWebVaultURL;
|
||||
return of(true);
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
finalizeUpgrade = async () => {
|
||||
await this.apiService.refreshIdentityToken();
|
||||
await this.syncService.fullSync(true);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("premiumUpdated"),
|
||||
});
|
||||
await this.navigateToSubscriptionPage();
|
||||
};
|
||||
|
||||
navigateToSubscriptionPage = (): Promise<boolean> =>
|
||||
this.router.navigate(["../user-subscription"], { relativeTo: this.activatedRoute });
|
||||
|
||||
onLicenseFileSelected = (event: Event): void => {
|
||||
const element = event.target as HTMLInputElement;
|
||||
this.licenseFormGroup.value.file = element.files.length > 0 ? element.files[0] : null;
|
||||
};
|
||||
|
||||
submitPremiumLicense = async (): Promise<void> => {
|
||||
this.licenseFormGroup.markAllAsTouched();
|
||||
|
||||
if (this.licenseFormGroup.invalid) {
|
||||
return this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("selectFile"),
|
||||
});
|
||||
}
|
||||
|
||||
const emailVerified = await this.tokenService.getEmailVerified();
|
||||
if (!emailVerified) {
|
||||
return this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("verifyEmailFirst"),
|
||||
});
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("license", this.licenseFormGroup.value.file);
|
||||
|
||||
await this.apiService.postAccountLicense(formData);
|
||||
await this.finalizeUpgrade();
|
||||
};
|
||||
|
||||
submitPayment = async (): Promise<void> => {
|
||||
this.taxInfoComponent.taxFormGroup.markAllAsTouched();
|
||||
if (this.taxInfoComponent.taxFormGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { type, token } = await this.paymentComponent.tokenize();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("paymentMethodType", type.toString());
|
||||
formData.append("paymentToken", token);
|
||||
formData.append("additionalStorageGb", this.addOnFormGroup.value.additionalStorage.toString());
|
||||
formData.append("country", this.taxInfoComponent.country);
|
||||
formData.append("postalCode", this.taxInfoComponent.postalCode);
|
||||
|
||||
await this.apiService.postPremium(formData);
|
||||
await this.finalizeUpgrade();
|
||||
};
|
||||
|
||||
protected get additionalStorageCost(): number {
|
||||
return this.storageGBPrice * this.addOnFormGroup.value.additionalStorage;
|
||||
}
|
||||
|
||||
protected get estimatedTax(): number {
|
||||
return this.taxInfoComponent?.taxRate != null
|
||||
? (this.taxInfoComponent.taxRate / 100) * this.subtotal
|
||||
: 0;
|
||||
}
|
||||
|
||||
protected get premiumURL(): string {
|
||||
return `${this.cloudWebVaultURL}/#/settings/subscription/premium`;
|
||||
}
|
||||
|
||||
protected get subtotal(): number {
|
||||
return this.premiumPrice + this.additionalStorageCost;
|
||||
}
|
||||
|
||||
protected get total(): number {
|
||||
return this.subtotal + this.estimatedTax;
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@
|
||||
<form [formGroup]="licenseForm" [bitSubmit]="submit">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "licenseFile" | i18n }}</bit-label>
|
||||
<div>
|
||||
<div class="tw-pt-2 tw-pb-1">
|
||||
<button bitButton type="button" buttonType="secondary" (click)="fileSelector.click()">
|
||||
{{ "chooseFile" | i18n }}
|
||||
</button>
|
||||
@@ -13,7 +13,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { PaymentComponent, TaxInfoComponent } from "../shared";
|
||||
import { PaymentComponent, TaxInfoComponent } from "../../shared";
|
||||
|
||||
@Component({
|
||||
templateUrl: "premium.component.html",
|
||||
@@ -48,7 +48,7 @@
|
||||
}}</span>
|
||||
</dd>
|
||||
<dt>{{ "nextCharge" | i18n }}</dt>
|
||||
<dd>
|
||||
<dd *ngIf="!enableTimeThreshold">
|
||||
{{
|
||||
nextInvoice
|
||||
? (nextInvoice.date | date: "mediumDate") +
|
||||
@@ -57,6 +57,15 @@
|
||||
: "-"
|
||||
}}
|
||||
</dd>
|
||||
<dd *ngIf="enableTimeThreshold">
|
||||
{{
|
||||
nextInvoice
|
||||
? (sub.subscription.periodEndDate | date: "mediumDate") +
|
||||
", " +
|
||||
(nextInvoice.amount | currency: "$")
|
||||
: "-"
|
||||
}}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="tw-w-2/3" *ngIf="subscription">
|
||||
|
||||
@@ -5,6 +5,8 @@ import { firstValueFrom, lastValueFrom } from "rxjs";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -12,10 +14,14 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import {
|
||||
AdjustStorageDialogV2Component,
|
||||
AdjustStorageDialogV2ResultType,
|
||||
} from "../shared/adjust-storage-dialog/adjust-storage-dialog-v2.component";
|
||||
import {
|
||||
AdjustStorageDialogResult,
|
||||
openAdjustStorageDialog,
|
||||
} from "../shared/adjust-storage.component";
|
||||
} from "../shared/adjust-storage-dialog/adjust-storage-dialog.component";
|
||||
import {
|
||||
OffboardingSurveyDialogResultType,
|
||||
openOffboardingSurvey,
|
||||
@@ -34,9 +40,17 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
sub: SubscriptionResponse;
|
||||
selfHosted = false;
|
||||
cloudWebVaultUrl: string;
|
||||
enableTimeThreshold: boolean;
|
||||
|
||||
cancelPromise: Promise<any>;
|
||||
reinstatePromise: Promise<any>;
|
||||
protected enableTimeThreshold$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.EnableTimeThreshold,
|
||||
);
|
||||
|
||||
protected deprecateStripeSourcesAPI$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.AC2476_DeprecateStripeSourcesAPI,
|
||||
);
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
@@ -49,6 +63,7 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
private environmentService: EnvironmentService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private toastService: ToastService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
this.selfHosted = platformUtilsService.isSelfHost();
|
||||
}
|
||||
@@ -56,6 +71,7 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
async ngOnInit() {
|
||||
this.cloudWebVaultUrl = await firstValueFrom(this.environmentService.cloudWebVaultUrl$);
|
||||
await this.load();
|
||||
this.enableTimeThreshold = await firstValueFrom(this.enableTimeThreshold$);
|
||||
this.firstLoaded = true;
|
||||
}
|
||||
|
||||
@@ -150,15 +166,33 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
};
|
||||
|
||||
adjustStorage = async (add: boolean) => {
|
||||
const dialogRef = openAdjustStorageDialog(this.dialogService, {
|
||||
data: {
|
||||
storageGbPrice: 4,
|
||||
add: add,
|
||||
},
|
||||
});
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
if (result === AdjustStorageDialogResult.Adjusted) {
|
||||
await this.load();
|
||||
const deprecateStripeSourcesAPI = await firstValueFrom(this.deprecateStripeSourcesAPI$);
|
||||
|
||||
if (deprecateStripeSourcesAPI) {
|
||||
const dialogRef = AdjustStorageDialogV2Component.open(this.dialogService, {
|
||||
data: {
|
||||
price: 4,
|
||||
cadence: "year",
|
||||
type: add ? "Add" : "Remove",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
|
||||
if (result === AdjustStorageDialogV2ResultType.Submitted) {
|
||||
await this.load();
|
||||
}
|
||||
} else {
|
||||
const dialogRef = openAdjustStorageDialog(this.dialogService, {
|
||||
data: {
|
||||
storageGbPrice: 4,
|
||||
add: add,
|
||||
},
|
||||
});
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
if (result === AdjustStorageDialogResult.Adjusted) {
|
||||
await this.load();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-subscription-update.request";
|
||||
@@ -11,7 +11,7 @@ import { ToastService } from "@bitwarden/components";
|
||||
selector: "app-adjust-subscription",
|
||||
templateUrl: "adjust-subscription.component.html",
|
||||
})
|
||||
export class AdjustSubscription {
|
||||
export class AdjustSubscription implements OnInit, OnDestroy {
|
||||
@Input() organizationId: string;
|
||||
@Input() maxAutoscaleSeats: number;
|
||||
@Input() currentSeatCount: number;
|
||||
@@ -19,6 +19,8 @@ export class AdjustSubscription {
|
||||
@Input() interval = "year";
|
||||
@Output() onAdjusted = new EventEmitter();
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
adjustSubscriptionForm = this.formBuilder.group({
|
||||
newSeatCount: [0, [Validators.min(0)]],
|
||||
limitSubscription: [false],
|
||||
@@ -30,30 +32,25 @@ export class AdjustSubscription {
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private formBuilder: FormBuilder,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.adjustSubscriptionForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
const maxAutoscaleSeatsControl = this.adjustSubscriptionForm.controls.newMaxSeats;
|
||||
|
||||
if (value.limitSubscription) {
|
||||
maxAutoscaleSeatsControl.setValidators([Validators.min(value.newSeatCount)]);
|
||||
maxAutoscaleSeatsControl.enable({ emitEvent: false });
|
||||
} else {
|
||||
maxAutoscaleSeatsControl.disable({ emitEvent: false });
|
||||
}
|
||||
});
|
||||
|
||||
this.adjustSubscriptionForm.patchValue({
|
||||
newSeatCount: this.currentSeatCount,
|
||||
limitSubscription: this.maxAutoscaleSeats != null,
|
||||
newMaxSeats: this.maxAutoscaleSeats,
|
||||
limitSubscription: this.maxAutoscaleSeats != null,
|
||||
});
|
||||
this.adjustSubscriptionForm
|
||||
.get("limitSubscription")
|
||||
.valueChanges.pipe(takeUntilDestroyed())
|
||||
.subscribe((value: boolean) => {
|
||||
if (value) {
|
||||
this.adjustSubscriptionForm
|
||||
.get("newMaxSeats")
|
||||
.addValidators([
|
||||
Validators.min(
|
||||
this.adjustSubscriptionForm.value.newSeatCount == null
|
||||
? 1
|
||||
: this.adjustSubscriptionForm.value.newSeatCount,
|
||||
),
|
||||
Validators.required,
|
||||
]);
|
||||
}
|
||||
this.adjustSubscriptionForm.get("newMaxSeats").updateValueAndValidity();
|
||||
});
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
@@ -99,4 +96,9 @@ export class AdjustSubscription {
|
||||
get limitSubscription(): boolean {
|
||||
return this.adjustSubscriptionForm.value.limitSubscription;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,9 +53,12 @@
|
||||
<dt [ngClass]="{ 'tw-text-danger': isExpired }">
|
||||
{{ "subscriptionExpiration" | i18n }}
|
||||
</dt>
|
||||
<dd [ngClass]="{ 'tw-text-danger': isExpired }">
|
||||
<dd [ngClass]="{ 'tw-text-danger': isExpired }" *ngIf="!enableTimeThreshold">
|
||||
{{ nextInvoice ? (nextInvoice.date | date: "mediumDate") : "-" }}
|
||||
</dd>
|
||||
<dd [ngClass]="{ 'tw-text-danger': isExpired }" *ngIf="enableTimeThreshold">
|
||||
{{ nextInvoice ? (sub.subscription.periodEndDate | date: "mediumDate") : "-" }}
|
||||
</dd>
|
||||
</ng-container>
|
||||
</dl>
|
||||
</ng-container>
|
||||
|
||||
@@ -18,10 +18,14 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import {
|
||||
AdjustStorageDialogV2Component,
|
||||
AdjustStorageDialogV2ResultType,
|
||||
} from "../shared/adjust-storage-dialog/adjust-storage-dialog-v2.component";
|
||||
import {
|
||||
AdjustStorageDialogResult,
|
||||
openAdjustStorageDialog,
|
||||
} from "../shared/adjust-storage.component";
|
||||
} from "../shared/adjust-storage-dialog/adjust-storage-dialog.component";
|
||||
import {
|
||||
OffboardingSurveyDialogResultType,
|
||||
openOffboardingSurvey,
|
||||
@@ -71,6 +75,10 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
FeatureFlag.EnableUpgradePasswordManagerSub,
|
||||
);
|
||||
|
||||
protected deprecateStripeSourcesAPI$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.AC2476_DeprecateStripeSourcesAPI,
|
||||
);
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
@@ -458,17 +466,36 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
|
||||
adjustStorage = (add: boolean) => {
|
||||
return async () => {
|
||||
const dialogRef = openAdjustStorageDialog(this.dialogService, {
|
||||
data: {
|
||||
storageGbPrice: this.storageGbPrice,
|
||||
add: add,
|
||||
organizationId: this.organizationId,
|
||||
interval: this.billingInterval,
|
||||
},
|
||||
});
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
if (result === AdjustStorageDialogResult.Adjusted) {
|
||||
await this.load();
|
||||
const deprecateStripeSourcesAPI = await firstValueFrom(this.deprecateStripeSourcesAPI$);
|
||||
|
||||
if (deprecateStripeSourcesAPI) {
|
||||
const dialogRef = AdjustStorageDialogV2Component.open(this.dialogService, {
|
||||
data: {
|
||||
price: this.storageGbPrice,
|
||||
cadence: this.billingInterval,
|
||||
type: add ? "Add" : "Remove",
|
||||
organizationId: this.organizationId,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
|
||||
if (result === AdjustStorageDialogV2ResultType.Submitted) {
|
||||
await this.load();
|
||||
}
|
||||
} else {
|
||||
const dialogRef = openAdjustStorageDialog(this.dialogService, {
|
||||
data: {
|
||||
storageGbPrice: this.storageGbPrice,
|
||||
add: add,
|
||||
organizationId: this.organizationId,
|
||||
interval: this.billingInterval,
|
||||
},
|
||||
});
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
if (result === AdjustStorageDialogResult.Adjusted) {
|
||||
await this.load();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog [title]="title">
|
||||
<ng-container bitDialogContent>
|
||||
<p bitTypography="body1">{{ body }}</p>
|
||||
<div class="tw-grid two-grid-cols-12">
|
||||
<bit-form-field class="tw-col-span-7">
|
||||
<bit-label>{{ storageFieldLabel }}</bit-label>
|
||||
<input bitInput type="number" formControlName="storage" />
|
||||
<bit-hint *ngIf="dialogParams.type === 'Add'">
|
||||
<!-- Total: 10 GB × $0.50 = $5.00 /month -->
|
||||
<strong>{{ "total" | i18n }}</strong>
|
||||
{{ this.formGroup.value.storage }} GB × {{ this.price | currency: "$" }} =
|
||||
{{ this.price * this.formGroup.value.storage | currency: "$" }} /
|
||||
{{ this.cadence | i18n }}
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container bitDialogFooter>
|
||||
<button type="submit" bitButton bitFormButton buttonType="primary">
|
||||
{{ "submit" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
bitFormButton
|
||||
buttonType="secondary"
|
||||
[bitDialogClose]="ResultType.Closed"
|
||||
>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
||||
@@ -0,0 +1,104 @@
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { FormControl, FormGroup, Validators } 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 { StorageRequest } from "@bitwarden/common/models/request/storage.request";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
export interface AdjustStorageDialogV2Params {
|
||||
price: number;
|
||||
cadence: "month" | "year";
|
||||
type: "Add" | "Remove";
|
||||
organizationId?: string;
|
||||
}
|
||||
|
||||
export enum AdjustStorageDialogV2ResultType {
|
||||
Submitted = "submitted",
|
||||
Closed = "closed",
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: "./adjust-storage-dialog-v2.component.html",
|
||||
})
|
||||
export class AdjustStorageDialogV2Component {
|
||||
protected formGroup = new FormGroup({
|
||||
storage: new FormControl<number>(0, [
|
||||
Validators.required,
|
||||
Validators.min(0),
|
||||
Validators.max(99),
|
||||
]),
|
||||
});
|
||||
|
||||
protected organizationId?: string;
|
||||
protected price: number;
|
||||
protected cadence: "month" | "year";
|
||||
|
||||
protected title: string;
|
||||
protected body: string;
|
||||
protected storageFieldLabel: string;
|
||||
|
||||
protected ResultType = AdjustStorageDialogV2ResultType;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
@Inject(DIALOG_DATA) protected dialogParams: AdjustStorageDialogV2Params,
|
||||
private dialogRef: DialogRef<AdjustStorageDialogV2ResultType>,
|
||||
private i18nService: I18nService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
this.price = this.dialogParams.price;
|
||||
this.cadence = this.dialogParams.cadence;
|
||||
this.organizationId = this.dialogParams.organizationId;
|
||||
switch (this.dialogParams.type) {
|
||||
case "Add":
|
||||
this.title = this.i18nService.t("addStorage");
|
||||
this.body = this.i18nService.t("storageAddNote");
|
||||
this.storageFieldLabel = this.i18nService.t("gbStorageAdd");
|
||||
break;
|
||||
case "Remove":
|
||||
this.title = this.i18nService.t("removeStorage");
|
||||
this.body = this.i18nService.t("storageRemoveNote");
|
||||
this.storageFieldLabel = this.i18nService.t("gbStorageRemove");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
const request = new StorageRequest();
|
||||
switch (this.dialogParams.type) {
|
||||
case "Add":
|
||||
request.storageGbAdjustment = this.formGroup.value.storage;
|
||||
break;
|
||||
case "Remove":
|
||||
request.storageGbAdjustment = this.formGroup.value.storage * -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.organizationId) {
|
||||
await this.organizationApiService.updateStorage(this.organizationId, request);
|
||||
} else {
|
||||
await this.apiService.postAccountStorage(request);
|
||||
}
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("adjustedStorage", request.storageGbAdjustment.toString()),
|
||||
});
|
||||
|
||||
this.dialogRef.close(this.ResultType.Submitted);
|
||||
};
|
||||
|
||||
static open = (
|
||||
dialogService: DialogService,
|
||||
dialogConfig: DialogConfig<AdjustStorageDialogV2Params>,
|
||||
) =>
|
||||
dialogService.open<AdjustStorageDialogV2ResultType>(
|
||||
AdjustStorageDialogV2Component,
|
||||
dialogConfig,
|
||||
);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { PaymentComponent } from "./payment/payment.component";
|
||||
import { PaymentComponent } from "../payment/payment.component";
|
||||
|
||||
export interface AdjustStorageDialogData {
|
||||
storageGbPrice: number;
|
||||
@@ -27,9 +27,9 @@ export enum AdjustStorageDialogResult {
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: "adjust-storage.component.html",
|
||||
templateUrl: "adjust-storage-dialog.component.html",
|
||||
})
|
||||
export class AdjustStorageComponent {
|
||||
export class AdjustStorageDialogComponent {
|
||||
storageGbPrice: number;
|
||||
add: boolean;
|
||||
organizationId: string;
|
||||
@@ -126,5 +126,5 @@ export function openAdjustStorageDialog(
|
||||
dialogService: DialogService,
|
||||
config: DialogConfig<AdjustStorageDialogData>,
|
||||
) {
|
||||
return dialogService.open<AdjustStorageDialogResult>(AdjustStorageComponent, config);
|
||||
return dialogService.open<AdjustStorageDialogResult>(AdjustStorageDialogComponent, config);
|
||||
}
|
||||
@@ -6,7 +6,8 @@ import { SharedModule } from "../../shared";
|
||||
import { AddCreditDialogComponent } from "./add-credit-dialog.component";
|
||||
import { AdjustPaymentDialogV2Component } from "./adjust-payment-dialog/adjust-payment-dialog-v2.component";
|
||||
import { AdjustPaymentDialogComponent } from "./adjust-payment-dialog/adjust-payment-dialog.component";
|
||||
import { AdjustStorageComponent } from "./adjust-storage.component";
|
||||
import { AdjustStorageDialogV2Component } from "./adjust-storage-dialog/adjust-storage-dialog-v2.component";
|
||||
import { AdjustStorageDialogComponent } from "./adjust-storage-dialog/adjust-storage-dialog.component";
|
||||
import { BillingHistoryComponent } from "./billing-history.component";
|
||||
import { OffboardingSurveyComponent } from "./offboarding-survey.component";
|
||||
import { PaymentV2Component } from "./payment/payment-v2.component";
|
||||
@@ -30,7 +31,7 @@ import { VerifyBankAccountComponent } from "./verify-bank-account/verify-bank-ac
|
||||
declarations: [
|
||||
AddCreditDialogComponent,
|
||||
AdjustPaymentDialogComponent,
|
||||
AdjustStorageComponent,
|
||||
AdjustStorageDialogComponent,
|
||||
BillingHistoryComponent,
|
||||
PaymentMethodComponent,
|
||||
SecretsManagerSubscribeComponent,
|
||||
@@ -38,18 +39,20 @@ import { VerifyBankAccountComponent } from "./verify-bank-account/verify-bank-ac
|
||||
UpdateLicenseDialogComponent,
|
||||
OffboardingSurveyComponent,
|
||||
AdjustPaymentDialogV2Component,
|
||||
AdjustStorageDialogV2Component,
|
||||
],
|
||||
exports: [
|
||||
SharedModule,
|
||||
PaymentComponent,
|
||||
TaxInfoComponent,
|
||||
AdjustStorageComponent,
|
||||
AdjustStorageDialogComponent,
|
||||
BillingHistoryComponent,
|
||||
SecretsManagerSubscribeComponent,
|
||||
UpdateLicenseComponent,
|
||||
UpdateLicenseDialogComponent,
|
||||
OffboardingSurveyComponent,
|
||||
VerifyBankAccountComponent,
|
||||
PaymentV2Component,
|
||||
],
|
||||
})
|
||||
export class BillingSharedModule {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<form [formGroup]="updateLicenseForm" [bitSubmit]="submit">
|
||||
<bit-form-field>
|
||||
<bit-label *ngIf="showAutomaticSyncAndManualUpload">{{ "licenseFile" | i18n }}</bit-label>
|
||||
<div>
|
||||
<div class="tw-pb-1 tw-pt-2">
|
||||
<button bitButton type="button" buttonType="secondary" (click)="fileSelector.click()">
|
||||
{{ "chooseFile" | i18n }}
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { APP_INITIALIZER, NgModule, Optional, SkipSelf } from "@angular/core";
|
||||
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
|
||||
import {
|
||||
SECURE_STORAGE,
|
||||
@@ -25,7 +26,6 @@ import {
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service";
|
||||
@@ -33,6 +33,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
@@ -42,6 +43,7 @@ import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwar
|
||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { BiometricsService } from "@bitwarden/common/platform/biometrics/biometric.service";
|
||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||
import { AppIdService as DefaultAppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||
// eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage
|
||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||
@@ -205,10 +207,15 @@ const safeProviders: SafeProvider[] = [
|
||||
KdfConfigService,
|
||||
InternalMasterPasswordServiceAbstraction,
|
||||
OrganizationApiServiceAbstraction,
|
||||
OrganizationUserService,
|
||||
OrganizationUserApiService,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: AppIdService,
|
||||
useClass: DefaultAppIdService,
|
||||
deps: [OBSERVABLE_DISK_LOCAL_STORAGE, LogService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: LoginService,
|
||||
useClass: WebLoginService,
|
||||
|
||||
@@ -36,13 +36,13 @@
|
||||
<bit-menu #accountMenu>
|
||||
<div class="tw-flex tw-min-w-52 tw-max-w-72 tw-flex-col">
|
||||
<div
|
||||
class="tw-flex tw-items-center tw-px-4 tw-py-1 tw-leading-tight tw-text-info"
|
||||
class="tw-flex tw-items-center tw-px-4 tw-py-1 tw-leading-tight tw-text-muted"
|
||||
appStopProp
|
||||
>
|
||||
<dynamic-avatar [id]="account.id" [text]="account | userName"></dynamic-avatar>
|
||||
<div class="tw-ml-2 tw-block tw-overflow-hidden tw-whitespace-nowrap">
|
||||
<span>{{ "loggedInAs" | i18n }}</span>
|
||||
<small class="tw-block tw-overflow-hidden tw-whitespace-nowrap tw-text-muted">
|
||||
<small class="tw-block tw-overflow-hidden tw-whitespace-nowrap">
|
||||
{{ account | userName }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*ngFor="let d of custom; let i = index; trackBy: indexTrackBy"
|
||||
>
|
||||
<bit-form-field class="tw-flex-1 !tw-mb-0" formGroupName="{{ i }}">
|
||||
<bit-label class="tw-sr-only">{{ "customDomainX" | i18n: i + 1 }} </bit-label>
|
||||
<bit-label>{{ "customDomainX" | i18n: i + 1 }} </bit-label>
|
||||
<textarea
|
||||
rows="2"
|
||||
bitInput
|
||||
|
||||
@@ -13,9 +13,11 @@ import {
|
||||
} from "rxjs";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserUserDetailsResponse,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses/organization-user.response";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -106,7 +108,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
||||
private collectionAdminService: CollectionAdminService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private dialogService: DialogService,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
) {
|
||||
@@ -155,7 +157,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
||||
collections: this.collectionAdminService.getAll(orgId),
|
||||
groups: groups$,
|
||||
// Collection(s) needed to map readonlypermission for (potential) access selector disabled state
|
||||
users: this.organizationUserService.getAllUsers(orgId, { includeCollections: true }),
|
||||
users: this.organizationUserApiService.getAllUsers(orgId, { includeCollections: true }),
|
||||
})
|
||||
.pipe(takeUntil(this.formGroup.controls.selectedOrg.valueChanges), takeUntil(this.destroy$))
|
||||
.subscribe(({ organization, collections: allCollections, groups, users }) => {
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="access(false)">
|
||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||
{{ "access" | i18n }}
|
||||
{{ "editAccess" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!canEditCollection && canViewCollectionInfo">
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
(click)="bulkEditCollectionAccess()"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||
{{ "access" | i18n }}
|
||||
{{ "editAccess" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
*ngIf="
|
||||
@@ -81,14 +81,7 @@
|
||||
>
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{
|
||||
(showBulkTrashOptions
|
||||
? "permanentlyDeleteSelected"
|
||||
: vaultBulkManagementActionEnabled
|
||||
? "delete"
|
||||
: "deleteSelected"
|
||||
) | i18n
|
||||
}}
|
||||
{{ (showBulkTrashOptions ? "permanentlyDeleteSelected" : "delete") | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
|
||||
@@ -24,7 +24,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
|
||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { Launchable } from "@bitwarden/common/vault/interfaces/launchable";
|
||||
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
||||
import { isCardExpired } from "@bitwarden/common/vault/utils";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
@@ -123,7 +123,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
|
||||
this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh),
|
||||
);
|
||||
|
||||
this.cardIsExpired = extensionRefreshEnabled && this.isCardExpiryInThePast();
|
||||
this.cardIsExpired = extensionRefreshEnabled && isCardExpired(this.cipher.card);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -235,24 +235,6 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
|
||||
this.viewingPasswordHistory = !this.viewingPasswordHistory;
|
||||
}
|
||||
|
||||
isCardExpiryInThePast() {
|
||||
if (this.cipher.card) {
|
||||
const { expMonth, expYear }: CardView = this.cipher.card;
|
||||
|
||||
if (expYear && expMonth) {
|
||||
// `Date` months are zero-indexed
|
||||
const parsedMonth = parseInt(expMonth) - 1;
|
||||
const parsedYear = parseInt(expYear);
|
||||
|
||||
// First day of the next month minus one, to get last day of the card month
|
||||
const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0);
|
||||
const now = new Date();
|
||||
|
||||
return cardExpiry < now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected cleanUp() {
|
||||
if (this.totpInterval) {
|
||||
window.clearInterval(this.totpInterval);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
|
||||
import { combineLatest, map, Observable, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserResetPasswordEnrollmentRequest,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
@@ -45,7 +47,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
|
||||
private policyService: PolicyService,
|
||||
private logService: LogService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
private dialogService: DialogService,
|
||||
private resetPasswordService: OrganizationUserResetPasswordService,
|
||||
@@ -153,7 +155,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
|
||||
this.dialogService,
|
||||
{ organization: org },
|
||||
this.resetPasswordService,
|
||||
this.organizationUserService,
|
||||
this.organizationUserApiService,
|
||||
this.platformUtilsService,
|
||||
this.i18nService,
|
||||
this.syncService,
|
||||
@@ -166,11 +168,12 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
|
||||
const request = new OrganizationUserResetPasswordEnrollmentRequest();
|
||||
request.masterPasswordHash = "ignored";
|
||||
request.resetPasswordKey = null;
|
||||
this.actionPromise = this.organizationUserService.putOrganizationUserResetPasswordEnrollment(
|
||||
this.organization.id,
|
||||
this.organization.userId,
|
||||
request,
|
||||
);
|
||||
this.actionPromise =
|
||||
this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment(
|
||||
this.organization.id,
|
||||
this.organization.userId,
|
||||
request,
|
||||
);
|
||||
try {
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<bit-dialog dialogSize="large">
|
||||
<bit-dialog dialogSize="large" background="alt">
|
||||
<span bitDialogTitle>
|
||||
{{ cipherTypeString }}
|
||||
</span>
|
||||
|
||||
@@ -3,8 +3,8 @@ import { Component, Inject, OnDestroy } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { combineLatest, of, Subject, switchMap, takeUntil } from "rxjs";
|
||||
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -60,7 +60,7 @@ export class BulkCollectionsDialogComponent implements OnDestroy {
|
||||
private formBuilder: FormBuilder,
|
||||
private organizationService: OrganizationService,
|
||||
private groupService: GroupService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private collectionAdminService: CollectionAdminService,
|
||||
@@ -79,7 +79,7 @@ export class BulkCollectionsDialogComponent implements OnDestroy {
|
||||
combineLatest([
|
||||
organization$,
|
||||
groups$,
|
||||
this.organizationUserService.getAllUsers(this.params.organizationId),
|
||||
this.organizationUserApiService.getAllUsers(this.params.organizationId),
|
||||
])
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(([organization, groups, users]) => {
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
<i class="bwi bwi-sticky-note" slot="start" aria-hidden="true"></i>
|
||||
{{ "note" | i18n }}
|
||||
</button>
|
||||
<bit-menu-divider></bit-menu-divider>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="false">
|
||||
<button type="button" bitMenuItem (click)="addCipher()">
|
||||
|
||||
@@ -30,14 +30,16 @@ import {
|
||||
tap,
|
||||
} from "rxjs/operators";
|
||||
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserUserDetailsResponse,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
@@ -215,7 +217,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
private totpService: TotpService,
|
||||
private apiService: ApiService,
|
||||
private collectionService: CollectionService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
protected configService: ConfigService,
|
||||
private toastService: ToastService,
|
||||
private accountService: AccountService,
|
||||
@@ -395,7 +397,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
// This will be passed into the usersCanManage call
|
||||
this.orgRevokedUsers = (
|
||||
await this.organizationUserService.getAllUsers(await firstValueFrom(organizationId$))
|
||||
await this.organizationUserApiService.getAllUsers(await firstValueFrom(organizationId$))
|
||||
).data.filter((user: OrganizationUserUserDetailsResponse) => {
|
||||
return user.status === -1;
|
||||
});
|
||||
|
||||
@@ -24,5 +24,16 @@ describe("CollectionUtils Service", () => {
|
||||
expect(result[0].node.name).toBe("Parent");
|
||||
expect(result[0].children[0].node.name).toBe("Child");
|
||||
});
|
||||
|
||||
it("should return an empty array if no collections are provided", () => {
|
||||
// Arrange
|
||||
const collections: CollectionView[] = [];
|
||||
|
||||
// Act
|
||||
const result = getNestedCollectionTree(collections);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,10 @@ export function getNestedCollectionTree(collections: CollectionView[]): TreeNode
|
||||
export function getNestedCollectionTree(
|
||||
collections: (CollectionView | CollectionAdminView)[],
|
||||
): TreeNode<CollectionView | CollectionAdminView>[] {
|
||||
if (!collections) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Collections need to be cloned because ServiceUtils.nestedTraverse actively
|
||||
// modifies the names of collections.
|
||||
// These changes risk affecting collections store in StateService.
|
||||
|
||||
@@ -48,6 +48,30 @@
|
||||
"loginCredentials": {
|
||||
"message": "Login credentials"
|
||||
},
|
||||
"personalDetails": {
|
||||
"message": "Personal details"
|
||||
},
|
||||
"identification": {
|
||||
"message": "Identification"
|
||||
},
|
||||
"contactInfo": {
|
||||
"message": "Contact info"
|
||||
},
|
||||
"cardDetails": {
|
||||
"message": "Card details"
|
||||
},
|
||||
"cardBrandDetails": {
|
||||
"message": "$BRAND$ details",
|
||||
"placeholders": {
|
||||
"brand": {
|
||||
"content": "$1",
|
||||
"example": "Visa"
|
||||
}
|
||||
}
|
||||
},
|
||||
"itemHistory": {
|
||||
"message": "Item history"
|
||||
},
|
||||
"authenticatorKey": {
|
||||
"message": "Authenticator key"
|
||||
},
|
||||
@@ -439,6 +463,9 @@
|
||||
"fullName": {
|
||||
"message": "Full name"
|
||||
},
|
||||
"address": {
|
||||
"message": "Address"
|
||||
},
|
||||
"address1": {
|
||||
"message": "Address 1"
|
||||
},
|
||||
@@ -9028,5 +9055,8 @@
|
||||
},
|
||||
"additionalContentAvailable": {
|
||||
"message": "Additional content is available"
|
||||
},
|
||||
"editAccess": {
|
||||
"message": "Edit access"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user