1
0
mirror of https://github.com/bitwarden/web synced 2025-12-10 05:13:40 +00:00

Merge branch 'master' into update-self-hosted

This commit is contained in:
Vince Grassia
2022-05-10 12:47:55 -04:00
113 changed files with 4653 additions and 5173 deletions

View File

@@ -21,10 +21,6 @@
<!--Required for any UI changes. Delete if not applicable-->
## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit
- [ ] I have checked for **linting** errors (`npm run lint`) (required)

View File

@@ -1,4 +1,5 @@
import { DragDropModule } from "@angular/cdk/drag-drop";
import { OverlayModule } from "@angular/cdk/overlay";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
@@ -20,6 +21,7 @@ import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-tim
@NgModule({
imports: [
OverlayModule,
OssModule,
JslibModule,
BrowserAnimationsModule,

View File

@@ -1,13 +1,13 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { Permissions } from "jslib-common/enums/permissions";
import { OrganizationLayoutComponent } from "src/app/layouts/organization-layout.component";
import { PermissionsGuard } from "src/app/organizations/guards/permissions.guard";
import { OrganizationLayoutComponent } from "src/app/organizations/layouts/organization-layout.component";
import { ManageComponent } from "src/app/organizations/manage/manage.component";
import { OrganizationGuardService } from "src/app/services/organization-guard.service";
import { OrganizationTypeGuardService } from "src/app/services/organization-type-guard.service";
import { NavigationPermissionsService } from "src/app/organizations/services/navigation-permissions.service";
import { SsoComponent } from "./manage/sso.component";
@@ -15,24 +15,15 @@ const routes: Routes = [
{
path: "organizations/:organizationId",
component: OrganizationLayoutComponent,
canActivate: [AuthGuardService, OrganizationGuardService],
canActivate: [AuthGuard, PermissionsGuard],
children: [
{
path: "manage",
component: ManageComponent,
canActivate: [OrganizationTypeGuardService],
canActivate: [PermissionsGuard],
data: {
permissions: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
Permissions.AccessEventLogs,
Permissions.ManageGroups,
Permissions.ManageUsers,
Permissions.ManagePolicies,
Permissions.ManageSso,
NavigationPermissionsService.getPermissions("manage").concat(Permissions.ManageSso),
],
},
children: [

View File

@@ -5,7 +5,7 @@ import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Permissions } from "jslib-common/enums/permissions";
@Injectable()
export class ProviderTypeGuardService implements CanActivate {
export class PermissionsGuard implements CanActivate {
constructor(private providerService: ProviderService, private router: Router) {}
async canActivate(route: ActivatedRouteSnapshot) {

View File

@@ -6,7 +6,7 @@ import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.se
import { ProviderService } from "jslib-common/abstractions/provider.service";
@Injectable()
export class ProviderGuardService implements CanActivate {
export class ProviderGuard implements CanActivate {
constructor(
private router: Router,
private platformUtilsService: PlatformUtilsService,

View File

@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { Permissions } from "jslib-common/enums/permissions";
import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component";
@@ -9,13 +9,13 @@ import { ProvidersComponent } from "src/app/providers/providers.component";
import { ClientsComponent } from "./clients/clients.component";
import { CreateOrganizationComponent } from "./clients/create-organization.component";
import { PermissionsGuard } from "./guards/provider-type.guard";
import { ProviderGuard } from "./guards/provider.guard";
import { AcceptProviderComponent } from "./manage/accept-provider.component";
import { EventsComponent } from "./manage/events.component";
import { ManageComponent } from "./manage/manage.component";
import { PeopleComponent } from "./manage/people.component";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { AccountComponent } from "./settings/account.component";
import { SettingsComponent } from "./settings/settings.component";
import { SetupProviderComponent } from "./setup/setup-provider.component";
@@ -24,7 +24,7 @@ import { SetupComponent } from "./setup/setup.component";
const routes: Routes = [
{
path: "",
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
component: ProvidersComponent,
},
{
@@ -45,7 +45,7 @@ const routes: Routes = [
},
{
path: "",
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
children: [
{
path: "setup",
@@ -54,7 +54,7 @@ const routes: Routes = [
{
path: ":providerId",
component: ProvidersLayoutComponent,
canActivate: [ProviderGuardService],
canActivate: [ProviderGuard],
children: [
{ path: "", pathMatch: "full", redirectTo: "clients" },
{ path: "clients/create", component: CreateOrganizationComponent },
@@ -71,7 +71,7 @@ const routes: Routes = [
{
path: "people",
component: PeopleComponent,
canActivate: [ProviderTypeGuardService],
canActivate: [PermissionsGuard],
data: {
titleId: "people",
permissions: [Permissions.ManageUsers],
@@ -80,7 +80,7 @@ const routes: Routes = [
{
path: "events",
component: EventsComponent,
canActivate: [ProviderTypeGuardService],
canActivate: [PermissionsGuard],
data: {
titleId: "eventLogs",
permissions: [Permissions.AccessEventLogs],
@@ -100,7 +100,7 @@ const routes: Routes = [
{
path: "account",
component: AccountComponent,
canActivate: [ProviderTypeGuardService],
canActivate: [PermissionsGuard],
data: {
titleId: "myProvider",
permissions: [Permissions.ManageProvider],

View File

@@ -10,6 +10,8 @@ import { OssModule } from "src/app/oss.module";
import { AddOrganizationComponent } from "./clients/add-organization.component";
import { ClientsComponent } from "./clients/clients.component";
import { CreateOrganizationComponent } from "./clients/create-organization.component";
import { PermissionsGuard } from "./guards/provider-type.guard";
import { ProviderGuard } from "./guards/provider.guard";
import { AcceptProviderComponent } from "./manage/accept-provider.component";
import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component";
import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component";
@@ -19,8 +21,6 @@ import { PeopleComponent } from "./manage/people.component";
import { UserAddEditComponent } from "./manage/user-add-edit.component";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProvidersRoutingModule } from "./providers-routing.module";
import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { WebProviderService } from "./services/webProvider.service";
import { AccountComponent } from "./settings/account.component";
import { SettingsComponent } from "./settings/settings.component";
@@ -46,7 +46,7 @@ import { SetupComponent } from "./setup/setup.component";
SetupProviderComponent,
UserAddEditComponent,
],
providers: [WebProviderService, ProviderGuardService, ProviderTypeGuardService],
providers: [WebProviderService, ProviderGuard, PermissionsGuard],
})
export class ProvidersModule {
constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) {

2
jslib

Submodule jslib updated: 2e2849b4de...00deb38de5

3925
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -98,6 +98,7 @@
"jszip": "^3.7.1",
"ngx-infinite-scroll": "^10.0.1",
"ngx-toastr": "14.1.4",
"node-forge": "^1.3.1",
"popper.js": "1.16.1",
"qrious": "4.0.2",
"rxjs": "^7.4.0",

View File

@@ -0,0 +1,68 @@
<div *ngIf="loaded && activeOrganization != null" class="tw-flex">
<button
class="tw-flex tw-items-center tw-bg-background-alt tw-border-none"
type="button"
id="pickerButton"
[appA11yTitle]="'organizationPicker' | i18n"
[bitMenuTriggerFor]="orgPickerMenu"
>
<app-avatar
[data]="activeOrganization.name"
size="45"
[circle]="true"
[dynamic]="true"
></app-avatar>
<div class="tw-flex">
<div class="org-name tw-ml-3">
<span>{{ activeOrganization.name }}</span>
<small class="tw-text-muted">{{ "organization" | i18n }}</small>
</div>
<div class="tw-ml-3">
<i class="bwi bwi-angle-down tw-text-main" aria-hidden="true"></i>
</div>
</div>
</button>
<div>
<div
class="tw-ml-3 tw-border tw-border-solid tw-rounded tw-border-danger-500 tw-text-danger"
*ngIf="!activeOrganization.enabled"
>
<div class="tw-py-2 tw-px-5">
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
{{ "organizationIsDisabled" | i18n }}
</div>
</div>
<div
class="tw-ml-3 tw-border tw-border-solid tw-rounded tw-border-info-500 tw-text-info"
*ngIf="activeOrganization.isProviderUser"
>
<div class="tw-py-2 tw-px-5">
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
{{ "accessingUsingProvider" | i18n: activeOrganization.providerName }}
</div>
</div>
</div>
<bit-menu #orgPickerMenu>
<ul aria-labelledby="pickerButton" class="tw-p-0 tw-m-0">
<li *ngFor="let org of organizations" class="tw-list-none tw-flex tw-flex-col" role="none">
<a bit-menu-item [routerLink]="['/organizations', org.id]">
<i
class="bwi bwi-check mr-2"
[ngClass]="org.id === activeOrganization.id ? 'visible' : 'invisible'"
>
<span class="tw-sr-only">{{ "currentOrganization" | i18n }}</span>
</i>
{{ org.name }}
</a>
</li>
<bit-menu-divider></bit-menu-divider>
<li class="tw-list-none" role="none">
<a bit-menu-item routerLink="/settings/create-organization">
<i class="bwi bwi-plus mr-2"></i>
{{ "newOrganization" | i18n }}</a
>
</li>
</ul>
</bit-menu>
</div>

View File

@@ -0,0 +1,34 @@
import { Component, Input, OnInit } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { Utils } from "jslib-common/misc/utils";
import { Organization } from "jslib-common/models/domain/organization";
import { NavigationPermissionsService } from "../organizations/services/navigation-permissions.service";
@Component({
selector: "app-organization-switcher",
templateUrl: "organization-switcher.component.html",
})
export class OrganizationSwitcherComponent implements OnInit {
constructor(private organizationService: OrganizationService, private i18nService: I18nService) {}
@Input() activeOrganization: Organization = null;
organizations: Organization[] = [];
loaded = false;
async ngOnInit() {
await this.load();
}
async load() {
const orgs = await this.organizationService.getAll();
this.organizations = orgs
.filter((org) => NavigationPermissionsService.canAccessAdmin(org))
.sort(Utils.getSortFunction(this.i18nService, "name"));
this.loaded = true;
}
}

View File

@@ -6,11 +6,22 @@
<div class="collapse navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/vault">{{ "myVault" | i18n }}</a>
<a class="nav-link" routerLink="/vault">{{ "vaults" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/sends">{{ "send" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/tools">{{ "tools" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/reports">{{ "reports" | i18n }}</a>
</li>
<li *ngIf="organizations.length >= 1" class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/organizations', organizations[0].id]">{{
"organizations" | i18n
}}</a>
</li>
<ng-container *ngIf="providers.length >= 1">
<li class="nav-item" routerLinkActive="active" *ngIf="providers.length == 1">
<a class="nav-link" [routerLink]="['/providers', providers[0].id]">{{
@@ -21,76 +32,62 @@
<a class="nav-link" routerLink="/providers">{{ "provider" | i18n }}</a>
</li>
</ng-container>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/tools">{{ "tools" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/reports">{{ "reports" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/settings">{{ "settings" | i18n }}</a>
</li>
</ul>
</div>
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
<li class="nav-item dropdown">
<a
class="nav-item nav-link dropdown-toggle"
href="#"
id="nav-profile"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
<li>
<button
[bitMenuTriggerFor]="accountMenu"
class="tw-border-0 tw-bg-transparent tw-text-contrast tw-opacity-70 hover:tw-opacity-90"
>
<i class="bwi bwi-user-circle bwi-lg" aria-hidden="true"></i>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="nav-profile">
<div class="dropdown-item-text d-flex align-items-center" *ngIf="name" appStopProp>
<app-avatar
[data]="name"
[email]="email"
size="25"
fontSize="14"
[circle]="true"
></app-avatar>
<div class="ml-2 overflow-hidden">
<span>{{ "loggedInAs" | i18n }}</span>
<small class="text-muted">{{ name }}</small>
<i class="bwi bwi-caret-down bwi-sm" aria-hidden="true"></i>
</button>
<bit-menu class="dropdown-menu" #accountMenu>
<div class="tw-max-w-[300px] tw-min-w-[200px] tw-flex tw-flex-col">
<div
class="tw-flex tw-items-center tw-leading-tight tw-text-info tw-py-1 tw-px-4"
*ngIf="name"
appStopProp
>
<app-avatar
[data]="name"
[email]="email"
size="25"
fontSize="14"
[circle]="true"
></app-avatar>
<div class="tw-ml-2 tw-block tw-overflow-hidden tw-whitespace-nowrap">
<span>{{ "loggedInAs" | i18n }}</span>
<small class="tw-text-muted tw-block tw-overflow-hidden tw-whitespace-nowrap">{{
name
}}</small>
</div>
</div>
<bit-menu-divider></bit-menu-divider>
<a bit-menu-item routerLink="/settings/account">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
{{ "accountSettings" | i18n }}
</a>
<a bit-menu-item href="https://bitwarden.com/help/" target="_blank" rel="noopener">
<i class="bwi bwi-fw bwi-question-circle" aria-hidden="true"></i>
{{ "getHelp" | i18n }}
</a>
<a bit-menu-item href="https://bitwarden.com/download/" target="_blank" rel="noopener">
<i class="bwi bwi-fw bwi-download" aria-hidden="true"></i>
{{ "getApps" | i18n }}
</a>
<bit-menu-divider></bit-menu-divider>
<button bit-menu-item type="button" (click)="lock()">
<i class="bwi bwi-fw bwi-lock" aria-hidden="true"></i>
{{ "lockNow" | i18n }}
</button>
<button bit-menu-item type="button" (click)="logOut()">
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
{{ "logOut" | i18n }}
</button>
</div>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" routerLink="/settings/account">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
{{ "myAccount" | i18n }}
</a>
<a
class="dropdown-item"
href="https://bitwarden.com/help/"
target="_blank"
rel="noopener"
>
<i class="bwi bwi-fw bwi-question-circle" aria-hidden="true"></i>
{{ "getHelp" | i18n }}
</a>
<a
class="dropdown-item"
href="https://bitwarden.com/download/"
target="_blank"
rel="noopener"
>
<i class="bwi bwi-fw bwi-download" aria-hidden="true"></i>
{{ "getApps" | i18n }}
</a>
<div class="dropdown-divider"></div>
<button type="button" class="dropdown-item" (click)="lock()">
<i class="bwi bwi-fw bwi-lock" aria-hidden="true"></i>
{{ "lockNow" | i18n }}
</button>
<button type="button" class="dropdown-item" (click)="logOut()">
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
{{ "logOut" | i18n }}
</button>
</div>
</bit-menu>
</li>
</ul>
</div>

View File

@@ -1,12 +1,18 @@
import { Component, OnInit } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { Utils } from "jslib-common/misc/utils";
import { Organization } from "jslib-common/models/domain/organization";
import { Provider } from "jslib-common/models/domain/provider";
import { NavigationPermissionsService as OrgNavigationPermissionsService } from "../organizations/services/navigation-permissions.service";
@Component({
selector: "app-navbar",
templateUrl: "navbar.component.html",
@@ -16,13 +22,16 @@ export class NavbarComponent implements OnInit {
name: string;
email: string;
providers: Provider[] = [];
organizations: Organization[] = [];
constructor(
private messagingService: MessagingService,
private platformUtilsService: PlatformUtilsService,
private tokenService: TokenService,
private providerService: ProviderService,
private syncService: SyncService
private syncService: SyncService,
private organizationService: OrganizationService,
private i18nService: I18nService
) {
this.selfHosted = this.platformUtilsService.isSelfHost();
}
@@ -34,11 +43,16 @@ export class NavbarComponent implements OnInit {
this.name = this.email;
}
// Ensure provides are loaded
// Ensure providers and organizations are loaded
if ((await this.syncService.getLastSync()) == null) {
await this.syncService.fullSync(false);
}
this.providers = await this.providerService.getAll();
const allOrgs = await this.organizationService.getAll();
this.organizations = allOrgs
.filter((org) => OrgNavigationPermissionsService.canAccessAdmin(org))
.sort(Utils.getSortFunction(this.i18nService, "name"));
}
lock() {

View File

@@ -1,60 +0,0 @@
<app-navbar></app-navbar>
<div class="org-nav" *ngIf="organization">
<div class="container d-flex">
<div class="d-flex flex-column">
<div class="my-auto d-flex align-items-center pl-1">
<app-avatar [data]="organization.name" size="45" [circle]="true"></app-avatar>
<div class="org-name ml-3">
<span>{{ organization.name }}</span>
<small class="text-muted">{{ "organization" | i18n }}</small>
</div>
<div
class="ml-3 card border-danger text-danger bg-transparent"
*ngIf="!organization.enabled"
>
<div class="card-body py-2">
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
{{ "organizationIsDisabled" | i18n }}
</div>
</div>
<div
class="ml-3 card border-info text-info bg-transparent"
*ngIf="organization.isProviderUser"
>
<div class="card-body py-2">
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
{{ "accessingUsingProvider" | i18n: organization.providerName }}
</div>
</div>
</div>
<ul class="nav nav-tabs" *ngIf="showMenuBar">
<li class="nav-item">
<a class="nav-link" routerLink="vault" routerLinkActive="active">
<i class="bwi bwi-lock" aria-hidden="true"></i>
{{ "vault" | i18n }}
</a>
</li>
<li class="nav-item" *ngIf="showManageTab">
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
<i class="bwi bwi-sliders" aria-hidden="true"></i>
{{ "manage" | i18n }}
</a>
</li>
<li class="nav-item" *ngIf="showToolsTab">
<a class="nav-link" [routerLink]="toolsRoute" routerLinkActive="active">
<i class="bwi bwi-wrench" aria-hidden="true"></i>
{{ "tools" | i18n }}
</a>
</li>
<li class="nav-item" *ngIf="organization.isOwner">
<a class="nav-link" routerLink="settings" routerLinkActive="active">
<i class="bwi bwi-cogs" aria-hidden="true"></i>
{{ "settings" | i18n }}
</a>
</li>
</ul>
</div>
</div>
</div>
<router-outlet></router-outlet>
<app-footer></app-footer>

View File

@@ -0,0 +1,493 @@
import { NgModule } from "@angular/core";
import { UserVerificationComponent } from "jslib-angular/components/user-verification.component";
import { AcceptEmergencyComponent } from "../accounts/accept-emergency.component";
import { AcceptOrganizationComponent } from "../accounts/accept-organization.component";
import { HintComponent } from "../accounts/hint.component";
import { LockComponent } from "../accounts/lock.component";
import { LoginComponent } from "../accounts/login.component";
import { RecoverDeleteComponent } from "../accounts/recover-delete.component";
import { RecoverTwoFactorComponent } from "../accounts/recover-two-factor.component";
import { RegisterComponent } from "../accounts/register.component";
import { RemovePasswordComponent } from "../accounts/remove-password.component";
import { SetPasswordComponent } from "../accounts/set-password.component";
import { SsoComponent } from "../accounts/sso.component";
import { TwoFactorOptionsComponent } from "../accounts/two-factor-options.component";
import { TwoFactorComponent } from "../accounts/two-factor.component";
import { UpdatePasswordComponent } from "../accounts/update-password.component";
import { UpdateTempPasswordComponent } from "../accounts/update-temp-password.component";
import { VerifyEmailTokenComponent } from "../accounts/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "../accounts/verify-recover-delete.component";
import { NestedCheckboxComponent } from "../components/nested-checkbox.component";
import { OrganizationSwitcherComponent } from "../components/organization-switcher.component";
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
import { PasswordStrengthComponent } from "../components/password-strength.component";
import { PremiumBadgeComponent } from "../components/premium-badge.component";
import { FooterComponent } from "../layouts/footer.component";
import { FrontendLayoutComponent } from "../layouts/frontend-layout.component";
import { NavbarComponent } from "../layouts/navbar.component";
import { UserLayoutComponent } from "../layouts/user-layout.component";
import { OrganizationLayoutComponent } from "../organizations/layouts/organization-layout.component";
import { BulkConfirmComponent as OrgBulkConfirmComponent } from "../organizations/manage/bulk/bulk-confirm.component";
import { BulkRemoveComponent as OrgBulkRemoveComponent } from "../organizations/manage/bulk/bulk-remove.component";
import { BulkStatusComponent as OrgBulkStatusComponent } from "../organizations/manage/bulk/bulk-status.component";
import { CollectionAddEditComponent as OrgCollectionAddEditComponent } from "../organizations/manage/collection-add-edit.component";
import { CollectionsComponent as OrgManageCollectionsComponent } from "../organizations/manage/collections.component";
import { EntityEventsComponent as OrgEntityEventsComponent } from "../organizations/manage/entity-events.component";
import { EntityUsersComponent as OrgEntityUsersComponent } from "../organizations/manage/entity-users.component";
import { EventsComponent as OrgEventsComponent } from "../organizations/manage/events.component";
import { GroupAddEditComponent as OrgGroupAddEditComponent } from "../organizations/manage/group-add-edit.component";
import { GroupsComponent as OrgGroupsComponent } from "../organizations/manage/groups.component";
import { ManageComponent as OrgManageComponent } from "../organizations/manage/manage.component";
import { PeopleComponent as OrgPeopleComponent } from "../organizations/manage/people.component";
import { PoliciesComponent as OrgPoliciesComponent } from "../organizations/manage/policies.component";
import { PolicyEditComponent as OrgPolicyEditComponent } from "../organizations/manage/policy-edit.component";
import { ResetPasswordComponent as OrgResetPasswordComponent } from "../organizations/manage/reset-password.component";
import { UserAddEditComponent as OrgUserAddEditComponent } from "../organizations/manage/user-add-edit.component";
import { UserConfirmComponent as OrgUserConfirmComponent } from "../organizations/manage/user-confirm.component";
import { UserGroupsComponent as OrgUserGroupsComponent } from "../organizations/manage/user-groups.component";
import { DisableSendPolicyComponent } from "../organizations/policies/disable-send.component";
import { MasterPasswordPolicyComponent } from "../organizations/policies/master-password.component";
import { PasswordGeneratorPolicyComponent } from "../organizations/policies/password-generator.component";
import { PersonalOwnershipPolicyComponent } from "../organizations/policies/personal-ownership.component";
import { RequireSsoPolicyComponent } from "../organizations/policies/require-sso.component";
import { ResetPasswordPolicyComponent } from "../organizations/policies/reset-password.component";
import { SendOptionsPolicyComponent } from "../organizations/policies/send-options.component";
import { SingleOrgPolicyComponent } from "../organizations/policies/single-org.component";
import { TwoFactorAuthenticationPolicyComponent } from "../organizations/policies/two-factor-authentication.component";
import { AccountComponent as OrgAccountComponent } from "../organizations/settings/account.component";
import { AdjustSubscription } from "../organizations/settings/adjust-subscription.component";
import { ChangePlanComponent } from "../organizations/settings/change-plan.component";
import { DeleteOrganizationComponent } from "../organizations/settings/delete-organization.component";
import { DownloadLicenseComponent } from "../organizations/settings/download-license.component";
import { ImageSubscriptionHiddenComponent as OrgSubscriptionHiddenComponent } from "../organizations/settings/image-subscription-hidden.component";
import { OrganizationBillingComponent } from "../organizations/settings/organization-billing.component";
import { OrganizationSubscriptionComponent } from "../organizations/settings/organization-subscription.component";
import { SettingsComponent as OrgSettingComponent } from "../organizations/settings/settings.component";
import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "../organizations/settings/two-factor-setup.component";
import { FamiliesForEnterpriseSetupComponent } from "../organizations/sponsorships/families-for-enterprise-setup.component";
import { ExportComponent as OrgExportComponent } from "../organizations/tools/export.component";
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "../organizations/tools/exposed-passwords-report.component";
import { ImportComponent as OrgImportComponent } from "../organizations/tools/import.component";
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "../organizations/tools/inactive-two-factor-report.component";
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "../organizations/tools/reused-passwords-report.component";
import { ToolsComponent as OrgToolsComponent } from "../organizations/tools/tools.component";
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../organizations/tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../organizations/tools/weak-passwords-report.component";
import { AddEditComponent as OrgAddEditComponent } from "../organizations/vault/add-edit.component";
import { AttachmentsComponent as OrgAttachmentsComponent } from "../organizations/vault/attachments.component";
import { CiphersComponent as OrgCiphersComponent } from "../organizations/vault/ciphers.component";
import { CollectionsComponent as OrgCollectionsComponent } from "../organizations/vault/collections.component";
import { ProvidersComponent } from "../providers/providers.component";
import { BreachReportComponent } from "../reports/breach-report.component";
import { ExposedPasswordsReportComponent } from "../reports/exposed-passwords-report.component";
import { InactiveTwoFactorReportComponent } from "../reports/inactive-two-factor-report.component";
import { ReportCardComponent } from "../reports/report-card.component";
import { ReportListComponent } from "../reports/report-list.component";
import { ReportsComponent } from "../reports/reports.component";
import { ReusedPasswordsReportComponent } from "../reports/reused-passwords-report.component";
import { UnsecuredWebsitesReportComponent } from "../reports/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "../reports/weak-passwords-report.component";
import { AccessComponent } from "../send/access.component";
import { AddEditComponent as SendAddEditComponent } from "../send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from "../send/efflux-dates.component";
import { SendComponent } from "../send/send.component";
import { AccountComponent } from "../settings/account.component";
import { AddCreditComponent } from "../settings/add-credit.component";
import { AdjustPaymentComponent } from "../settings/adjust-payment.component";
import { AdjustStorageComponent } from "../settings/adjust-storage.component";
import { ApiKeyComponent } from "../settings/api-key.component";
import { ChangeEmailComponent } from "../settings/change-email.component";
import { ChangeKdfComponent } from "../settings/change-kdf.component";
import { ChangePasswordComponent } from "../settings/change-password.component";
import { CreateOrganizationComponent } from "../settings/create-organization.component";
import { DeauthorizeSessionsComponent } from "../settings/deauthorize-sessions.component";
import { DeleteAccountComponent } from "../settings/delete-account.component";
import { DomainRulesComponent } from "../settings/domain-rules.component";
import { EmergencyAccessAddEditComponent } from "../settings/emergency-access-add-edit.component";
import { EmergencyAccessAttachmentsComponent } from "../settings/emergency-access-attachments.component";
import { EmergencyAccessConfirmComponent } from "../settings/emergency-access-confirm.component";
import { EmergencyAccessTakeoverComponent } from "../settings/emergency-access-takeover.component";
import { EmergencyAccessViewComponent } from "../settings/emergency-access-view.component";
import { EmergencyAccessComponent } from "../settings/emergency-access.component";
import { EmergencyAddEditComponent } from "../settings/emergency-add-edit.component";
import { LinkSsoComponent } from "../settings/link-sso.component";
import { OrganizationPlansComponent } from "../settings/organization-plans.component";
import { PaymentMethodComponent } from "../settings/payment-method.component";
import { PaymentComponent } from "../settings/payment.component";
import { PreferencesComponent } from "../settings/preferences.component";
import { PremiumComponent } from "../settings/premium.component";
import { ProfileComponent } from "../settings/profile.component";
import { PurgeVaultComponent } from "../settings/purge-vault.component";
import { SecurityKeysComponent } from "../settings/security-keys.component";
import { SecurityComponent } from "../settings/security.component";
import { SettingsComponent } from "../settings/settings.component";
import { SponsoredFamiliesComponent } from "../settings/sponsored-families.component";
import { SponsoringOrgRowComponent } from "../settings/sponsoring-org-row.component";
import { SubscriptionComponent } from "../settings/subscription.component";
import { TaxInfoComponent } from "../settings/tax-info.component";
import { TwoFactorAuthenticatorComponent } from "../settings/two-factor-authenticator.component";
import { TwoFactorDuoComponent } from "../settings/two-factor-duo.component";
import { TwoFactorEmailComponent } from "../settings/two-factor-email.component";
import { TwoFactorRecoveryComponent } from "../settings/two-factor-recovery.component";
import { TwoFactorSetupComponent } from "../settings/two-factor-setup.component";
import { TwoFactorVerifyComponent } from "../settings/two-factor-verify.component";
import { TwoFactorWebAuthnComponent } from "../settings/two-factor-webauthn.component";
import { TwoFactorYubiKeyComponent } from "../settings/two-factor-yubikey.component";
import { UpdateKeyComponent } from "../settings/update-key.component";
import { UpdateLicenseComponent } from "../settings/update-license.component";
import { UserBillingHistoryComponent } from "../settings/user-billing-history.component";
import { UserSubscriptionComponent } from "../settings/user-subscription.component";
import { VaultTimeoutInputComponent } from "../settings/vault-timeout-input.component";
import { VerifyEmailComponent } from "../settings/verify-email.component";
import { ExportComponent } from "../tools/export.component";
import { GeneratorComponent } from "../tools/generator.component";
import { ImportComponent } from "../tools/import.component";
import { PasswordGeneratorHistoryComponent } from "../tools/password-generator-history.component";
import { ToolsComponent } from "../tools/tools.component";
import { AddEditCustomFieldsComponent } from "../vault/add-edit-custom-fields.component";
import { AddEditComponent } from "../vault/add-edit.component";
import { AttachmentsComponent } from "../vault/attachments.component";
import { BulkActionsComponent } from "../vault/bulk-actions.component";
import { BulkDeleteComponent } from "../vault/bulk-delete.component";
import { BulkMoveComponent } from "../vault/bulk-move.component";
import { BulkRestoreComponent } from "../vault/bulk-restore.component";
import { BulkShareComponent } from "../vault/bulk-share.component";
import { CiphersComponent } from "../vault/ciphers.component";
import { CollectionsComponent } from "../vault/collections.component";
import { FolderAddEditComponent } from "../vault/folder-add-edit.component";
import { ShareComponent } from "../vault/share.component";
import { PipesModule } from "./pipes/pipes.module";
import { SharedModule } from "./shared.module";
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
import { OrganizationBadgeModule } from "./vault/modules/organization-badge/organization-badge.module";
// Please do not add to this list of declarations - we should refactor these into modules when doing so makes sense until there are none left.
// If you are building new functionality, please create or extend a feature module instead.
@NgModule({
imports: [SharedModule, VaultFilterModule, OrganizationBadgeModule, PipesModule],
declarations: [
PremiumBadgeComponent,
AcceptEmergencyComponent,
AcceptOrganizationComponent,
AccessComponent,
AccountComponent,
AddCreditComponent,
AddEditComponent,
AddEditCustomFieldsComponent,
AddEditCustomFieldsComponent,
AdjustPaymentComponent,
AdjustStorageComponent,
AdjustSubscription,
ApiKeyComponent,
AttachmentsComponent,
BreachReportComponent,
BulkActionsComponent,
BulkDeleteComponent,
BulkMoveComponent,
BulkRestoreComponent,
BulkShareComponent,
ChangeEmailComponent,
ChangeKdfComponent,
ChangePasswordComponent,
ChangePlanComponent,
CiphersComponent,
CollectionsComponent,
CreateOrganizationComponent,
DeauthorizeSessionsComponent,
DeleteAccountComponent,
DeleteOrganizationComponent,
DisableSendPolicyComponent,
DomainRulesComponent,
DownloadLicenseComponent,
EmergencyAccessAddEditComponent,
EmergencyAccessAttachmentsComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
EmergencyAddEditComponent,
ExportComponent,
ExposedPasswordsReportComponent,
FamiliesForEnterpriseSetupComponent,
FolderAddEditComponent,
FooterComponent,
FrontendLayoutComponent,
HintComponent,
ImportComponent,
InactiveTwoFactorReportComponent,
LinkSsoComponent,
LockComponent,
LoginComponent,
MasterPasswordPolicyComponent,
NavbarComponent,
NestedCheckboxComponent,
OrganizationSwitcherComponent,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
OrganizationLayoutComponent,
OrganizationPlansComponent,
OrganizationSubscriptionComponent,
OrgAttachmentsComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
OrgBulkStatusComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
OrgEntityUsersComponent,
OrgEventsComponent,
OrgExportComponent,
OrgExposedPasswordsReportComponent,
OrgGroupAddEditComponent,
OrgGroupsComponent,
OrgImportComponent,
OrgInactiveTwoFactorReportComponent,
OrgManageCollectionsComponent,
OrgManageComponent,
OrgPeopleComponent,
OrgPoliciesComponent,
OrgPolicyEditComponent,
OrgResetPasswordComponent,
OrgReusedPasswordsReportComponent,
OrgSettingComponent,
OrgToolsComponent,
OrgTwoFactorSetupComponent,
OrgSubscriptionHiddenComponent,
OrgUnsecuredWebsitesReportComponent,
OrgUserAddEditComponent,
OrgUserConfirmComponent,
OrgUserGroupsComponent,
OrgWeakPasswordsReportComponent,
GeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordGeneratorPolicyComponent,
PasswordRepromptComponent,
PasswordStrengthComponent,
PaymentComponent,
PaymentMethodComponent,
PersonalOwnershipPolicyComponent,
PreferencesComponent,
PremiumBadgeComponent,
PremiumComponent,
ProfileComponent,
ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RegisterComponent,
RemovePasswordComponent,
ReportCardComponent,
ReportListComponent,
ReportsComponent,
RequireSsoPolicyComponent,
ResetPasswordPolicyComponent,
ReusedPasswordsReportComponent,
SecurityComponent,
SecurityKeysComponent,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SendOptionsPolicyComponent,
SetPasswordComponent,
SettingsComponent,
ShareComponent,
SingleOrgPolicyComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
SsoComponent,
SubscriptionComponent,
TaxInfoComponent,
ToolsComponent,
TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
UpdateLicenseComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
UserBillingHistoryComponent,
UserLayoutComponent,
UserSubscriptionComponent,
UserVerificationComponent,
VaultTimeoutInputComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent,
],
exports: [
PremiumBadgeComponent,
AcceptEmergencyComponent,
AcceptOrganizationComponent,
AccessComponent,
AccountComponent,
AddCreditComponent,
AddEditComponent,
AddEditCustomFieldsComponent,
AddEditCustomFieldsComponent,
AdjustPaymentComponent,
AdjustStorageComponent,
AdjustSubscription,
ApiKeyComponent,
AttachmentsComponent,
BreachReportComponent,
BulkActionsComponent,
BulkDeleteComponent,
BulkMoveComponent,
BulkRestoreComponent,
BulkShareComponent,
ChangeEmailComponent,
ChangeKdfComponent,
ChangePasswordComponent,
ChangePlanComponent,
CiphersComponent,
CollectionsComponent,
CreateOrganizationComponent,
DeauthorizeSessionsComponent,
DeleteAccountComponent,
DeleteOrganizationComponent,
DisableSendPolicyComponent,
DomainRulesComponent,
DownloadLicenseComponent,
EmergencyAccessAddEditComponent,
EmergencyAccessAttachmentsComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
EmergencyAddEditComponent,
ExportComponent,
ExposedPasswordsReportComponent,
FamiliesForEnterpriseSetupComponent,
FolderAddEditComponent,
FooterComponent,
FrontendLayoutComponent,
HintComponent,
ImportComponent,
InactiveTwoFactorReportComponent,
LinkSsoComponent,
LockComponent,
LoginComponent,
MasterPasswordPolicyComponent,
NavbarComponent,
NestedCheckboxComponent,
OrganizationSwitcherComponent,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
OrganizationLayoutComponent,
OrganizationPlansComponent,
OrganizationSubscriptionComponent,
OrgAttachmentsComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
OrgBulkStatusComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
OrgEntityUsersComponent,
OrgEventsComponent,
OrgExportComponent,
OrgExposedPasswordsReportComponent,
OrgGroupAddEditComponent,
OrgGroupsComponent,
OrgImportComponent,
OrgInactiveTwoFactorReportComponent,
OrgManageCollectionsComponent,
OrgManageComponent,
OrgPeopleComponent,
OrgPoliciesComponent,
OrgPolicyEditComponent,
OrgResetPasswordComponent,
OrgReusedPasswordsReportComponent,
OrgSettingComponent,
OrgToolsComponent,
OrgTwoFactorSetupComponent,
OrgUnsecuredWebsitesReportComponent,
OrgUserAddEditComponent,
OrgUserConfirmComponent,
OrgUserGroupsComponent,
OrgWeakPasswordsReportComponent,
GeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordGeneratorPolicyComponent,
PasswordRepromptComponent,
PasswordStrengthComponent,
PaymentComponent,
PaymentMethodComponent,
PersonalOwnershipPolicyComponent,
PreferencesComponent,
PremiumBadgeComponent,
PremiumComponent,
ProfileComponent,
ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RegisterComponent,
RemovePasswordComponent,
ReportCardComponent,
ReportListComponent,
ReportsComponent,
RequireSsoPolicyComponent,
ResetPasswordPolicyComponent,
ReusedPasswordsReportComponent,
SecurityComponent,
SecurityKeysComponent,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SendOptionsPolicyComponent,
SetPasswordComponent,
SettingsComponent,
ShareComponent,
SingleOrgPolicyComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
SsoComponent,
SubscriptionComponent,
TaxInfoComponent,
ToolsComponent,
TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
UpdateLicenseComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
UserBillingHistoryComponent,
UserLayoutComponent,
UserSubscriptionComponent,
UserVerificationComponent,
VaultTimeoutInputComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent,
],
})
export class LooseComponentsModule {}

View File

@@ -0,0 +1,14 @@
import { Pipe, PipeTransform } from "@angular/core";
import { Organization } from "jslib-common/models/domain/organization";
@Pipe({
name: "orgNameFromId",
pure: true,
})
export class GetOrgNameFromIdPipe implements PipeTransform {
transform(value: string, organizations: Organization[]) {
const orgName = organizations.find((o) => o.id === value)?.name;
return orgName;
}
}

View File

@@ -0,0 +1,10 @@
import { NgModule } from "@angular/core";
import { GetOrgNameFromIdPipe } from "./get-organization-name.pipe";
@NgModule({
imports: [],
declarations: [GetOrgNameFromIdPipe],
exports: [GetOrgNameFromIdPipe],
})
export class PipesModule {}

View File

@@ -0,0 +1,149 @@
import { DragDropModule } from "@angular/cdk/drag-drop";
import { DatePipe, registerLocaleData, CommonModule } from "@angular/common";
import localeAf from "@angular/common/locales/af";
import localeAz from "@angular/common/locales/az";
import localeBe from "@angular/common/locales/be";
import localeBg from "@angular/common/locales/bg";
import localeBn from "@angular/common/locales/bn";
import localeBs from "@angular/common/locales/bs";
import localeCa from "@angular/common/locales/ca";
import localeCs from "@angular/common/locales/cs";
import localeDa from "@angular/common/locales/da";
import localeDe from "@angular/common/locales/de";
import localeEl from "@angular/common/locales/el";
import localeEnGb from "@angular/common/locales/en-GB";
import localeEnIn from "@angular/common/locales/en-IN";
import localeEo from "@angular/common/locales/eo";
import localeEs from "@angular/common/locales/es";
import localeEt from "@angular/common/locales/et";
import localeFi from "@angular/common/locales/fi";
import localeFil from "@angular/common/locales/fil";
import localeFr from "@angular/common/locales/fr";
import localeHe from "@angular/common/locales/he";
import localeHi from "@angular/common/locales/hi";
import localeHr from "@angular/common/locales/hr";
import localeHu from "@angular/common/locales/hu";
import localeId from "@angular/common/locales/id";
import localeIt from "@angular/common/locales/it";
import localeJa from "@angular/common/locales/ja";
import localeKa from "@angular/common/locales/ka";
import localeKm from "@angular/common/locales/km";
import localeKn from "@angular/common/locales/kn";
import localeKo from "@angular/common/locales/ko";
import localeLv from "@angular/common/locales/lv";
import localeMl from "@angular/common/locales/ml";
import localeNb from "@angular/common/locales/nb";
import localeNl from "@angular/common/locales/nl";
import localeNn from "@angular/common/locales/nn";
import localePl from "@angular/common/locales/pl";
import localePtBr from "@angular/common/locales/pt";
import localePtPt from "@angular/common/locales/pt-PT";
import localeRo from "@angular/common/locales/ro";
import localeRu from "@angular/common/locales/ru";
import localeSi from "@angular/common/locales/si";
import localeSk from "@angular/common/locales/sk";
import localeSl from "@angular/common/locales/sl";
import localeSr from "@angular/common/locales/sr";
import localeSv from "@angular/common/locales/sv";
import localeTr from "@angular/common/locales/tr";
import localeUk from "@angular/common/locales/uk";
import localeVi from "@angular/common/locales/vi";
import localeZhCn from "@angular/common/locales/zh-Hans";
import localeZhTw from "@angular/common/locales/zh-Hant";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { RouterModule } from "@angular/router";
import { BadgeModule, ButtonModule, CalloutModule, MenuModule } from "@bitwarden/components";
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { ToastrModule } from "ngx-toastr";
import { JslibModule } from "jslib-angular/jslib.module";
registerLocaleData(localeAf, "af");
registerLocaleData(localeAz, "az");
registerLocaleData(localeBe, "be");
registerLocaleData(localeBg, "bg");
registerLocaleData(localeBn, "bn");
registerLocaleData(localeBs, "bs");
registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, "cs");
registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, "el");
registerLocaleData(localeEnGb, "en-GB");
registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEo, "eo");
registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, "et");
registerLocaleData(localeFi, "fi");
registerLocaleData(localeFil, "fil");
registerLocaleData(localeFr, "fr");
registerLocaleData(localeHe, "he");
registerLocaleData(localeHi, "hi");
registerLocaleData(localeHr, "hr");
registerLocaleData(localeHu, "hu");
registerLocaleData(localeId, "id");
registerLocaleData(localeIt, "it");
registerLocaleData(localeJa, "ja");
registerLocaleData(localeKa, "ka");
registerLocaleData(localeKm, "km");
registerLocaleData(localeKn, "kn");
registerLocaleData(localeKo, "ko");
registerLocaleData(localeLv, "lv");
registerLocaleData(localeMl, "ml");
registerLocaleData(localeNb, "nb");
registerLocaleData(localeNl, "nl");
registerLocaleData(localeNn, "nn");
registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, "pt-PT");
registerLocaleData(localeRo, "ro");
registerLocaleData(localeRu, "ru");
registerLocaleData(localeSi, "si");
registerLocaleData(localeSk, "sk");
registerLocaleData(localeSl, "sl");
registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, "sv");
registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, "uk");
registerLocaleData(localeVi, "vi");
registerLocaleData(localeZhCn, "zh-CN");
registerLocaleData(localeZhTw, "zh-TW");
@NgModule({
imports: [
CommonModule,
DragDropModule,
FormsModule,
InfiniteScrollModule,
JslibModule,
ReactiveFormsModule,
RouterModule,
BadgeModule,
ButtonModule,
CalloutModule,
ToastrModule,
BadgeModule,
ButtonModule,
MenuModule,
],
exports: [
CommonModule,
DragDropModule,
FormsModule,
InfiniteScrollModule,
JslibModule,
ReactiveFormsModule,
RouterModule,
BadgeModule,
ButtonModule,
CalloutModule,
ToastrModule,
BadgeModule,
ButtonModule,
MenuModule,
],
providers: [DatePipe],
bootstrap: [],
})
export class SharedModule {}

View File

@@ -0,0 +1,74 @@
<ng-container *ngIf="show">
<div class="filter-heading">
<button
(click)="toggleCollapse(collectionsGrouping)"
[attr.aria-expanded]="!isCollapsed(collectionsGrouping)"
aria-controls="collection-filters"
title="{{ 'toggleCollapse' | i18n }}"
class="toggle-button"
>
<i
class="bwi bwi-fw"
[ngClass]="{
'bwi-angle-right': isCollapsed(collectionsGrouping),
'bwi-angle-down': !isCollapsed(collectionsGrouping)
}"
aria-hidden="true"
></i>
</button>
<h3 class="filter-title">{{ collectionsGrouping.name | i18n }}</h3>
</div>
<ul id="collection-filters" *ngIf="!isCollapsed(collectionsGrouping)" class="filter-options">
<ng-template #recursiveCollections let-collections>
<li
*ngFor="let c of collections"
[ngClass]="{
active: c.node.id === activeFilter.selectedCollectionId
}"
class="filter-option"
>
<span class="filter-buttons">
<button
class="toggle-button"
*ngIf="c.children.length"
(click)="collapse(c.node)"
title="{{ 'toggleCollapse' | i18n }}"
[attr.aria-expanded]="!isCollapsed(c.node)"
[attr.aria-controls]="c.node.name + '_children'"
>
<i
class="bwi bwi-fw"
[ngClass]="{
'bwi-angle-right': isCollapsed(c.node),
'bwi-angle-down': !isCollapsed(c.node)
}"
aria-hidden="true"
></i>
</button>
<button class="filter-button" (click)="applyFilter(c.node)">
<i
*ngIf="c.children.length === 0"
class="bwi bwi-collection bwi-fw"
aria-hidden="true"
></i
>{{ c.node.name }}
</button>
</span>
<ul
[id]="c.node.name + '_children'"
class="nested-filter-options"
*ngIf="c.children.length && !isCollapsed(c.node)"
>
<ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: c.children }"
>
</ng-container>
</ul>
</li>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: nestedCollections }"
>
</ng-container>
</ul>
</ng-container>

View File

@@ -0,0 +1,9 @@
import { Component } from "@angular/core";
import { CollectionFilterComponent as BaseCollectionFilterComponent } from "jslib-angular/modules/vault-filter/components/collection-filter.component";
@Component({
selector: "app-collection-filter",
templateUrl: "collection-filter.component.html",
})
export class CollectionFilterComponent extends BaseCollectionFilterComponent {}

View File

@@ -0,0 +1,84 @@
<ng-container *ngIf="!hide && !activeFilter.selectedOrganizationId">
<div class="filter-heading">
<button
class="toggle-button"
(click)="toggleCollapse(foldersGrouping)"
[attr.aria-expanded]="!isCollapsed(foldersGrouping)"
aria-controls="folder-filters"
title="{{ 'toggleCollapse' | i18n }}"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed(foldersGrouping),
'bwi-angle-down': !isCollapsed(foldersGrouping)
}"
></i>
</button>
<h3 class="filter-title">
{{ "folders" | i18n }}
</h3>
<button
class="text-muted ml-auto add-button"
(click)="addFolder()"
appA11yTitle="{{ 'addFolder' | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</button>
</div>
<ul id="folder-filters" *ngIf="!isCollapsed(foldersGrouping)" class="filter-options">
<ng-template #recursiveFolders let-folders>
<li
*ngFor="let f of folders"
[ngClass]="{
active: f.node.id === activeFilter.selectedFolderId && activeFilter.selectedFolder
}"
class="filter-option"
>
<span class="filter-buttons">
<button
*ngIf="f.children.length"
title="{{ 'toggleCollapse' | i18n }}"
(click)="toggleCollapse(f.node)"
[attr.aria-expanded]="!isCollapsed(f.node)"
[attr.aria-controls]="f.node.name + '_children'"
class="toggle-button"
>
<i
class="bwi bwi-fw"
[ngClass]="{
'bwi-angle-right': isCollapsed(f.node),
'bwi-angle-down': !isCollapsed(f.node)
}"
aria-hidden="true"
></i>
</button>
<button class="filter-button" (click)="applyFilter(f.node)">
<i *ngIf="f.children.length === 0" class="bwi bwi-fw bwi-folder" aria-hidden="true"></i
>{{ f.node.name }}
</button>
<button
class="edit-button"
(click)="editFolder(f.node)"
appA11yTitle="{{ 'editFolder' | i18n }}"
*ngIf="f.node.id"
>
<i class="bwi bwi-pencil bwi-fw" aria-hidden="true"></i>
</button>
</span>
<ul
[id]="f.node.name + '_children'"
class="nested-filter-options"
*ngIf="f.children.length && !isCollapsed(f.node)"
>
<ng-container *ngTemplateOutlet="recursiveFolders; context: { $implicit: f.children }">
</ng-container>
</ul>
</li>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveFolders; context: { $implicit: nestedFolders }"
></ng-container>
</ul>
</ng-container>

View File

@@ -0,0 +1,9 @@
import { Component } from "@angular/core";
import { FolderFilterComponent as BaseFolderFilterComponent } from "jslib-angular/modules/vault-filter/components/folder-filter.component";
@Component({
selector: "app-folder-filter",
templateUrl: "folder-filter.component.html",
})
export class FolderFilterComponent extends BaseFolderFilterComponent {}

View File

@@ -0,0 +1,157 @@
<ng-container *ngIf="!hide">
<ng-container [ngSwitch]="displayMode">
<ng-container *ngSwitchCase="'noOrganizations'">
<ul class="filter-options">
<li class="filter-option active">
<span class="filter-buttons">
<button class="filter-button">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
{{ "myVault" | i18n }}
</button>
</span>
</li>
<li class="filter-option">
<span class="filter-buttons">
<a href="#" routerLink="/settings/create-organization" class="filter-button">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newOrganization" | i18n }}
</a>
</span>
</li>
</ul>
</ng-container>
<ng-container *ngSwitchCase="'personalOwnershipPolicy'">
<div class="filter-heading">
<button
(click)="toggleCollapse()"
title="{{ 'toggleCollapse' | i18n }}"
class="toggle-button"
[attr.aria-expanded]="!isCollapsed"
aria-controls="organization-filters"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed
}"
></i>
</button>
<button
class="filter-button"
(click)="clearFilter()"
[ngClass]="{ active: !hasActiveFilter }"
>
&nbsp;{{ organizationGrouping.name | i18n }}
</button>
<a
href="#"
routerLink="/settings/create-organization"
class="text-muted ml-auto create-organization-link"
appA11yTitle="{{ 'addOrganization' | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</a>
</div>
<ul id="organization-filters" *ngIf="!isCollapsed" class="filter-options">
<li
class="filter-option"
*ngFor="let organization of organizations"
[ngClass]="{ active: organization.id === activeFilter.selectedOrganizationId }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyOrganizationFilter(organization)">
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
{{ organization.name }}
</button>
<ng-container *ngIf="organization.id === activeFilter.selectedOrganizationId">
<button [bitMenuTriggerFor]="orgMenu" class="org-options ml-auto">
<i class="bwi bwi-ellipsis-v" aria-hidden="true"></i>
</button>
<bit-menu class="filter-organization-options" #orgMenu>
<app-organization-options [organization]="organization"></app-organization-options>
</bit-menu>
</ng-container>
</span>
</li>
</ul>
</ng-container>
<ng-container *ngSwitchCase="'singleOrganizationAndPersonalOwnershipPolicies'">
<ul class="filter-options">
<li class="filter-option active">
<button class="filter-button">
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
{{ organizations[0].name }}
</button>
</li>
</ul>
</ng-container>
<ng-container *ngSwitchCase="'organizationMember'">
<div class="filter-heading">
<button
class="toggle-button"
title="{{ 'toggleCollapse' | i18n }}"
(click)="toggleCollapse()"
[attr.aria-expanded]="!isCollapsed"
aria-controls="organization-filters"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed
}"
></i>
</button>
<button
class="filter-button"
(click)="clearFilter()"
[ngClass]="{ active: !hasActiveFilter }"
>
&nbsp;{{ organizationGrouping.name | i18n }}
</button>
<a
href="#"
routerLink="/settings/create-organization"
class="text-muted ml-auto create-organization-link"
appA11yTitle="{{ 'addOrganization' | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</a>
</div>
<ul id="organization-filters" *ngIf="!isCollapsed" class="filter-options">
<li class="filter-option" [ngClass]="{ active: activeFilter.myVaultOnly }">
<span class="filter-buttons">
<button class="filter-button" (click)="applyMyVaultFilter()">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
{{ "myVault" | i18n }}
</button>
</span>
</li>
<li
class="filter-option"
*ngFor="let organization of organizations"
[ngClass]="{ active: organization.id === activeFilter.selectedOrganizationId }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyOrganizationFilter(organization)">
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
{{ organization.name }}
</button>
<ng-container *ngIf="organization.id === activeFilter.selectedOrganizationId">
<button [bitMenuTriggerFor]="orgMenu" class="org-options ml-auto">
<i class="bwi bwi-ellipsis-v" aria-hidden="true"></i>
</button>
<bit-menu class="filter-organization-options" #orgMenu>
<app-organization-options [organization]="organization"></app-organization-options>
</bit-menu>
</ng-container>
</span>
</li>
</ul>
</ng-container>
</ng-container>
<hr />
</ng-container>

View File

@@ -0,0 +1,11 @@
import { Component } from "@angular/core";
import { OrganizationFilterComponent as BaseOrganizationFilterComponent } from "jslib-angular/modules/vault-filter/components/organization-filter.component";
@Component({
selector: "app-organization-filter",
templateUrl: "organization-filter.component.html",
})
export class OrganizationFilterComponent extends BaseOrganizationFilterComponent {
displayText = "allVaults";
}

View File

@@ -0,0 +1,43 @@
<ng-container *ngIf="!loaded">
<i
class="bwi bwi-spinner bwi-spin text-muted tw-m-2"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<div *ngIf="loaded" class="tw-max-w-[300px] tw-min-w-[200px] tw-flex tw-flex-col">
<button
*ngIf="allowEnrollmentChanges(organization) && !organization.resetPasswordEnrolled"
class="dropdown-item"
(click)="toggleResetPasswordEnrollment(organization)"
>
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
{{ "enrollPasswordReset" | i18n }}
</button>
<button
*ngIf="allowEnrollmentChanges(organization) && organization.resetPasswordEnrolled"
class="dropdown-item"
(click)="toggleResetPasswordEnrollment(organization)"
>
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
{{ "withdrawPasswordReset" | i18n }}
</button>
<ng-container *ngIf="organization.useSso && organization.identifier">
<button
*ngIf="organization.ssoBound; else linkSso"
class="dropdown-item"
(click)="unlinkSso(organization)"
>
<i class="bwi bwi-fw bwi-chain-broken" aria-hidden="true"></i>
{{ "unlinkSso" | i18n }}
</button>
<ng-template #linkSso>
<app-link-sso [organization]="organization"> </app-link-sso>
</ng-template>
</ng-container>
<button class="dropdown-item text-danger" (click)="leave(organization)">
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
{{ "leave" | i18n }}
</button>
</div>

View File

@@ -1,10 +1,9 @@
import { Component, Input, OnInit } from "@angular/core";
import { Component, Input } from "@angular/core";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
@@ -15,19 +14,17 @@ import { Policy } from "jslib-common/models/domain/policy";
import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
@Component({
selector: "app-organizations",
templateUrl: "organizations.component.html",
selector: "app-organization-options",
templateUrl: "organization-options.component.html",
})
export class OrganizationsComponent implements OnInit {
@Input() vault = false;
organizations: Organization[];
export class OrganizationOptionsComponent {
actionPromise: Promise<any>;
policies: Policy[];
loaded = false;
actionPromise: Promise<any>;
@Input() organization: Organization;
constructor(
private organizationService: OrganizationService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private apiService: ApiService,
@@ -38,16 +35,11 @@ export class OrganizationsComponent implements OnInit {
) {}
async ngOnInit() {
if (!this.vault) {
await this.syncService.fullSync(true);
await this.load();
}
await this.syncService.fullSync(true);
await this.load();
}
async load() {
const orgs = await this.organizationService.getAll();
orgs.sort(Utils.getSortFunction(this.i18nService, "name"));
this.organizations = orgs;
this.policies = await this.policyService.getAll(PolicyType.ResetPassword);
this.loaded = true;
}

View File

@@ -0,0 +1,33 @@
<ng-container *ngIf="show">
<ul class="filter-options">
<li class="filter-option" [ngClass]="{ active: activeFilter.status === 'all' }">
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter('all')">
<i class="bwi bwi-fw bwi-filter" aria-hidden="true"></i>&nbsp;{{ "allItems" | i18n }}
</button>
</span>
</li>
<li
*ngIf="!hideFavorites"
class="filter-option"
[ngClass]="{ active: activeFilter.status === 'favorites' }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter('favorites')">
<i class="bwi bwi-fw bwi-star" aria-hidden="true"></i>&nbsp;{{ "favorites" | i18n }}
</button>
</span>
</li>
<li
*ngIf="!hideTrash"
class="filter-option"
[ngClass]="{ active: activeFilter.status === 'trash' }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter('trash')">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>&nbsp;{{ "trash" | i18n }}
</button>
</span>
</li>
</ul>
</ng-container>

View File

@@ -0,0 +1,9 @@
import { Component } from "@angular/core";
import { StatusFilterComponent as BaseStatusFilterComponent } from "jslib-angular/modules/vault-filter/components/status-filter.component";
@Component({
selector: "app-status-filter",
templateUrl: "status-filter.component.html",
})
export class StatusFilterComponent extends BaseStatusFilterComponent {}

View File

@@ -0,0 +1,60 @@
<div class="filter-heading">
<button
class="toggle-button"
[attr.aria-expanded]="!isCollapsed"
aria-controls="type-filters"
(click)="toggleCollapse()"
title="{{ 'toggleCollapse' | i18n }}"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed
}"
></i>
</button>
<h3>
{{ "types" | i18n }}
</h3>
</div>
<ul id="type-filters" *ngIf="!isCollapsed" class="filter-options">
<li
class="filter-option"
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Login }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Login)">
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>{{ "typeLogin" | i18n }}
</button>
</span>
</li>
<li class="filter-option" [ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Card }">
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Card)">
<i class="bwi bwi-fw bwi-credit-card" aria-hidden="true"></i>{{ "typeCard" | i18n }}
</button>
</span>
</li>
<li
class="filter-option"
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Identity }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.Identity)">
<i class="bwi bwi-fw bwi-id-card" aria-hidden="true"></i>{{ "typeIdentity" | i18n }}
</button>
</span>
</li>
<li
class="filter-option"
[ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.SecureNote }"
>
<span class="filter-buttons">
<button class="filter-button" (click)="applyFilter(cipherTypeEnum.SecureNote)">
<i class="bwi bwi-fw bwi-sticky-note" aria-hidden="true"></i>{{ "typeSecureNote" | i18n }}
</button>
</span>
</li>
</ul>

View File

@@ -0,0 +1,9 @@
import { Component } from "@angular/core";
import { TypeFilterComponent as BaseTypeFilterComponent } from "jslib-angular/modules/vault-filter/components/type-filter.component";
@Component({
selector: "app-type-filter",
templateUrl: "type-filter.component.html",
})
export class TypeFilterComponent extends BaseTypeFilterComponent {}

View File

@@ -0,0 +1,82 @@
<div class="card vault-filters">
<div class="container loading-spinner" *ngIf="!isLoaded">
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
</div>
<div *ngIf="isLoaded">
<div class="card-header d-flex">
{{ "filters" | i18n }}
<a
class="ml-auto"
href="https://bitwarden.com/help/searching-vault/"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div class="card-body">
<input
type="search"
placeholder="{{ (searchPlaceholder | i18n) || ('searchVault' | i18n) }}"
id="search"
class="form-control"
[(ngModel)]="searchText"
(input)="searchTextChanged()"
autocomplete="off"
appAutofocus
/>
<div class="filter">
<app-organization-filter
*ngIf="showOrgFilter"
[hide]="hideOrganizations"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[organizations]="organizations"
[activePersonalOwnershipPolicy]="activePersonalOwnershipPolicy"
[activeSingleOrganizationPolicy]="activeSingleOrganizationPolicy"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-organization-filter>
</div>
<div class="filter">
<app-status-filter
[hideFavorites]="!showFavorites"
[hideTrash]="hideTrash"
[activeFilter]="activeFilter"
(onFilterChange)="applyFilter($event)"
></app-status-filter>
</div>
<div class="filter">
<app-type-filter
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-type-filter>
</div>
<div class="filter">
<app-folder-filter
[hide]="!showFolders"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[folderNodes]="folders"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
(onAddFolder)="addFolder()"
(onEditFolder)="editFolder($event)"
></app-folder-filter>
</div>
<div class="filter">
<app-collection-filter
[hide]="hideCollections"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[collectionNodes]="collections"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-collection-filter>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,40 @@
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { VaultFilterComponent as BaseVaultFilterComponent } from "jslib-angular/modules/vault-filter/vault-filter.component";
import { VaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
import { Organization } from "jslib-common/models/domain/organization";
@Component({
selector: "app-vault-filter",
templateUrl: "vault-filter.component.html",
})
export class VaultFilterComponent extends BaseVaultFilterComponent {
@Input() showOrgFilter = true;
@Input() showFolders = true;
@Input() showFavorites = true;
@Output() onSearchTextChanged = new EventEmitter<string>();
searchPlaceholder: string;
searchText = "";
organization: Organization;
constructor(vaultFilterService: VaultFilterService) {
super(vaultFilterService);
}
searchTextChanged() {
this.onSearchTextChanged.emit(this.searchText);
}
// This method exists because the vault component gets its data mixed up during the initial sync on first login. It looks for data before the sync is complete.
// It should be removed as soon as doing so makes sense.
async reloadOrganizations() {
this.organizations = await this.vaultFilterService.buildOrganizations();
}
async initCollections() {
return await this.vaultFilterService.buildCollections(this.organization?.id);
}
}

View File

@@ -0,0 +1,48 @@
import { NgModule } from "@angular/core";
import { VaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SharedModule } from "../shared.module";
import { CollectionFilterComponent } from "./components/collection-filter.component";
import { FolderFilterComponent } from "./components/folder-filter.component";
import { OrganizationFilterComponent } from "./components/organization-filter.component";
import { OrganizationOptionsComponent } from "./components/organization-options.component";
import { StatusFilterComponent } from "./components/status-filter.component";
import { TypeFilterComponent } from "./components/type-filter.component";
import { VaultFilterComponent } from "./vault-filter.component";
@NgModule({
imports: [SharedModule],
declarations: [
VaultFilterComponent,
CollectionFilterComponent,
FolderFilterComponent,
OrganizationFilterComponent,
OrganizationOptionsComponent,
StatusFilterComponent,
TypeFilterComponent,
],
exports: [VaultFilterComponent],
providers: [
{
provide: VaultFilterService,
useClass: VaultFilterService,
deps: [
StateService,
OrganizationService,
FolderService,
CipherService,
CollectionService,
PolicyService,
],
},
],
})
export class VaultFilterModule {}

View File

@@ -0,0 +1,3 @@
import { VaultFilterService as BaseVaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
export class VaultFilterService extends BaseVaultFilterService {}

View File

@@ -0,0 +1,16 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { IndividualVaultComponent } from "./individual-vault.component";
const routes: Routes = [
{
path: "",
component: IndividualVaultComponent,
data: { titleId: "vaults" },
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class IndividualVaultRoutingModule {}

View File

@@ -1,23 +1,25 @@
<div class="container page-content">
<div class="row">
<div class="col-3">
<app-vault-groupings
(onAllClicked)="clearGroupingFilters()"
(onFavoritesClicked)="filterFavorites()"
(onCipherTypeClicked)="filterCipherType($event)"
(onFolderClicked)="filterFolder($event.id)"
(onAddFolder)="addFolder()"
(onEditFolder)="editFolder($event.id)"
(onCollectionClicked)="filterCollection($event.id)"
(onSearchTextChanged)="filterSearchText($event)"
(onTrashClicked)="filterDeleted()"
>
</app-vault-groupings>
<div class="groupings">
<div class="content">
<div class="inner-content">
<app-vault-filter
#vaultFilter
[activeFilter]="activeFilter"
(onFilterChange)="applyVaultFilter($event)"
(onAddFolder)="addFolder()"
(onEditFolder)="editFolder($event.id)"
(onSearchTextChanged)="filterSearchText($event)"
></app-vault-filter>
</div>
</div>
</div>
</div>
<div class="col-6">
<div [ngClass]="{ 'col-6': isShowingCards, 'col-9': !isShowingCards }">
<div class="page-header d-flex">
<h1>
{{ "myVault" | i18n }}
{{ "vaultItems" | i18n }}
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
<ng-container *ngIf="actionSpinner.loading">
<i
@@ -52,6 +54,7 @@
(onShareClicked)="shareCipher($event)"
(onCollectionsClicked)="editCipherCollections($event)"
(onCloneClicked)="cloneCipher($event)"
(onOrganzationBadgeClicked)="applyOrganizationFilter($event)"
>
</app-vault-ciphers>
</div>
@@ -97,40 +100,6 @@
</a>
</div>
</div>
<div class="card mb-4">
<div class="card-header d-flex">
{{ "organizations" | i18n }}
<a
class="ml-auto"
href="https://bitwarden.com/help/about-organizations/"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div class="card-body">
<app-organizations [vault]="true"></app-organizations>
</div>
</div>
<div class="card mt-4" *ngIf="showProviders">
<div class="card-header d-flex">
{{ "providers" | i18n }}
<a
class="ml-auto"
href="https://bitwarden.com/help/providers/"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div class="card-body">
<app-providers vault="true"></app-providers>
</div>
</div>
</div>
</div>
</div>

View File

@@ -10,6 +10,7 @@ import {
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { VaultFilter } from "jslib-angular/modules/vault-filter/models/vault-filter.model";
import { ModalService } from "jslib-angular/services/modal.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
@@ -17,36 +18,33 @@ import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { CipherType } from "jslib-common/enums/cipherType";
import { CipherView } from "jslib-common/models/view/cipherView";
import { OrganizationsComponent } from "../settings/organizations.component";
import { UpdateKeyComponent } from "../settings/update-key.component";
import { AddEditComponent } from "./add-edit.component";
import { AttachmentsComponent } from "./attachments.component";
import { CiphersComponent } from "./ciphers.component";
import { CollectionsComponent } from "./collections.component";
import { FolderAddEditComponent } from "./folder-add-edit.component";
import { GroupingsComponent } from "./groupings.component";
import { ShareComponent } from "./share.component";
import { UpdateKeyComponent } from "../../../../settings/update-key.component";
import { AddEditComponent } from "../../../../vault/add-edit.component";
import { AttachmentsComponent } from "../../../../vault/attachments.component";
import { CiphersComponent } from "../../../../vault/ciphers.component";
import { CollectionsComponent } from "../../../../vault/collections.component";
import { FolderAddEditComponent } from "../../../../vault/folder-add-edit.component";
import { ShareComponent } from "../../../../vault/share.component";
import { VaultFilterComponent } from "../../../vault-filter/vault-filter.component";
import { VaultService } from "../../vault.service";
const BroadcasterSubscriptionId = "VaultComponent";
@Component({
selector: "app-vault",
templateUrl: "vault.component.html",
templateUrl: "individual-vault.component.html",
})
export class VaultComponent implements OnInit, OnDestroy {
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
export class IndividualVaultComponent implements OnInit, OnDestroy {
@ViewChild("vaultFilter", { static: true }) filterComponent: VaultFilterComponent;
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
@ViewChild(OrganizationsComponent, { static: true })
organizationsComponent: OrganizationsComponent;
@ViewChild("attachments", { read: ViewContainerRef, static: true })
attachmentsModalRef: ViewContainerRef;
@ViewChild("folderAddEdit", { read: ViewContainerRef, static: true })
@@ -63,13 +61,15 @@ export class VaultComponent implements OnInit, OnDestroy {
type: CipherType = null;
folderId: string = null;
collectionId: string = null;
organizationId: string = null;
myVaultOnly = false;
showVerifyEmail = false;
showBrowserOutdated = false;
showUpdateKey = false;
showPremiumCallout = false;
showProviders = false;
deleted = false;
trashCleanupWarning: string = null;
activeFilter: VaultFilter = new VaultFilter();
constructor(
private syncService: SyncService,
@@ -86,8 +86,9 @@ export class VaultComponent implements OnInit, OnDestroy {
private ngZone: NgZone,
private stateService: StateService,
private organizationService: OrganizationService,
private providerService: ProviderService,
private cipherService: CipherService
private vaultService: VaultService,
private cipherService: CipherService,
private passwordRepromptService: PasswordRepromptService
) {}
async ngOnInit() {
@@ -101,42 +102,24 @@ export class VaultComponent implements OnInit, OnDestroy {
this.route.queryParams.pipe(first()).subscribe(async (params) => {
await this.syncService.fullSync(false);
const canAccessPremium = await this.stateService.getCanAccessPremium();
this.showPremiumCallout =
!this.showVerifyEmail && !canAccessPremium && !this.platformUtilsService.isSelfHost();
this.showProviders = (await this.providerService.getAll()).length > 0;
await Promise.all([this.groupingsComponent.load(), this.organizationsComponent.load()]);
this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
this.filterComponent.reloadOrganizations();
this.showUpdateKey = !(await this.cryptoService.hasEncKey());
if (params == null) {
this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.reload();
} else {
if (params.deleted) {
this.groupingsComponent.selectedTrash = true;
await this.filterDeleted();
} else if (params.favorites) {
this.groupingsComponent.selectedFavorites = true;
await this.filterFavorites();
} else if (params.type) {
const t = parseInt(params.type, null);
this.groupingsComponent.selectedType = t;
await this.filterCipherType(t);
} else if (params.folderId) {
this.groupingsComponent.selectedFolder = true;
this.groupingsComponent.selectedFolderId = params.folderId;
await this.filterFolder(params.folderId);
} else if (params.collectionId) {
this.groupingsComponent.selectedCollectionId = params.collectionId;
await this.filterCollection(params.collectionId);
} else {
this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.reload();
if (params.cipherId) {
const cipherView = new CipherView();
cipherView.id = params.cipherId;
if (params.action === "clone") {
await this.cloneCipher(cipherView);
} else if (params.action === "edit") {
await this.editCipher(cipherView);
}
}
await this.ciphersComponent.reload();
this.route.queryParams.subscribe(async (params) => {
if (params.cipherId) {
@@ -162,8 +145,8 @@ export class VaultComponent implements OnInit, OnDestroy {
case "syncCompleted":
if (message.successfully) {
await Promise.all([
this.groupingsComponent.load(),
this.organizationsComponent.load(),
this.filterComponent.reloadCollectionsAndFolders(this.activeFilter),
this.filterComponent.reloadOrganizations(),
this.ciphersComponent.load(this.ciphersComponent.filter),
]);
this.changeDetectorRef.detectChanges();
@@ -175,72 +158,78 @@ export class VaultComponent implements OnInit, OnDestroy {
});
}
get isShowingCards() {
return (
this.showBrowserOutdated ||
this.showPremiumCallout ||
this.showUpdateKey ||
this.showVerifyEmail
);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async clearGroupingFilters() {
this.ciphersComponent.showAddNew = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchVault");
await this.ciphersComponent.reload();
this.clearFilters();
this.go();
}
async filterFavorites() {
this.ciphersComponent.showAddNew = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchFavorites");
await this.ciphersComponent.reload((c) => c.favorite);
this.clearFilters();
this.favorites = true;
this.go();
}
async filterDeleted() {
this.ciphersComponent.showAddNew = false;
this.ciphersComponent.deleted = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchTrash");
await this.ciphersComponent.reload(null, true);
this.clearFilters();
this.deleted = true;
this.go();
}
async filterCipherType(type: CipherType) {
this.ciphersComponent.showAddNew = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchType");
await this.ciphersComponent.reload((c) => c.type === type);
this.clearFilters();
this.type = type;
this.go();
}
async filterFolder(folderId: string) {
this.ciphersComponent.showAddNew = true;
folderId = folderId === "none" ? null : folderId;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchFolder");
await this.ciphersComponent.reload((c) => c.folderId === folderId);
this.clearFilters();
this.folderId = folderId == null ? "none" : folderId;
this.go();
}
async filterCollection(collectionId: string) {
this.ciphersComponent.showAddNew = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchCollection");
await this.ciphersComponent.reload(
(c) => c.collectionIds != null && c.collectionIds.indexOf(collectionId) > -1
async applyVaultFilter(vaultFilter: VaultFilter) {
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
this.activeFilter = vaultFilter;
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
this.filterComponent.searchPlaceholder = this.vaultService.calculateSearchBarLocalizationString(
this.activeFilter
);
this.clearFilters();
this.collectionId = collectionId;
this.go();
}
async applyOrganizationFilter(orgId: string) {
if (orgId == null) {
this.activeFilter.resetOrganization();
this.activeFilter.myVaultOnly = true;
} else {
this.activeFilter.selectedOrganizationId = orgId;
}
await this.applyVaultFilter(this.activeFilter);
}
filterSearchText(searchText: string) {
this.ciphersComponent.searchText = searchText;
this.ciphersComponent.search(200);
}
private buildFilter(): (cipher: CipherView) => boolean {
return (cipher) => {
let cipherPassesFilter = true;
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
cipherPassesFilter = cipher.favorite;
}
if (this.activeFilter.status === "trash" && cipherPassesFilter) {
cipherPassesFilter = cipher.isDeleted;
}
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
}
if (
this.activeFilter.selectedFolderId != null &&
this.activeFilter.selectedFolderId != "none" &&
cipherPassesFilter
) {
cipherPassesFilter = cipher.folderId === this.activeFilter.selectedFolderId;
}
if (this.activeFilter.selectedCollectionId != null && cipherPassesFilter) {
cipherPassesFilter =
cipher.collectionIds != null &&
cipher.collectionIds.indexOf(this.activeFilter.selectedCollectionId) > -1;
}
if (this.activeFilter.selectedOrganizationId != null && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === this.activeFilter.selectedOrganizationId;
}
if (this.activeFilter.myVaultOnly && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === null;
}
return cipherPassesFilter;
};
}
async editCipherAttachments(cipher: CipherView) {
const canAccessPremium = await this.stateService.getCanAccessPremium();
if (cipher.organizationId == null && !canAccessPremium) {
@@ -312,7 +301,7 @@ export class VaultComponent implements OnInit, OnDestroy {
comp.folderId = null;
comp.onSavedFolder.subscribe(async () => {
modal.close();
await this.groupingsComponent.loadFolders();
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
});
}
);
@@ -326,13 +315,11 @@ export class VaultComponent implements OnInit, OnDestroy {
comp.folderId = folderId;
comp.onSavedFolder.subscribe(async () => {
modal.close();
await this.groupingsComponent.loadFolders();
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
});
comp.onDeletedFolder.subscribe(async () => {
modal.close();
await this.groupingsComponent.loadFolders();
await this.filterFolder("none");
this.groupingsComponent.selectedFolderId = null;
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
});
}
);
@@ -342,15 +329,21 @@ export class VaultComponent implements OnInit, OnDestroy {
const component = await this.editCipher(null);
component.type = this.type;
component.folderId = this.folderId === "none" ? null : this.folderId;
if (this.collectionId != null) {
const collection = this.groupingsComponent.collections.filter(
(c) => c.id === this.collectionId
if (this.activeFilter.selectedCollectionId != null) {
const collection = this.filterComponent.collections.fullList.filter(
(c) => c.id === this.activeFilter.selectedCollectionId
);
if (collection.length > 0) {
component.organizationId = collection[0].organizationId;
component.collectionIds = [this.collectionId];
component.collectionIds = [this.activeFilter.selectedCollectionId];
}
}
if (this.activeFilter.selectedFolderId && this.activeFilter.selectedFolder) {
component.folderId = this.activeFilter.selectedFolderId;
}
if (this.activeFilter.selectedOrganizationId) {
component.organizationId = this.activeFilter.selectedOrganizationId;
}
}
async editCipher(cipher: CipherView) {
@@ -358,6 +351,14 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async editCipherId(id: string) {
const cipher = await this.cipherService.get(id);
if (cipher.reprompt != 0) {
if (!(await this.passwordRepromptService.showPasswordPrompt())) {
this.go({ cipherId: null });
return;
}
}
const [modal, childComponent] = await this.modalService.openViewRef(
AddEditComponent,
this.cipherAddEditModalRef,
@@ -379,8 +380,7 @@ export class VaultComponent implements OnInit, OnDestroy {
);
modal.onClosedPromise().then(() => {
this.route.params;
this.router.navigate([], { queryParams: { cipherId: null }, queryParamsHandling: "merge" });
this.go({ cipherId: null });
});
return childComponent;
@@ -395,14 +395,6 @@ export class VaultComponent implements OnInit, OnDestroy {
await this.modalService.openViewRef(UpdateKeyComponent, this.updateKeyModalRef);
}
private clearFilters() {
this.folderId = null;
this.collectionId = null;
this.favorites = false;
this.type = null;
this.deleted = false;
}
private go(queryParams: any = null) {
if (queryParams == null) {
queryParams = {

View File

@@ -0,0 +1,13 @@
import { NgModule } from "@angular/core";
import { VaultModule } from "../../vault.module";
import { IndividualVaultRoutingModule } from "./individual-vault-routing.module";
import { IndividualVaultComponent } from "./individual-vault.component";
@NgModule({
imports: [VaultModule, IndividualVaultRoutingModule],
declarations: [IndividualVaultComponent],
exports: [IndividualVaultComponent],
})
export class IndividualVaultModule {}

View File

@@ -0,0 +1,12 @@
import { NgModule } from "@angular/core";
import { SharedModule } from "../../../shared.module";
import { OrganizationNameBadgeComponent } from "./organization-name-badge.component";
@NgModule({
imports: [SharedModule],
declarations: [OrganizationNameBadgeComponent],
exports: [OrganizationNameBadgeComponent],
})
export class OrganizationBadgeModule {}

View File

@@ -0,0 +1,9 @@
<button
bit-badge
[style.color]="textColor"
[style.background-color]="color"
appA11yTitle="{{ organizationName }}"
(click)="emitOnOrganizationClicked()"
>
{{ organizationName | ellipsis: 13 }}
</button>

View File

@@ -0,0 +1,59 @@
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@Component({
selector: "app-org-badge",
templateUrl: "organization-name-badge.component.html",
})
export class OrganizationNameBadgeComponent implements OnInit {
@Input() organizationName: string;
@Input() profileName: string;
@Output() onOrganizationClicked = new EventEmitter<string>();
color: string;
textColor: string;
constructor(private i18nService: I18nService) {}
ngOnInit(): void {
if (this.organizationName == null || this.organizationName === "") {
this.organizationName = this.i18nService.t("me");
this.color = this.stringToColor(this.profileName.toUpperCase());
}
if (this.color == null) {
this.color = this.stringToColor(this.organizationName.toUpperCase());
}
this.textColor = this.pickTextColorBasedOnBgColor();
}
// This value currently isn't stored anywhere, only calculated in the app-avatar component
// Once we are allowing org colors to be changed and saved, change this out
private stringToColor(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let color = "#";
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff;
color += ("00" + value.toString(16)).substr(-2);
}
return color;
}
// There are a few ways to calculate text color for contrast, this one seems to fit accessibility guidelines best.
// https://stackoverflow.com/a/3943023/6869691
private pickTextColorBasedOnBgColor() {
const color = this.color.charAt(0) === "#" ? this.color.substring(1, 7) : this.color;
const r = parseInt(color.substring(0, 2), 16); // hexToR
const g = parseInt(color.substring(2, 4), 16); // hexToG
const b = parseInt(color.substring(4, 6), 16); // hexToB
return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? "black !important" : "white !important";
}
emitOnOrganizationClicked() {
this.onOrganizationClicked.emit();
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { OrganizationVaultComponent } from "./organization-vault.component";
const routes: Routes = [
{
path: "",
component: OrganizationVaultComponent,
data: { titleId: "vaults" },
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class OrganizationVaultRoutingModule {}

View File

@@ -1,22 +1,26 @@
<div class="container page-content">
<div class="row">
<div class="col-3">
<app-org-vault-groupings
[showFolders]="false"
[showFavorites]="false"
[showTrash]="true"
(onAllClicked)="clearGroupingFilters()"
(onCipherTypeClicked)="filterCipherType($event)"
(onCollectionClicked)="filterCollection($event.id)"
(onSearchTextChanged)="filterSearchText($event)"
(onTrashClicked)="filterDeleted()"
>
</app-org-vault-groupings>
<div class="groupings">
<div class="content">
<div class="inner-content">
<app-vault-filter
#vaultFilter
[showFolders]="false"
[showFavorites]="false"
[activeFilter]="activeFilter"
[showOrgFilter]="false"
(onFilterChange)="applyVaultFilter($event)"
(onSearchTextChanged)="filterSearchText($event)"
></app-vault-filter>
</div>
</div>
</div>
</div>
<div class="col-9">
<div class="page-header d-flex">
<h1>
{{ "vault" | i18n }}
{{ "vaultItems" | i18n }}
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
<ng-container *ngIf="actionSpinner.loading">
<i

View File

@@ -10,34 +10,36 @@ import {
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { VaultFilter } from "jslib-angular/modules/vault-filter/models/vault-filter.model";
import { ModalService } from "jslib-angular/services/modal.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { CipherType } from "jslib-common/enums/cipherType";
import { Organization } from "jslib-common/models/domain/organization";
import { CipherView } from "jslib-common/models/view/cipherView";
import { EntityEventsComponent } from "../manage/entity-events.component";
import { AddEditComponent } from "./add-edit.component";
import { AttachmentsComponent } from "./attachments.component";
import { CiphersComponent } from "./ciphers.component";
import { CollectionsComponent } from "./collections.component";
import { GroupingsComponent } from "./groupings.component";
import { EntityEventsComponent } from "../../../../organizations/manage/entity-events.component";
import { AddEditComponent } from "../../../../organizations/vault/add-edit.component";
import { AttachmentsComponent } from "../../../../organizations/vault/attachments.component";
import { CiphersComponent } from "../../../../organizations/vault/ciphers.component";
import { CollectionsComponent } from "../../../../organizations/vault/collections.component";
import { VaultFilterComponent } from "../../../vault-filter/vault-filter.component";
import { VaultService } from "../../vault.service";
const BroadcasterSubscriptionId = "OrgVaultComponent";
@Component({
selector: "app-org-vault",
templateUrl: "vault.component.html",
templateUrl: "organization-vault.component.html",
})
export class VaultComponent implements OnInit, OnDestroy {
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
export class OrganizationVaultComponent implements OnInit, OnDestroy {
@ViewChild("vaultFilter", { static: true }) vaultFilterComponent: VaultFilterComponent;
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
@ViewChild("attachments", { read: ViewContainerRef, static: true })
attachmentsModalRef: ViewContainerRef;
@@ -53,6 +55,7 @@ export class VaultComponent implements OnInit, OnDestroy {
type: CipherType = null;
deleted = false;
trashCleanupWarning: string = null;
activeFilter: VaultFilter = new VaultFilter();
constructor(
private route: ActivatedRoute,
@@ -66,7 +69,9 @@ export class VaultComponent implements OnInit, OnDestroy {
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
private platformUtilsService: PlatformUtilsService,
private cipherService: CipherService
private vaultService: VaultService,
private cipherService: CipherService,
private passwordRepromptService: PasswordRepromptService
) {}
ngOnInit() {
@@ -75,13 +80,13 @@ export class VaultComponent implements OnInit, OnDestroy {
? "trashCleanupWarningSelfHosted"
: "trashCleanupWarning"
);
this.route.parent.params.pipe(first()).subscribe(async (params) => {
this.route.parent.params.subscribe(async (params: any) => {
this.organization = await this.organizationService.get(params.organizationId);
this.groupingsComponent.organization = this.organization;
this.vaultFilterComponent.organization = this.organization;
this.ciphersComponent.organization = this.organization;
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search;
// this.ciphersComponent.searchText = this.vaultFilterComponent.search = qParams.search;
if (!this.organization.canViewAllCollections) {
await this.syncService.fullSync(false);
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
@@ -90,7 +95,11 @@ export class VaultComponent implements OnInit, OnDestroy {
case "syncCompleted":
if (message.successfully) {
await Promise.all([
this.groupingsComponent.load(),
this.vaultFilterComponent.reloadCollectionsAndFolders(
new VaultFilter({
selectedOrganizationId: this.organization.id,
} as Partial<VaultFilter>)
),
this.ciphersComponent.refresh(),
]);
this.changeDetectorRef.detectChanges();
@@ -100,27 +109,10 @@ export class VaultComponent implements OnInit, OnDestroy {
});
});
}
await this.groupingsComponent.load();
if (qParams == null) {
this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.reload();
} else {
if (qParams.deleted) {
this.groupingsComponent.selectedTrash = true;
await this.filterDeleted(true);
} else if (qParams.type) {
const t = parseInt(qParams.type, null);
this.groupingsComponent.selectedType = t;
await this.filterCipherType(t, true);
} else if (qParams.collectionId) {
this.groupingsComponent.selectedCollectionId = qParams.collectionId;
await this.filterCollection(qParams.collectionId, true);
} else {
this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.reload();
}
}
await this.vaultFilterComponent.reloadCollectionsAndFolders(
new VaultFilter({ selectedOrganizationId: this.organization.id } as Partial<VaultFilter>)
);
await this.ciphersComponent.reload();
if (qParams.viewEvents != null) {
const cipher = this.ciphersComponent.ciphers.filter((c) => c.id === qParams.viewEvents);
@@ -154,63 +146,47 @@ export class VaultComponent implements OnInit, OnDestroy {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async clearGroupingFilters() {
this.ciphersComponent.showAddNew = true;
this.ciphersComponent.deleted = false;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchVault");
await this.ciphersComponent.applyFilter();
this.clearFilters();
async applyVaultFilter(vaultFilter: VaultFilter) {
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
this.activeFilter = vaultFilter;
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
this.vaultFilterComponent.searchPlaceholder =
this.vaultService.calculateSearchBarLocalizationString(this.activeFilter);
this.go();
}
async filterCipherType(type: CipherType, load = false) {
this.ciphersComponent.showAddNew = true;
this.ciphersComponent.deleted = false;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchType");
const filter = (c: CipherView) => c.type === type;
if (load) {
await this.ciphersComponent.reload(filter);
} else {
await this.ciphersComponent.applyFilter(filter);
}
this.clearFilters();
this.type = type;
this.go();
}
async filterCollection(collectionId: string, load = false) {
this.ciphersComponent.showAddNew = true;
this.ciphersComponent.deleted = false;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchCollection");
const filter = (c: CipherView) => {
if (collectionId === "unassigned") {
return c.collectionIds == null || c.collectionIds.length === 0;
} else {
return c.collectionIds != null && c.collectionIds.indexOf(collectionId) > -1;
private buildFilter(): (cipher: CipherView) => boolean {
return (cipher) => {
let cipherPassesFilter = true;
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
cipherPassesFilter = cipher.favorite;
}
if (this.activeFilter.status === "trash" && cipherPassesFilter) {
cipherPassesFilter = cipher.isDeleted;
}
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
}
if (
this.activeFilter.selectedFolderId != null &&
this.activeFilter.selectedFolderId != "none" &&
cipherPassesFilter
) {
cipherPassesFilter = cipher.folderId === this.activeFilter.selectedFolderId;
}
if (this.activeFilter.selectedCollectionId != null && cipherPassesFilter) {
cipherPassesFilter =
cipher.collectionIds != null &&
cipher.collectionIds.indexOf(this.activeFilter.selectedCollectionId) > -1;
}
if (this.activeFilter.selectedOrganizationId != null && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === this.activeFilter.selectedOrganizationId;
}
if (this.activeFilter.myVaultOnly && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === null;
}
return cipherPassesFilter;
};
if (load) {
await this.ciphersComponent.reload(filter);
} else {
await this.ciphersComponent.applyFilter(filter);
}
this.clearFilters();
this.collectionId = collectionId;
this.go();
}
async filterDeleted(load = false) {
this.ciphersComponent.showAddNew = false;
this.ciphersComponent.deleted = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchTrash");
if (load) {
await this.ciphersComponent.reload(null, true);
} else {
await this.ciphersComponent.applyFilter(null);
}
this.clearFilters();
this.deleted = true;
this.go();
}
filterSearchText(searchText: string) {
@@ -252,7 +228,9 @@ export class VaultComponent implements OnInit, OnDestroy {
(comp) => {
if (this.organization.canEditAnyCollection) {
comp.collectionIds = cipher.collectionIds;
comp.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
comp.collections = this.vaultFilterComponent.collections.fullList.filter(
(c) => !c.readOnly
);
}
comp.organization = this.organization;
comp.cipherId = cipher.id;
@@ -269,7 +247,9 @@ export class VaultComponent implements OnInit, OnDestroy {
component.organizationId = this.organization.id;
component.type = this.type;
if (this.organization.canEditAnyCollection) {
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
component.collections = this.vaultFilterComponent.collections.fullList.filter(
(c) => !c.readOnly
);
}
if (this.collectionId != null) {
component.collectionIds = [this.collectionId];
@@ -281,6 +261,14 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async editCipherId(cipherId: string) {
const cipher = await this.cipherService.get(cipherId);
if (cipher != null && cipher.reprompt != 0) {
if (!(await this.passwordRepromptService.showPasswordPrompt())) {
this.go({ cipherId: null });
return;
}
}
const [modal, childComponent] = await this.modalService.openViewRef(
AddEditComponent,
this.cipherAddEditModalRef,
@@ -303,8 +291,7 @@ export class VaultComponent implements OnInit, OnDestroy {
);
modal.onClosedPromise().then(() => {
this.route.params;
this.router.navigate([], { queryParams: { cipherId: null }, queryParamsHandling: "merge" });
this.go({ cipherId: null });
});
return childComponent;
@@ -315,7 +302,9 @@ export class VaultComponent implements OnInit, OnDestroy {
component.cloneMode = true;
component.organizationId = this.organization.id;
if (this.organization.canEditAnyCollection) {
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
component.collections = this.vaultFilterComponent.collections.fullList.filter(
(c) => !c.readOnly
);
}
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
// in the add-edit componenet

View File

@@ -0,0 +1,13 @@
import { NgModule } from "@angular/core";
import { VaultModule } from "../../vault.module";
import { OrganizationVaultRoutingModule } from "./organization-vault-routing.module";
import { OrganizationVaultComponent } from "./organization-vault.component";
@NgModule({
imports: [VaultModule, OrganizationVaultRoutingModule],
declarations: [OrganizationVaultComponent],
exports: [OrganizationVaultComponent],
})
export class OrganizationVaultModule {}

View File

@@ -0,0 +1,19 @@
import { NgModule } from "@angular/core";
import { LooseComponentsModule } from "../loose-components.module";
import { SharedModule } from "../shared.module";
import { VaultFilterModule } from "../vault-filter/vault-filter.module";
import { VaultService } from "./vault.service";
@NgModule({
imports: [SharedModule, VaultFilterModule, LooseComponentsModule],
exports: [SharedModule, VaultFilterModule, LooseComponentsModule],
providers: [
{
provide: VaultService,
useClass: VaultService,
},
],
})
export class VaultModule {}

View File

@@ -0,0 +1,29 @@
import { VaultFilter } from "jslib-angular/modules/vault-filter/models/vault-filter.model";
export class VaultService {
calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string {
if (vaultFilter.status === "favorites") {
return "searchFavorites";
}
if (vaultFilter.status === "trash") {
return "searchTrash";
}
if (vaultFilter.cipherType != null) {
return "searchType";
}
if (vaultFilter.selectedFolderId != null && vaultFilter.selectedFolderId != "none") {
return "searchFolder";
}
if (vaultFilter.selectedCollectionId != null) {
return "searchCollection";
}
if (vaultFilter.selectedOrganizationId != null) {
return "searchOrganization";
}
if (vaultFilter.myVaultOnly) {
return "searchMyVault";
}
return "searchVault";
}
}

View File

@@ -5,14 +5,15 @@ import { I18nService } from "jslib-common/abstractions/i18n.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { Permissions } from "jslib-common/enums/permissions";
@Injectable()
export class OrganizationGuardService implements CanActivate {
export class PermissionsGuard implements CanActivate {
constructor(
private router: Router,
private organizationService: OrganizationService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private organizationService: OrganizationService,
private syncService: SyncService
) {}
@@ -26,6 +27,7 @@ export class OrganizationGuardService implements CanActivate {
if (org == null) {
return this.router.createUrlTree(["/"]);
}
if (!org.isOwner && !org.enabled) {
this.platformUtilsService.showToast(
"error",
@@ -35,6 +37,12 @@ export class OrganizationGuardService implements CanActivate {
return this.router.createUrlTree(["/"]);
}
const permissions = route.data == null ? [] : (route.data.permissions as Permissions[]);
if (permissions != null && !org.hasAnyPermission(permissions)) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("accessDenied"));
return this.router.createUrlTree(["/"]);
}
return true;
}
}

View File

@@ -0,0 +1,37 @@
<div class="org-nav" *ngIf="organization">
<div class="container d-flex">
<div class="d-flex flex-column">
<app-organization-switcher
class="my-auto pl-1"
[activeOrganization]="organization"
></app-organization-switcher>
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" routerLink="vault" routerLinkActive="active">
<i class="bwi bwi-lock" aria-hidden="true"></i>
{{ "vault" | i18n }}
</a>
</li>
<li class="nav-item" *ngIf="showManageTab">
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
<i class="bwi bwi-sliders" aria-hidden="true"></i>
{{ "manage" | i18n }}
</a>
</li>
<li class="nav-item" *ngIf="showToolsTab">
<a class="nav-link" [routerLink]="toolsRoute" routerLinkActive="active">
<i class="bwi bwi-wrench" aria-hidden="true"></i>
{{ "tools" | i18n }}
</a>
</li>
<li class="nav-item" *ngIf="showSettingsTab">
<a class="nav-link" routerLink="settings" routerLinkActive="active">
<i class="bwi bwi-cogs" aria-hidden="true"></i>
{{ "settings" | i18n }}
</a>
</li>
</ul>
</div>
</div>
</div>
<router-outlet></router-outlet>

View File

@@ -5,6 +5,8 @@ import { BroadcasterService } from "jslib-common/abstractions/broadcaster.servic
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { Organization } from "jslib-common/models/domain/organization";
import { NavigationPermissionsService } from "../services/navigation-permissions.service";
const BroadcasterSubscriptionId = "OrganizationLayoutComponent";
@Component({
@@ -25,7 +27,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
ngOnInit() {
document.body.classList.remove("layout_frontend");
this.route.params.subscribe(async (params) => {
this.route.params.subscribe(async (params: any) => {
this.organizationId = params.organizationId;
await this.load();
});
@@ -48,23 +50,16 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
this.organization = await this.organizationService.get(this.organizationId);
}
get showMenuBar() {
return this.showManageTab || this.showToolsTab || this.organization.isOwner;
}
get showManageTab(): boolean {
return (
this.organization.canManageUsers ||
this.organization.canViewAllCollections ||
this.organization.canViewAssignedCollections ||
this.organization.canManageGroups ||
this.organization.canManagePolicies ||
this.organization.canAccessEventLogs
);
return NavigationPermissionsService.canAccessManage(this.organization);
}
get showToolsTab(): boolean {
return this.organization.canAccessImportExport || this.organization.canAccessReports;
return NavigationPermissionsService.canAccessTools(this.organization);
}
get showSettingsTab(): boolean {
return NavigationPermissionsService.canAccessSettings(this.organization);
}
get toolsRoute(): string {

View File

@@ -0,0 +1,222 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { Permissions } from "jslib-common/enums/permissions";
import { PermissionsGuard } from "./guards/permissions.guard";
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
import { CollectionsComponent } from "./manage/collections.component";
import { EventsComponent } from "./manage/events.component";
import { GroupsComponent } from "./manage/groups.component";
import { ManageComponent } from "./manage/manage.component";
import { PeopleComponent } from "./manage/people.component";
import { PoliciesComponent } from "./manage/policies.component";
import { NavigationPermissionsService } from "./services/navigation-permissions.service";
import { AccountComponent } from "./settings/account.component";
import { OrganizationBillingComponent } from "./settings/organization-billing.component";
import { OrganizationSubscriptionComponent } from "./settings/organization-subscription.component";
import { SettingsComponent } from "./settings/settings.component";
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
import { ExportComponent } from "./tools/export.component";
import { ExposedPasswordsReportComponent } from "./tools/exposed-passwords-report.component";
import { ImportComponent } from "./tools/import.component";
import { InactiveTwoFactorReportComponent } from "./tools/inactive-two-factor-report.component";
import { ReusedPasswordsReportComponent } from "./tools/reused-passwords-report.component";
import { ToolsComponent } from "./tools/tools.component";
import { UnsecuredWebsitesReportComponent } from "./tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./tools/weak-passwords-report.component";
const routes: Routes = [
{
path: ":organizationId",
component: OrganizationLayoutComponent,
canActivate: [AuthGuard, PermissionsGuard],
data: {
permissions: NavigationPermissionsService.getPermissions("admin"),
},
children: [
{ path: "", pathMatch: "full", redirectTo: "vault" },
{
path: "vault",
loadChildren: async () =>
(await import("../modules/vault/modules/organization-vault/organization-vault.module"))
.OrganizationVaultModule,
},
{
path: "tools",
component: ToolsComponent,
canActivate: [PermissionsGuard],
data: { permissions: NavigationPermissionsService.getPermissions("tools") },
children: [
{
path: "",
pathMatch: "full",
redirectTo: "import",
},
{
path: "import",
component: ImportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "importData",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "export",
component: ExportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "exportVault",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "exposed-passwords-report",
component: ExposedPasswordsReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "exposedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "inactive-two-factor-report",
component: InactiveTwoFactorReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "inactive2faReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "reused-passwords-report",
component: ReusedPasswordsReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "reusedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "unsecured-websites-report",
component: UnsecuredWebsitesReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "unsecuredWebsitesReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "weak-passwords-report",
component: WeakPasswordsReportComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "weakPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
],
},
{
path: "manage",
component: ManageComponent,
canActivate: [PermissionsGuard],
data: {
permissions: NavigationPermissionsService.getPermissions("manage"),
},
children: [
{
path: "",
pathMatch: "full",
redirectTo: "people",
},
{
path: "collections",
component: CollectionsComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "collections",
permissions: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
],
},
},
{
path: "events",
component: EventsComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "eventLogs",
permissions: [Permissions.AccessEventLogs],
},
},
{
path: "groups",
component: GroupsComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "groups",
permissions: [Permissions.ManageGroups],
},
},
{
path: "people",
component: PeopleComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "people",
permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword],
},
},
{
path: "policies",
component: PoliciesComponent,
canActivate: [PermissionsGuard],
data: {
titleId: "policies",
permissions: [Permissions.ManagePolicies],
},
},
],
},
{
path: "settings",
component: SettingsComponent,
canActivate: [PermissionsGuard],
data: { permissions: NavigationPermissionsService.getPermissions("settings") },
children: [
{ path: "", pathMatch: "full", redirectTo: "account" },
{ path: "account", component: AccountComponent, data: { titleId: "myOrganization" } },
{
path: "two-factor",
component: TwoFactorSetupComponent,
data: { titleId: "twoStepLogin" },
},
{
path: "billing",
component: OrganizationBillingComponent,
canActivate: [PermissionsGuard],
data: { titleId: "billing", permissions: [Permissions.ManageBilling] },
},
{
path: "subscription",
component: OrganizationSubscriptionComponent,
data: { titleId: "subscription" },
},
],
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class OrganizationsRoutingModule {}

View File

@@ -0,0 +1,48 @@
import { Permissions } from "jslib-common/enums/permissions";
import { Organization } from "jslib-common/models/domain/organization";
const permissions = {
manage: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
Permissions.AccessEventLogs,
Permissions.ManageGroups,
Permissions.ManageUsers,
Permissions.ManagePolicies,
],
tools: [Permissions.AccessImportExport, Permissions.AccessReports],
settings: [Permissions.ManageOrganization],
};
export class NavigationPermissionsService {
static getPermissions(route: keyof typeof permissions | "admin") {
if (route === "admin") {
return Object.values(permissions).reduce((previous, current) => previous.concat(current), []);
}
return permissions[route];
}
static canAccessAdmin(organization: Organization): boolean {
return (
this.canAccessTools(organization) ||
this.canAccessSettings(organization) ||
this.canAccessManage(organization)
);
}
static canAccessTools(organization: Organization): boolean {
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("tools"));
}
static canAccessSettings(organization: Organization): boolean {
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("settings"));
}
static canAccessManage(organization: Organization): boolean {
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("manage"));
}
}

View File

@@ -37,7 +37,7 @@
type="text"
name="BillingEmail"
[(ngModel)]="org.billingEmail"
[disabled]="selfHosted"
[disabled]="selfHosted || !canManageBilling"
/>
</div>
<div class="form-group">
@@ -48,7 +48,7 @@
type="text"
name="BusinessName"
[(ngModel)]="org.businessName"
[disabled]="selfHosted"
[disabled]="selfHosted || !canManageBilling"
/>
</div>
<div class="form-group">

View File

@@ -6,6 +6,7 @@ import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { OrganizationKeysRequest } from "jslib-common/models/request/organizationKeysRequest";
@@ -34,6 +35,7 @@ export class AccountComponent {
@ViewChild(TaxInfoComponent) taxInfo: TaxInfoComponent;
selfHosted = false;
canManageBilling = true;
loading = true;
canUseApi = false;
org: OrganizationResponse;
@@ -51,13 +53,18 @@ export class AccountComponent {
private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService,
private logService: LogService,
private router: Router
private router: Router,
private organizationService: OrganizationService
) {}
async ngOnInit() {
this.selfHosted = this.platformUtilsService.isSelfHost();
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
this.canManageBilling = (
await this.organizationService.get(this.organizationId)
).canManageBilling;
try {
this.org = await this.apiService.getOrganization(this.organizationId);
this.canUseApi = this.org.useApi;

View File

@@ -0,0 +1,20 @@
<svg width="216" height="231" viewBox="0 0 216 231" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M133.356 85.6618C133.136 85.43 132.871 85.2457 132.577 85.1198C132.283 84.9939 131.968 84.93 131.648 84.9318H87.8482C87.5289 84.93 87.2113 84.9939 86.9175 85.1198C86.6237 85.2457 86.359 85.43 86.14 85.6618C85.9083 85.8808 85.7239 86.1473 85.598 86.4411C85.4721 86.7349 85.4082 87.0506 85.41 87.37V116.57C85.4192 118.793 85.8499 120.994 86.6802 123.056C87.4705 125.091 88.5326 127.011 89.8375 128.761C91.1789 130.515 92.6808 132.137 94.3233 133.612C95.8472 135.01 97.4532 136.318 99.1304 137.528C100.59 138.565 102.123 139.547 103.729 140.474C105.335 141.401 106.469 142.027 107.131 142.354C107.799 142.682 108.339 142.941 108.741 143.113C109.055 143.264 109.4 143.339 109.748 143.332C110.091 143.337 110.431 143.257 110.737 143.102C111.146 142.923 111.679 142.671 112.354 142.343C113.03 142.014 114.179 141.386 115.756 140.463C117.333 139.539 118.884 138.554 120.355 137.517C122.034 136.306 123.642 134.999 125.169 133.601C126.814 132.128 128.316 130.504 129.655 128.75C130.958 126.998 132.021 125.08 132.813 123.045C133.645 120.983 134.075 118.782 134.083 116.559V87.3591C134.085 87.0415 134.021 86.7276 133.895 86.4356C133.769 86.1436 133.586 85.8808 133.356 85.6618ZM127.71 116.836C127.71 127.421 109.748 136.514 109.748 136.514V91.1879H127.71V116.836Z" fill="var(--color-secondary-700)"/>
<path d="M24.6216 122.3C24.7144 123.4 25.6819 124.217 26.7825 124.125C27.8832 124.032 28.7002 123.064 28.6074 121.964L24.6216 122.3ZM151.501 45.7445C152.57 45.4679 153.213 44.3768 152.936 43.3074L148.429 25.8809C148.152 24.8115 147.061 24.1688 145.992 24.4454C144.922 24.722 144.28 25.8131 144.556 26.8825L148.563 42.3728L133.073 46.3793C132.003 46.6559 131.361 47.747 131.637 48.8164C131.914 49.8858 133.005 50.5285 134.074 50.2519L151.501 45.7445ZM28.6074 121.964C26.6788 99.0874 34.4658 75.5543 51.9661 58.054L49.1377 55.2256C30.7695 73.5938 22.5982 98.2999 24.6216 122.3L28.6074 121.964ZM51.9661 58.054C78.5404 31.4797 119.036 27.3026 149.985 45.5315L152.015 42.0849C119.534 22.9534 77.0327 27.3306 49.1377 55.2256L51.9661 58.054Z" fill="var(--color-secondary-700)"/>
<path d="M67.4747 185.062C66.4089 185.352 65.7801 186.451 66.0701 187.517L70.797 204.885C71.0871 205.951 72.1862 206.58 73.252 206.29C74.3179 205.999 74.9467 204.9 74.6567 203.834L70.455 188.396L85.8934 184.194C86.9592 183.904 87.5881 182.805 87.298 181.739C87.008 180.674 85.9088 180.045 84.843 180.335L67.4747 185.062ZM192.478 100.283C192.286 99.1952 191.248 98.4697 190.16 98.6625C189.072 98.8552 188.347 99.8931 188.54 100.981L192.478 100.283ZM166.138 172.225C139.813 198.55 99.8271 202.897 68.9932 185.255L67.0068 188.727C99.3669 207.242 141.333 202.687 168.966 175.054L166.138 172.225ZM188.54 100.981C192.985 126.07 185.516 152.847 166.138 172.225L168.966 175.054C189.307 154.713 197.142 126.603 192.478 100.283L188.54 100.981Z" fill="var(--color-secondary-700)"/>
<path d="M37.726 108.132C39.283 92.1931 46.1655 76.6765 58.3734 64.4686C75.2893 47.5527 98.5583 40.8618 120.5 44.396" stroke="var(--color-secondary-700)" stroke-width="2" stroke-linecap="round"/>
<path d="M180.595 119.132C179.653 136.119 172.693 152.834 159.717 165.811C144.44 181.088 123.982 188.025 104 186.623" stroke="var(--color-secondary-700)" stroke-width="2" stroke-linecap="round"/>
<rect x="153.5" y="56.1317" width="49" height="34" rx="2.5" stroke="var(--color-secondary-700)" stroke-width="3"/>
<path d="M153.5 63.6317V63.6317C153.5 66.9454 156.186 69.6317 159.5 69.6317H172.509M202.5 63.6317V63.6317C202.5 66.9454 199.814 69.6317 196.5 69.6317H183.491" stroke="var(--color-secondary-700)" stroke-width="3"/>
<mask id="path-8-inside-1_1000_26057" fill="white">
<rect x="172" y="65.6317" width="12" height="9" rx="1.25"/>
</mask>
<rect x="172" y="65.6317" width="12" height="9" rx="1.25" stroke="var(--color-secondary-700)" stroke-width="6" mask="url(#path-8-inside-1_1000_26057)"/>
<path d="M187 54.6317C187 54.6317 187 53.6317 187 52.6317C187 51.6317 186.053 50.6317 185.105 50.6317C184.158 50.6317 171.842 50.6317 170.895 50.6317C169.947 50.6317 169 51.6317 169 52.6317C169 53.6317 169 54.6317 169 54.6317" stroke="var(--color-secondary-700)" stroke-width="3"/>
<circle cx="48" cy="141" r="10.5" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<path d="M64.9935 168.5H64.9126H64.8318H64.7512H64.6708H64.5905H64.5104H64.4304H64.3506H64.2709H64.1914H64.1121H64.0329H63.9538H63.8749H63.7962H63.7176H63.6392H63.5609H63.4828H63.4048H63.3269H63.2492H63.1717H63.0943H63.017H62.9399H62.8629H62.7861H62.7094H62.6329H62.5565H62.4802H62.4041H62.3281H62.2523H62.1766H62.101H62.0256H61.9503H61.8751H61.8001H61.7252H61.6504H61.5758H61.5013H61.4269H61.3527H61.2786H61.2046H61.1308H61.0571H60.9835H60.91H60.8367H60.7635H60.6904H60.6175H60.5446H60.4719H60.3993H60.3269H60.2545H60.1823H60.1102H60.0382H59.9664H59.8946H59.823H59.7515H59.6801H59.6088H59.5376H59.4666H59.3956H59.3248H59.2541H59.1835H59.113H59.0426H58.9724H58.9022H58.8322H58.7622H58.6924H58.6226H58.553H58.4835H58.4141H58.3448H58.2756H58.2065H58.1375H58.0686H57.9998H57.9311H57.8625H57.794H57.7256H57.6572H57.589H57.5209H57.4529H57.385H57.3172H57.2494H57.1818H57.1142H57.0468H56.9794H56.9121H56.8449H56.7779H56.7108H56.6439H56.5771H56.5103H56.4437H56.3771H56.3106H56.2442H56.1779H56.1117H56.0455H55.9794H55.9134H55.8475H55.7817H55.7159H55.6502H55.5846H55.5191H55.4537H55.3883H55.323H55.2578H55.1926H55.1275H55.0625H54.9976H54.9328H54.868H54.8032H54.7386H54.674H54.6095H54.5451H54.4807H54.4164H54.3521H54.2879H54.2238H54.1598H54.0958H54.0318H53.968H53.9042H53.8404H53.7767H53.7131H53.6495H53.586H53.5226H53.4592H53.3958H53.3325H53.2693H53.2061H53.143H53.0799H53.0169H52.9539H52.891H52.8281H52.7653H52.7025H52.6398H52.5771H52.5145H52.4519H52.3894H52.3269H52.2645H52.202H52.1397H52.0774H52.0151H51.9528H51.8907H51.8285H51.7664H51.7043H51.6423H51.5803H51.5183H51.4564H51.3945H51.3326H51.2708H51.209H51.1472H51.0855H51.0238H50.9621H50.9005H50.8389H50.7773H50.7157H50.6542H50.5927H50.5312H50.4698H50.4084H50.347H50.2856H50.2243H50.1629H50.1016H50.0404H49.9791H49.9178H49.8566H49.7954H49.7342H49.6731H49.6119H49.5508H49.4896H49.4285H49.3674H49.3064H49.2453H49.1842H49.1232H49.0622H49.0011H48.9401H48.8791H48.8181H48.7571H48.6962H48.6352H48.5742H48.5133H48.4523H48.3913H48.3304H48.2694H48.2085H48.1475H48.0866H48.0257H47.9647H47.9038H47.8428H47.7819H47.7209H47.6599H47.599H47.538H47.477H47.416H47.3551H47.2941H47.2331H47.172H47.111H47.05H46.9889H46.9279H46.8668H46.8057H46.7446H46.6835H46.6224H46.5612H46.5001H46.4389H46.3777H46.3165H46.2553H46.194H46.1328H46.0715H46.0102H45.9489H45.8875H45.8261H45.7647H45.7033H45.6418H45.5804H45.5189H45.4573H45.3958H45.3342H45.2726H45.2109H45.1493H45.0876H45.0258H44.9641H44.9023H44.8404H44.7786H44.7166H44.6547H44.5927H44.5307H44.4687H44.4066H44.3445H44.2823H44.2201H44.1578H44.0956H44.0332H43.9709H43.9084H43.846H43.7835H43.7209H43.6583H43.5957H43.533H43.4703H43.4075H43.3447H43.2818H43.2189H43.1559H43.0929H43.0298H42.9666H42.9034H42.8402H42.7769H42.7135H42.6501H42.5867H42.5231H42.4596H42.3959H42.3322H42.2685H42.2046H42.1408H42.0768H42.0128H41.9487H41.8846H41.8204H41.7562H41.6918H41.6274H41.563H41.4985H41.4339H41.3692H41.3045H41.2397H41.1748H41.1098H41.0448H40.9797H40.9146H40.8493H40.784H40.7186H40.6532H40.5876H40.522H40.4563H40.3905H40.3247H40.2588H40.1928H40.1267H40.0605H39.9942H39.9279H39.8615H39.795H39.7284H39.6617H39.5949H39.5281H39.4611H39.3941H39.327H39.2598H39.1925H39.1251H39.0576H38.9901H38.9224H38.8547H38.7868H38.7189H38.6508H38.5827H38.5145H38.4461H38.3777H38.3092H38.2406H38.1719H38.103H38.0341H37.9651H37.896H37.8267H37.7574H37.688H37.6184H37.5488H37.479H37.4092H37.3392H37.2691H37.199H37.1287H37.0583H36.9878H36.9171H36.8464H36.7755H36.7046H36.6335H36.5623H36.491H36.4196H36.3481H36.2764H36.2046H36.1328H36.0607H35.9886H35.9164H35.844H35.7715H35.6989H35.6262H35.5533H35.4803H35.4072H35.334H35.2606H35.1872H35.1135H35.0398H34.9659H34.8919H34.8178H34.7436H34.6692H34.5947H34.52H34.4452H34.3703H34.2952H34.2201H34.1447H34.0693H33.9937H33.9179H33.8421H33.7661H33.6899H33.6136H33.5372H33.4606H33.3839H33.3071H33.2301H33.1529H33.0756H32.9982H32.9206H32.8429H32.765H32.687H32.6088H32.5305H32.452H32.3734H32.2946H32.2157H32.1367H32.0574H31.978H31.8985H31.8188H31.739H31.659H31.5788H31.4985H31.418H31.3374H31.2566H31.1757H31.0946H31.0133H30.9318H30.8503H30.7685H30.6866H30.6045H30.5222H30.4398H30.3572H30.2745H30.1915C30.0489 168.5 29.9693 168.466 29.9164 168.433C29.8553 168.394 29.7779 168.322 29.702 168.192C29.5361 167.906 29.4608 167.48 29.5197 167.111C30.9235 158.322 38.6474 151.574 47.9925 151.574C57.3375 151.574 65.0615 158.322 66.4652 167.111C66.5751 167.799 66.4037 168.054 66.3124 168.149C66.1888 168.278 65.8364 168.5 64.9935 168.5Z" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<circle cx="20" cy="141" r="10.5" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<path d="M36.9935 168.5H36.9126H36.8318H36.7512H36.6708H36.5905H36.5104H36.4304H36.3506H36.2709H36.1914H36.1121H36.0329H35.9538H35.8749H35.7962H35.7176H35.6392H35.5609H35.4828H35.4048H35.3269H35.2492H35.1717H35.0943H35.017H34.9399H34.8629H34.7861H34.7094H34.6329H34.5565H34.4802H34.4041H34.3281H34.2523H34.1766H34.101H34.0256H33.9503H33.8751H33.8001H33.7252H33.6504H33.5758H33.5013H33.4269H33.3527H33.2786H33.2046H33.1308H33.0571H32.9835H32.91H32.8367H32.7635H32.6904H32.6175H32.5446H32.4719H32.3993H32.3269H32.2545H32.1823H32.1102H32.0382H31.9664H31.8946H31.823H31.7515H31.6801H31.6088H31.5376H31.4666H31.3956H31.3248H31.2541H31.1835H31.113H31.0426H30.9724H30.9022H30.8322H30.7622H30.6924H30.6226H30.553H30.4835H30.4141H30.3448H30.2756H30.2065H30.1375H30.0686H29.9998H29.9311H29.8625H29.794H29.7256H29.6572H29.589H29.5209H29.4529H29.385H29.3172H29.2494H29.1818H29.1142H29.0468H28.9794H28.9121H28.8449H28.7779H28.7108H28.6439H28.5771H28.5103H28.4437H28.3771H28.3106H28.2442H28.1779H28.1117H28.0455H27.9794H27.9134H27.8475H27.7817H27.7159H27.6502H27.5846H27.5191H27.4537H27.3883H27.323H27.2578H27.1926H27.1275H27.0625H26.9976H26.9328H26.868H26.8032H26.7386H26.674H26.6095H26.5451H26.4807H26.4164H26.3521H26.2879H26.2238H26.1598H26.0958H26.0318H25.968H25.9042H25.8404H25.7767H25.7131H25.6495H25.586H25.5226H25.4592H25.3958H25.3325H25.2693H25.2061H25.143H25.0799H25.0169H24.9539H24.891H24.8281H24.7653H24.7025H24.6398H24.5771H24.5145H24.4519H24.3894H24.3269H24.2645H24.202H24.1397H24.0774H24.0151H23.9528H23.8907H23.8285H23.7664H23.7043H23.6423H23.5803H23.5183H23.4564H23.3945H23.3326H23.2708H23.209H23.1472H23.0855H23.0238H22.9621H22.9005H22.8389H22.7773H22.7157H22.6542H22.5927H22.5312H22.4698H22.4084H22.347H22.2856H22.2243H22.1629H22.1016H22.0404H21.9791H21.9178H21.8566H21.7954H21.7342H21.6731H21.6119H21.5508H21.4896H21.4285H21.3674H21.3064H21.2453H21.1842H21.1232H21.0622H21.0011H20.9401H20.8791H20.8181H20.7571H20.6962H20.6352H20.5742H20.5133H20.4523H20.3913H20.3304H20.2694H20.2085H20.1475H20.0866H20.0257H19.9647H19.9038H19.8428H19.7819H19.7209H19.6599H19.599H19.538H19.477H19.416H19.3551H19.2941H19.2331H19.172H19.111H19.05H18.9889H18.9279H18.8668H18.8057H18.7446H18.6835H18.6224H18.5612H18.5001H18.4389H18.3777H18.3165H18.2553H18.194H18.1328H18.0715H18.0102H17.9489H17.8875H17.8261H17.7647H17.7033H17.6418H17.5804H17.5189H17.4573H17.3958H17.3342H17.2726H17.2109H17.1493H17.0876H17.0258H16.9641H16.9023H16.8404H16.7786H16.7166H16.6547H16.5927H16.5307H16.4687H16.4066H16.3445H16.2823H16.2201H16.1578H16.0956H16.0332H15.9709H15.9084H15.846H15.7835H15.7209H15.6583H15.5957H15.533H15.4703H15.4075H15.3447H15.2818H15.2189H15.1559H15.0929H15.0298H14.9666H14.9034H14.8402H14.7769H14.7135H14.6501H14.5867H14.5231H14.4596H14.3959H14.3322H14.2685H14.2046H14.1408H14.0768H14.0128H13.9487H13.8846H13.8204H13.7562H13.6918H13.6274H13.563H13.4985H13.4339H13.3692H13.3045H13.2397H13.1748H13.1098H13.0448H12.9797H12.9146H12.8493H12.784H12.7186H12.6532H12.5876H12.522H12.4563H12.3905H12.3247H12.2588H12.1928H12.1267H12.0605H11.9942H11.9279H11.8615H11.795H11.7284H11.6617H11.5949H11.5281H11.4611H11.3941H11.327H11.2598H11.1925H11.1251H11.0576H10.9901H10.9224H10.8547H10.7868H10.7189H10.6508H10.5827H10.5145H10.4461H10.3777H10.3092H10.2406H10.1719H10.103H10.0341H9.9651H9.89597H9.82674H9.75741H9.68798H9.61843H9.54879H9.47904H9.40918H9.33921H9.26914H9.19896H9.12867H9.05826H8.98775H8.91713H8.8464H8.77555H8.70459H8.63351H8.56232H8.49102H8.4196H8.34807H8.27641H8.20464H8.13276H8.06075H7.98862H7.91638H7.84401H7.77152H7.69891H7.62617H7.55332H7.48034H7.40723H7.334H7.26064H7.18716H7.11355H7.03981H6.96594H6.89195H6.81782H6.74356H6.66918H6.59466H6.52H6.44522H6.3703H6.29525H6.22006H6.14474H6.06928H5.99368H5.91795H5.84208H5.76607H5.68992H5.61363H5.5372H5.46062H5.38391H5.30705H5.23005H5.15291H5.07562H4.99819H4.92061H4.84288H4.76501H4.68699H4.60882H4.5305H4.45203H4.37342H4.29465H4.21573H4.13665H4.05743H3.97805H3.89851H3.81882H3.73898H3.65898H3.57882H3.49851H3.41804H3.33741H3.25662H3.17566H3.09455H3.01328H2.93185H2.85025H2.76849H2.68657H2.60448H2.52223H2.43981H2.35722H2.27447H2.19155C2.04893 168.5 1.96927 168.466 1.91645 168.433C1.85532 168.394 1.77792 168.322 1.702 168.192C1.53613 167.906 1.46078 167.48 1.51975 167.111C2.92347 158.322 10.6474 151.574 19.9925 151.574C29.3375 151.574 37.0615 158.322 38.4652 167.111C38.5751 167.799 38.4037 168.054 38.3124 168.149C38.1888 168.278 37.8364 168.5 36.9935 168.5Z" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<circle cx="34" cy="154" r="10.5" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
<path d="M50.9935 181.5H50.9126H50.8318H50.7512H50.6708H50.5905H50.5104H50.4304H50.3506H50.2709H50.1914H50.1121H50.0329H49.9538H49.8749H49.7962H49.7176H49.6392H49.5609H49.4828H49.4048H49.3269H49.2492H49.1717H49.0943H49.017H48.9399H48.8629H48.7861H48.7094H48.6329H48.5565H48.4802H48.4041H48.3281H48.2523H48.1766H48.101H48.0256H47.9503H47.8751H47.8001H47.7252H47.6504H47.5758H47.5013H47.4269H47.3527H47.2786H47.2046H47.1308H47.0571H46.9835H46.91H46.8367H46.7635H46.6904H46.6175H46.5446H46.4719H46.3993H46.3269H46.2545H46.1823H46.1102H46.0382H45.9664H45.8946H45.823H45.7515H45.6801H45.6088H45.5376H45.4666H45.3956H45.3248H45.2541H45.1835H45.113H45.0426H44.9724H44.9022H44.8322H44.7622H44.6924H44.6226H44.553H44.4835H44.4141H44.3448H44.2756H44.2065H44.1375H44.0686H43.9998H43.9311H43.8625H43.794H43.7256H43.6572H43.589H43.5209H43.4529H43.385H43.3172H43.2494H43.1818H43.1142H43.0468H42.9794H42.9121H42.8449H42.7779H42.7108H42.6439H42.5771H42.5103H42.4437H42.3771H42.3106H42.2442H42.1779H42.1117H42.0455H41.9794H41.9134H41.8475H41.7817H41.7159H41.6502H41.5846H41.5191H41.4537H41.3883H41.323H41.2578H41.1926H41.1275H41.0625H40.9976H40.9328H40.868H40.8032H40.7386H40.674H40.6095H40.5451H40.4807H40.4164H40.3521H40.2879H40.2238H40.1598H40.0958H40.0318H39.968H39.9042H39.8404H39.7767H39.7131H39.6495H39.586H39.5226H39.4592H39.3958H39.3325H39.2693H39.2061H39.143H39.0799H39.0169H38.9539H38.891H38.8281H38.7653H38.7025H38.6398H38.5771H38.5145H38.4519H38.3894H38.3269H38.2645H38.202H38.1397H38.0774H38.0151H37.9528H37.8907H37.8285H37.7664H37.7043H37.6423H37.5803H37.5183H37.4564H37.3945H37.3326H37.2708H37.209H37.1472H37.0855H37.0238H36.9621H36.9005H36.8389H36.7773H36.7157H36.6542H36.5927H36.5312H36.4698H36.4084H36.347H36.2856H36.2243H36.1629H36.1016H36.0404H35.9791H35.9178H35.8566H35.7954H35.7342H35.6731H35.6119H35.5508H35.4896H35.4285H35.3674H35.3064H35.2453H35.1842H35.1232H35.0622H35.0011H34.9401H34.8791H34.8181H34.7571H34.6962H34.6352H34.5742H34.5133H34.4523H34.3913H34.3304H34.2694H34.2085H34.1475H34.0866H34.0257H33.9647H33.9038H33.8428H33.7819H33.7209H33.6599H33.599H33.538H33.477H33.416H33.3551H33.2941H33.2331H33.172H33.111H33.05H32.9889H32.9279H32.8668H32.8057H32.7446H32.6835H32.6224H32.5612H32.5001H32.4389H32.3777H32.3165H32.2553H32.194H32.1328H32.0715H32.0102H31.9489H31.8875H31.8261H31.7647H31.7033H31.6418H31.5804H31.5189H31.4573H31.3958H31.3342H31.2726H31.2109H31.1493H31.0876H31.0258H30.9641H30.9023H30.8404H30.7786H30.7166H30.6547H30.5927H30.5307H30.4687H30.4066H30.3445H30.2823H30.2201H30.1578H30.0956H30.0332H29.9709H29.9084H29.846H29.7835H29.7209H29.6583H29.5957H29.533H29.4703H29.4075H29.3447H29.2818H29.2189H29.1559H29.0929H29.0298H28.9666H28.9034H28.8402H28.7769H28.7135H28.6501H28.5867H28.5231H28.4596H28.3959H28.3322H28.2685H28.2046H28.1408H28.0768H28.0128H27.9487H27.8846H27.8204H27.7562H27.6918H27.6274H27.563H27.4985H27.4339H27.3692H27.3045H27.2397H27.1748H27.1098H27.0448H26.9797H26.9146H26.8493H26.784H26.7186H26.6532H26.5876H26.522H26.4563H26.3905H26.3247H26.2588H26.1928H26.1267H26.0605H25.9942H25.9279H25.8615H25.795H25.7284H25.6617H25.5949H25.5281H25.4611H25.3941H25.327H25.2598H25.1925H25.1251H25.0576H24.9901H24.9224H24.8547H24.7868H24.7189H24.6508H24.5827H24.5145H24.4461H24.3777H24.3092H24.2406H24.1719H24.103H24.0341H23.9651H23.896H23.8267H23.7574H23.688H23.6184H23.5488H23.479H23.4092H23.3392H23.2691H23.199H23.1287H23.0583H22.9878H22.9171H22.8464H22.7755H22.7046H22.6335H22.5623H22.491H22.4196H22.3481H22.2764H22.2046H22.1328H22.0607H21.9886H21.9164H21.844H21.7715H21.6989H21.6262H21.5533H21.4803H21.4072H21.334H21.2606H21.1872H21.1135H21.0398H20.9659H20.8919H20.8178H20.7436H20.6692H20.5947H20.52H20.4452H20.3703H20.2952H20.2201H20.1447H20.0693H19.9937H19.9179H19.8421H19.7661H19.6899H19.6136H19.5372H19.4606H19.3839H19.3071H19.2301H19.1529H19.0756H18.9982H18.9206H18.8429H18.765H18.687H18.6088H18.5305H18.452H18.3734H18.2946H18.2157H18.1367H18.0574H17.978H17.8985H17.8188H17.739H17.659H17.5788H17.4985H17.418H17.3374H17.2566H17.1757H17.0946H17.0133H16.9318H16.8503H16.7685H16.6866H16.6045H16.5222H16.4398H16.3572H16.2745H16.1915C16.045 181.5 15.9628 181.465 15.9092 181.432C15.8479 181.394 15.772 181.324 15.6978 181.198C15.5354 180.922 15.4617 180.509 15.5193 180.153C16.9196 171.496 24.6325 164.823 33.9925 164.823C43.3524 164.823 51.0654 171.496 52.4657 180.153C52.574 180.823 52.4052 181.064 52.319 181.152C52.1962 181.279 51.8413 181.5 50.9935 181.5Z" fill="var(--color-background)" stroke="var(--color-secondary-700)" stroke-width="3"/>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,8 @@
import { Component } from "@angular/core";
// Component is used so that the SVG can embed CSS color variables
@Component({
selector: "app-image-org-subscription-hidden",
templateUrl: "image-subscription-hidden.component.svg",
})
export class ImageSubscriptionHiddenComponent {}

View File

@@ -5,23 +5,38 @@ import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { UserBillingComponent } from "../../settings/user-billing.component";
import { PaymentMethodType } from "jslib-common/enums/paymentMethodType";
import { TransactionType } from "jslib-common/enums/transactionType";
import { VerifyBankRequest } from "jslib-common/models/request/verifyBankRequest";
import { BillingResponse } from "jslib-common/models/response/billingResponse";
@Component({
selector: "app-org-billing",
templateUrl: "../../settings/user-billing.component.html",
templateUrl: "./organization-billing.component.html",
})
export class OrganizationBillingComponent extends UserBillingComponent implements OnInit {
export class OrganizationBillingComponent implements OnInit {
loading = false;
firstLoaded = false;
showAdjustPayment = false;
showAddCredit = false;
billing: BillingResponse;
paymentMethodType = PaymentMethodType;
transactionType = TransactionType;
organizationId: string;
verifyAmount1: number;
verifyAmount2: number;
verifyBankPromise: Promise<any>;
// TODO - Make sure to properly split out the billing/invoice and payment method/account during org admin refresh
constructor(
apiService: ApiService,
i18nService: I18nService,
private apiService: ApiService,
private i18nService: I18nService,
private route: ActivatedRoute,
platformUtilsService: PlatformUtilsService,
logService: LogService
) {
super(apiService, i18nService, platformUtilsService, logService);
}
private platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
@@ -30,4 +45,110 @@ export class OrganizationBillingComponent extends UserBillingComponent implement
this.firstLoaded = true;
});
}
async load() {
if (this.loading) {
return;
}
this.loading = true;
if (this.organizationId != null) {
this.billing = await this.apiService.getOrganizationBilling(this.organizationId);
}
this.loading = false;
}
async verifyBank() {
if (this.loading) {
return;
}
try {
const request = new VerifyBankRequest();
request.amount1 = this.verifyAmount1;
request.amount2 = this.verifyAmount2;
this.verifyBankPromise = this.apiService.postOrganizationVerifyBank(
this.organizationId,
request
);
await this.verifyBankPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("verifiedBankAccount")
);
this.load();
} catch (e) {
this.logService.error(e);
}
}
addCredit() {
if (this.paymentSourceInApp) {
this.platformUtilsService.showDialog(
this.i18nService.t("cannotPerformInAppPurchase"),
this.i18nService.t("addCredit"),
null,
null,
"warning"
);
return;
}
this.showAddCredit = true;
}
closeAddCredit(load: boolean) {
this.showAddCredit = false;
if (load) {
this.load();
}
}
changePayment() {
if (this.paymentSourceInApp) {
this.platformUtilsService.showDialog(
this.i18nService.t("cannotPerformInAppPurchase"),
this.i18nService.t("changePaymentMethod"),
null,
null,
"warning"
);
return;
}
this.showAdjustPayment = true;
}
closePayment(load: boolean) {
this.showAdjustPayment = false;
if (load) {
this.load();
}
}
get isCreditBalance() {
return this.billing == null || this.billing.balance <= 0;
}
get creditOrBalance() {
return Math.abs(this.billing != null ? this.billing.balance : 0);
}
get paymentSource() {
return this.billing != null ? this.billing.paymentSource : null;
}
get paymentSourceInApp() {
return (
this.paymentSource != null &&
(this.paymentSource.type === PaymentMethodType.AppleInApp ||
this.paymentSource.type === PaymentMethodType.GoogleInApp)
);
}
get invoices() {
return this.billing != null ? this.billing.invoices : null;
}
get transactions() {
return this.billing != null ? this.billing.transactions : null;
}
}

View File

@@ -16,6 +16,14 @@
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<ng-container *ngIf="firstLoaded && !userOrg.canManageBilling">
<div class="tw-flex tw-flex-col tw-items-center tw-text-info">
<app-image-org-subscription-hidden></app-image-org-subscription-hidden>
<p class="tw-font-bold">{{ "billingManagedByProvider" | i18n: this.userOrg.providerName }}</p>
<p>{{ "billingContactProviderForAssistance" | i18n }}</p>
</div>
</ng-container>
<ng-container *ngIf="sub">
<app-callout
type="warning"

View File

@@ -63,7 +63,11 @@ export class OrganizationSubscriptionComponent implements OnInit {
this.loading = true;
this.userOrg = await this.organizationService.get(this.organizationId);
this.sub = await this.apiService.getOrganizationSubscription(this.organizationId);
if (this.userOrg.canManageBilling) {
this.sub = await this.apiService.getOrganizationSubscription(this.organizationId);
}
this.loading = false;
}

View File

@@ -14,7 +14,7 @@
routerLink="billing"
class="list-group-item"
routerLinkActive="active"
*ngIf="!selfHosted"
*ngIf="showBilling"
>
{{ "billing" | i18n }}
</a>

View File

@@ -10,7 +10,7 @@ import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.se
})
export class SettingsComponent {
access2fa = false;
selfHosted: boolean;
showBilling: boolean;
constructor(
private route: ActivatedRoute,
@@ -20,8 +20,8 @@ export class SettingsComponent {
ngOnInit() {
this.route.parent.params.subscribe(async (params) => {
this.selfHosted = await this.platformUtilsService.isSelfHost();
const organization = await this.organizationService.get(params.organizationId);
this.showBilling = !this.platformUtilsService.isSelfHost() && organization.canManageBilling;
this.access2fa = organization.use2fa;
});
}

View File

@@ -5,10 +5,12 @@ import { CipherService } from "jslib-common/abstractions/cipher.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { TotpService } from "jslib-common/abstractions/totp.service";
import { Organization } from "jslib-common/models/domain/organization";
import { CipherView } from "jslib-common/models/view/cipherView";
@@ -32,12 +34,14 @@ export class CiphersComponent extends BaseCiphersComponent {
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
cipherService: CipherService,
private apiService: ApiService,
eventService: EventService,
totpService: TotpService,
passwordRepromptService: PasswordRepromptService,
logService: LogService,
stateService: StateService
stateService: StateService,
organizationService: OrganizationService,
tokenService: TokenService,
private apiService: ApiService
) {
super(
searchService,
@@ -48,7 +52,9 @@ export class CiphersComponent extends BaseCiphersComponent {
totpService,
stateService,
passwordRepromptService,
logService
logService,
organizationService,
tokenService
);
}

View File

@@ -1,65 +0,0 @@
import { Component } from "@angular/core";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { CollectionData } from "jslib-common/models/data/collectionData";
import { Collection } from "jslib-common/models/domain/collection";
import { Organization } from "jslib-common/models/domain/organization";
import { CollectionDetailsResponse } from "jslib-common/models/response/collectionResponse";
import { CollectionView } from "jslib-common/models/view/collectionView";
import { GroupingsComponent as BaseGroupingsComponent } from "../../vault/groupings.component";
@Component({
selector: "app-org-vault-groupings",
templateUrl: "../../vault/groupings.component.html",
})
export class GroupingsComponent extends BaseGroupingsComponent {
organization: Organization;
constructor(
collectionService: CollectionService,
folderService: FolderService,
stateService: StateService,
private apiService: ApiService,
private i18nService: I18nService
) {
super(collectionService, folderService, stateService);
}
async loadCollections() {
if (!this.organization.canEditAnyCollection) {
await super.loadCollections(this.organization.id);
return;
}
const collections = await this.apiService.getCollections(this.organization.id);
if (collections != null && collections.data != null && collections.data.length) {
const collectionDomains = collections.data.map(
(r) => new Collection(new CollectionData(r as CollectionDetailsResponse))
);
this.collections = await this.collectionService.decryptMany(collectionDomains);
} else {
this.collections = [];
}
const unassignedCollection = new CollectionView();
unassignedCollection.name = this.i18nService.t("unassigned");
unassignedCollection.id = "unassigned";
unassignedCollection.organizationId = this.organization.id;
unassignedCollection.readOnly = true;
this.collections.push(unassignedCollection);
this.nestedCollections = await this.collectionService.getAllNested(this.collections);
}
async collapse(grouping: CollectionView) {
await super.collapse(grouping, "org_");
}
isCollapsed(grouping: CollectionView) {
return super.isCollapsed(grouping, "org_");
}
}

View File

@@ -1,10 +1,9 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { LockGuardService } from "jslib-angular/services/lock-guard.service";
import { UnauthGuardService } from "jslib-angular/services/unauth-guard.service";
import { Permissions } from "jslib-common/enums/permissions";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { LockGuard } from "jslib-angular/guards/lock.guard";
import { UnauthGuard } from "jslib-angular/guards/unauth.guard";
import { AcceptEmergencyComponent } from "./accounts/accept-emergency.component";
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
@@ -24,52 +23,23 @@ import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.compone
import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component";
import { HomeGuard } from "./guards/home.guard";
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
import { UserLayoutComponent } from "./layouts/user-layout.component";
import { CollectionsComponent as OrgManageCollectionsComponent } from "./organizations/manage/collections.component";
import { EventsComponent as OrgEventsComponent } from "./organizations/manage/events.component";
import { GroupsComponent as OrgGroupsComponent } from "./organizations/manage/groups.component";
import { ManageComponent as OrgManageComponent } from "./organizations/manage/manage.component";
import { PeopleComponent as OrgPeopleComponent } from "./organizations/manage/people.component";
import { PoliciesComponent as OrgPoliciesComponent } from "./organizations/manage/policies.component";
import { AccountComponent as OrgAccountComponent } from "./organizations/settings/account.component";
import { OrganizationBillingComponent } from "./organizations/settings/organization-billing.component";
import { OrganizationSubscriptionComponent } from "./organizations/settings/organization-subscription.component";
import { SettingsComponent as OrgSettingsComponent } from "./organizations/settings/settings.component";
import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "./organizations/settings/two-factor-setup.component";
import { AcceptFamilySponsorshipComponent } from "./organizations/sponsorships/accept-family-sponsorship.component";
import { FamiliesForEnterpriseSetupComponent } from "./organizations/sponsorships/families-for-enterprise-setup.component";
import { ExportComponent as OrgExportComponent } from "./organizations/tools/export.component";
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "./organizations/tools/exposed-passwords-report.component";
import { ImportComponent as OrgImportComponent } from "./organizations/tools/import.component";
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "./organizations/tools/inactive-two-factor-report.component";
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "./organizations/tools/reused-passwords-report.component";
import { ToolsComponent as OrgToolsComponent } from "./organizations/tools/tools.component";
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "./organizations/tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "./organizations/tools/weak-passwords-report.component";
import { VaultComponent as OrgVaultComponent } from "./organizations/vault/vault.component";
import { AccessComponent } from "./send/access.component";
import { SendComponent } from "./send/send.component";
import { OrganizationGuardService } from "./services/organization-guard.service";
import { OrganizationTypeGuardService } from "./services/organization-type-guard.service";
import { AccountComponent } from "./settings/account.component";
import { CreateOrganizationComponent } from "./settings/create-organization.component";
import { DomainRulesComponent } from "./settings/domain-rules.component";
import { EmergencyAccessViewComponent } from "./settings/emergency-access-view.component";
import { EmergencyAccessComponent } from "./settings/emergency-access.component";
import { OptionsComponent } from "./settings/options.component";
import { OrganizationsComponent } from "./settings/organizations.component";
import { PremiumComponent } from "./settings/premium.component";
import { PreferencesComponent } from "./settings/preferences.component";
import { SettingsComponent } from "./settings/settings.component";
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
import { UserBillingComponent } from "./settings/user-billing.component";
import { UserSubscriptionComponent } from "./settings/user-subscription.component";
import { ExportComponent } from "./tools/export.component";
import { GeneratorComponent } from "./tools/generator.component";
import { ImportComponent } from "./tools/import.component";
import { ToolsComponent } from "./tools/tools.component";
import { VaultComponent } from "./vault/vault.component";
const routes: Routes = [
{
@@ -83,18 +53,18 @@ const routes: Routes = [
children: [], // Children lets us have an empty component.
canActivate: [HomeGuard], // Redirects either to vault, login or lock page.
},
{ path: "login", component: LoginComponent, canActivate: [UnauthGuardService] },
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuardService] },
{ path: "login", component: LoginComponent, canActivate: [UnauthGuard] },
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard] },
{
path: "register",
component: RegisterComponent,
canActivate: [UnauthGuardService],
canActivate: [UnauthGuard],
data: { titleId: "createAccount" },
},
{
path: "sso",
component: SsoComponent,
canActivate: [UnauthGuardService],
canActivate: [UnauthGuard],
data: { titleId: "enterpriseSingleSignOn" },
},
{
@@ -105,13 +75,13 @@ const routes: Routes = [
{
path: "hint",
component: HintComponent,
canActivate: [UnauthGuardService],
canActivate: [UnauthGuard],
data: { titleId: "passwordHint" },
},
{
path: "lock",
component: LockComponent,
canActivate: [LockGuardService],
canActivate: [LockGuard],
},
{ path: "verify-email", component: VerifyEmailTokenComponent },
{
@@ -133,19 +103,19 @@ const routes: Routes = [
{
path: "recover-2fa",
component: RecoverTwoFactorComponent,
canActivate: [UnauthGuardService],
canActivate: [UnauthGuard],
data: { titleId: "recoverAccountTwoStep" },
},
{
path: "recover-delete",
component: RecoverDeleteComponent,
canActivate: [UnauthGuardService],
canActivate: [UnauthGuard],
data: { titleId: "deleteAccount" },
},
{
path: "verify-recover-delete",
component: VerifyRecoverDeleteComponent,
canActivate: [UnauthGuardService],
canActivate: [UnauthGuard],
data: { titleId: "deleteAccount" },
},
{
@@ -156,19 +126,19 @@ const routes: Routes = [
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
data: { titleId: "updateTempPassword" },
},
{
path: "update-password",
component: UpdatePasswordComponent,
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
data: { titleId: "updatePassword" },
},
{
path: "remove-password",
component: RemovePasswordComponent,
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
data: { titleId: "removeMasterPassword" },
},
],
@@ -176,9 +146,14 @@ const routes: Routes = [
{
path: "",
component: UserLayoutComponent,
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
children: [
{ path: "vault", component: VaultComponent, data: { titleId: "myVault" } },
{
path: "vault",
loadChildren: async () =>
(await import("./modules/vault/modules/individual-vault/individual-vault.module"))
.IndividualVaultModule,
},
{ path: "sends", component: SendComponent, data: { title: "Send" } },
{
path: "settings",
@@ -186,28 +161,25 @@ const routes: Routes = [
children: [
{ path: "", pathMatch: "full", redirectTo: "account" },
{ path: "account", component: AccountComponent, data: { titleId: "myAccount" } },
{ path: "options", component: OptionsComponent, data: { titleId: "options" } },
{
path: "preferences",
component: PreferencesComponent,
data: { titleId: "preferences" },
},
{
path: "security",
loadChildren: async () =>
(await import("./settings/security-routing.module")).SecurityRoutingModule,
},
{
path: "domain-rules",
component: DomainRulesComponent,
data: { titleId: "domainRules" },
},
{
path: "two-factor",
component: TwoFactorSetupComponent,
data: { titleId: "twoStepLogin" },
},
{ path: "premium", component: PremiumComponent, data: { titleId: "goPremium" } },
{ path: "billing", component: UserBillingComponent, data: { titleId: "billing" } },
{
path: "subscription",
component: UserSubscriptionComponent,
data: { titleId: "premiumMembership" },
},
{
path: "organizations",
component: OrganizationsComponent,
data: { titleId: "organizations" },
loadChildren: async () =>
(await import("./settings/subscription-routing.module")).SubscriptionRoutingModule,
},
{
path: "create-organization",
@@ -239,7 +211,7 @@ const routes: Routes = [
{
path: "tools",
component: ToolsComponent,
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
children: [
{ path: "", pathMatch: "full", redirectTo: "generator" },
{ path: "import", component: ImportComponent, data: { titleId: "importData" } },
@@ -253,194 +225,16 @@ const routes: Routes = [
},
{
path: "reports",
loadChildren: async () => (await import("./reports/reports.module")).ReportsModule,
loadChildren: async () =>
(await import("./reports/reports-routing.module")).ReportsRoutingModule,
},
{ path: "setup/families-for-enterprise", component: FamiliesForEnterpriseSetupComponent },
],
},
{
path: "organizations/:organizationId",
component: OrganizationLayoutComponent,
canActivate: [AuthGuardService, OrganizationGuardService],
children: [
{ path: "", pathMatch: "full", redirectTo: "vault" },
{ path: "vault", component: OrgVaultComponent, data: { titleId: "vault" } },
{
path: "tools",
component: OrgToolsComponent,
canActivate: [OrganizationTypeGuardService],
data: { permissions: [Permissions.AccessImportExport, Permissions.AccessReports] },
children: [
{
path: "",
pathMatch: "full",
redirectTo: "import",
},
{
path: "import",
component: OrgImportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "importData",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "export",
component: OrgExportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "exportVault",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "exposed-passwords-report",
component: OrgExposedPasswordsReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "exposedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "inactive-two-factor-report",
component: OrgInactiveTwoFactorReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "inactive2faReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "reused-passwords-report",
component: OrgReusedPasswordsReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "reusedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "unsecured-websites-report",
component: OrgUnsecuredWebsitesReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "unsecuredWebsitesReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "weak-passwords-report",
component: OrgWeakPasswordsReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "weakPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
],
},
{
path: "manage",
component: OrgManageComponent,
canActivate: [OrganizationTypeGuardService],
data: {
permissions: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
Permissions.AccessEventLogs,
Permissions.ManageGroups,
Permissions.ManageUsers,
Permissions.ManagePolicies,
],
},
children: [
{
path: "",
pathMatch: "full",
redirectTo: "people",
},
{
path: "collections",
component: OrgManageCollectionsComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "collections",
permissions: [
Permissions.CreateNewCollections,
Permissions.EditAnyCollection,
Permissions.DeleteAnyCollection,
Permissions.EditAssignedCollections,
Permissions.DeleteAssignedCollections,
],
},
},
{
path: "events",
component: OrgEventsComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "eventLogs",
permissions: [Permissions.AccessEventLogs],
},
},
{
path: "groups",
component: OrgGroupsComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "groups",
permissions: [Permissions.ManageGroups],
},
},
{
path: "people",
component: OrgPeopleComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "people",
permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword],
},
},
{
path: "policies",
component: OrgPoliciesComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "policies",
permissions: [Permissions.ManagePolicies],
},
},
],
},
{
path: "settings",
component: OrgSettingsComponent,
canActivate: [OrganizationTypeGuardService],
data: { permissions: [Permissions.ManageOrganization] },
children: [
{ path: "", pathMatch: "full", redirectTo: "account" },
{ path: "account", component: OrgAccountComponent, data: { titleId: "myOrganization" } },
{
path: "two-factor",
component: OrgTwoFactorSetupComponent,
data: { titleId: "twoStepLogin" },
},
{
path: "billing",
component: OrganizationBillingComponent,
data: { titleId: "billing" },
},
{
path: "subscription",
component: OrganizationSubscriptionComponent,
data: { titleId: "subscription" },
},
],
path: "organizations",
loadChildren: () =>
import("./organizations/organization-routing.module").then(
(m) => m.OrganizationsRoutingModule
),
},
],
},
@@ -451,7 +245,7 @@ const routes: Routes = [
RouterModule.forRoot(routes, {
useHash: true,
paramsInheritanceStrategy: "always",
/*enableTracing: true,*/
// enableTracing: true,
}),
],
exports: [RouterModule],

View File

@@ -1,448 +1,20 @@
import { DragDropModule } from "@angular/cdk/drag-drop";
import { DatePipe, registerLocaleData, CommonModule } from "@angular/common";
import localeAf from "@angular/common/locales/af";
import localeAz from "@angular/common/locales/az";
import localeBe from "@angular/common/locales/be";
import localeBg from "@angular/common/locales/bg";
import localeBn from "@angular/common/locales/bn";
import localeBs from "@angular/common/locales/bs";
import localeCa from "@angular/common/locales/ca";
import localeCs from "@angular/common/locales/cs";
import localeDa from "@angular/common/locales/da";
import localeDe from "@angular/common/locales/de";
import localeEl from "@angular/common/locales/el";
import localeEnGb from "@angular/common/locales/en-GB";
import localeEnIn from "@angular/common/locales/en-IN";
import localeEo from "@angular/common/locales/eo";
import localeEs from "@angular/common/locales/es";
import localeEt from "@angular/common/locales/et";
import localeFi from "@angular/common/locales/fi";
import localeFil from "@angular/common/locales/fil";
import localeFr from "@angular/common/locales/fr";
import localeHe from "@angular/common/locales/he";
import localeHi from "@angular/common/locales/hi";
import localeHr from "@angular/common/locales/hr";
import localeHu from "@angular/common/locales/hu";
import localeId from "@angular/common/locales/id";
import localeIt from "@angular/common/locales/it";
import localeJa from "@angular/common/locales/ja";
import localeKa from "@angular/common/locales/ka";
import localeKm from "@angular/common/locales/km";
import localeKn from "@angular/common/locales/kn";
import localeKo from "@angular/common/locales/ko";
import localeLv from "@angular/common/locales/lv";
import localeMl from "@angular/common/locales/ml";
import localeNb from "@angular/common/locales/nb";
import localeNl from "@angular/common/locales/nl";
import localeNn from "@angular/common/locales/nn";
import localePl from "@angular/common/locales/pl";
import localePtBr from "@angular/common/locales/pt";
import localePtPt from "@angular/common/locales/pt-PT";
import localeRo from "@angular/common/locales/ro";
import localeRu from "@angular/common/locales/ru";
import localeSi from "@angular/common/locales/si";
import localeSk from "@angular/common/locales/sk";
import localeSl from "@angular/common/locales/sl";
import localeSr from "@angular/common/locales/sr";
import localeSv from "@angular/common/locales/sv";
import localeTr from "@angular/common/locales/tr";
import localeUk from "@angular/common/locales/uk";
import localeVi from "@angular/common/locales/vi";
import localeZhCn from "@angular/common/locales/zh-Hans";
import localeZhTw from "@angular/common/locales/zh-Hant";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { RouterModule } from "@angular/router";
import { BadgeModule, ButtonModule } from "@bitwarden/components";
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { ToastrModule } from "ngx-toastr";
import { UserVerificationComponent } from "jslib-angular/components/user-verification.component";
import { JslibModule } from "jslib-angular/jslib.module";
import { AcceptEmergencyComponent } from "./accounts/accept-emergency.component";
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
import { HintComponent } from "./accounts/hint.component";
import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from "./accounts/login.component";
import { RecoverDeleteComponent } from "./accounts/recover-delete.component";
import { RecoverTwoFactorComponent } from "./accounts/recover-two-factor.component";
import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from "./accounts/set-password.component";
import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdatePasswordComponent } from "./accounts/update-password.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component";
import { NestedCheckboxComponent } from "./components/nested-checkbox.component";
import { PasswordRepromptComponent } from "./components/password-reprompt.component";
import { PasswordStrengthComponent } from "./components/password-strength.component";
import { PremiumBadgeComponent } from "./components/premium-badge.component";
import { FooterComponent } from "./layouts/footer.component";
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
import { NavbarComponent } from "./layouts/navbar.component";
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
import { UserLayoutComponent } from "./layouts/user-layout.component";
import { BulkConfirmComponent as OrgBulkConfirmComponent } from "./organizations/manage/bulk/bulk-confirm.component";
import { BulkRemoveComponent as OrgBulkRemoveComponent } from "./organizations/manage/bulk/bulk-remove.component";
import { BulkStatusComponent as OrgBulkStatusComponent } from "./organizations/manage/bulk/bulk-status.component";
import { CollectionAddEditComponent as OrgCollectionAddEditComponent } from "./organizations/manage/collection-add-edit.component";
import { CollectionsComponent as OrgManageCollectionsComponent } from "./organizations/manage/collections.component";
import { EntityEventsComponent as OrgEntityEventsComponent } from "./organizations/manage/entity-events.component";
import { EntityUsersComponent as OrgEntityUsersComponent } from "./organizations/manage/entity-users.component";
import { EventsComponent as OrgEventsComponent } from "./organizations/manage/events.component";
import { GroupAddEditComponent as OrgGroupAddEditComponent } from "./organizations/manage/group-add-edit.component";
import { GroupsComponent as OrgGroupsComponent } from "./organizations/manage/groups.component";
import { ManageComponent as OrgManageComponent } from "./organizations/manage/manage.component";
import { PeopleComponent as OrgPeopleComponent } from "./organizations/manage/people.component";
import { PoliciesComponent as OrgPoliciesComponent } from "./organizations/manage/policies.component";
import { PolicyEditComponent as OrgPolicyEditComponent } from "./organizations/manage/policy-edit.component";
import { ResetPasswordComponent as OrgResetPasswordComponent } from "./organizations/manage/reset-password.component";
import { UserAddEditComponent as OrgUserAddEditComponent } from "./organizations/manage/user-add-edit.component";
import { UserConfirmComponent as OrgUserConfirmComponent } from "./organizations/manage/user-confirm.component";
import { UserGroupsComponent as OrgUserGroupsComponent } from "./organizations/manage/user-groups.component";
import { DisableSendPolicyComponent } from "./organizations/policies/disable-send.component";
import { MasterPasswordPolicyComponent } from "./organizations/policies/master-password.component";
import { PasswordGeneratorPolicyComponent } from "./organizations/policies/password-generator.component";
import { PersonalOwnershipPolicyComponent } from "./organizations/policies/personal-ownership.component";
import { RequireSsoPolicyComponent } from "./organizations/policies/require-sso.component";
import { ResetPasswordPolicyComponent } from "./organizations/policies/reset-password.component";
import { SendOptionsPolicyComponent } from "./organizations/policies/send-options.component";
import { SingleOrgPolicyComponent } from "./organizations/policies/single-org.component";
import { TwoFactorAuthenticationPolicyComponent } from "./organizations/policies/two-factor-authentication.component";
import { AccountComponent as OrgAccountComponent } from "./organizations/settings/account.component";
import { AdjustSubscription } from "./organizations/settings/adjust-subscription.component";
import { ChangePlanComponent } from "./organizations/settings/change-plan.component";
import { DeleteOrganizationComponent } from "./organizations/settings/delete-organization.component";
import { DownloadLicenseComponent } from "./organizations/settings/download-license.component";
import { OrganizationBillingComponent } from "./organizations/settings/organization-billing.component";
import { OrganizationSubscriptionComponent } from "./organizations/settings/organization-subscription.component";
import { SettingsComponent as OrgSettingComponent } from "./organizations/settings/settings.component";
import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "./organizations/settings/two-factor-setup.component";
import { AcceptFamilySponsorshipComponent } from "./organizations/sponsorships/accept-family-sponsorship.component";
import { FamiliesForEnterpriseSetupComponent } from "./organizations/sponsorships/families-for-enterprise-setup.component";
import { ExportComponent as OrgExportComponent } from "./organizations/tools/export.component";
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "./organizations/tools/exposed-passwords-report.component";
import { ImportComponent as OrgImportComponent } from "./organizations/tools/import.component";
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "./organizations/tools/inactive-two-factor-report.component";
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "./organizations/tools/reused-passwords-report.component";
import { ToolsComponent as OrgToolsComponent } from "./organizations/tools/tools.component";
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "./organizations/tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "./organizations/tools/weak-passwords-report.component";
import { AddEditComponent as OrgAddEditComponent } from "./organizations/vault/add-edit.component";
import { AttachmentsComponent as OrgAttachmentsComponent } from "./organizations/vault/attachments.component";
import { CiphersComponent as OrgCiphersComponent } from "./organizations/vault/ciphers.component";
import { CollectionsComponent as OrgCollectionsComponent } from "./organizations/vault/collections.component";
import { GroupingsComponent as OrgGroupingsComponent } from "./organizations/vault/groupings.component";
import { VaultComponent as OrgVaultComponent } from "./organizations/vault/vault.component";
import { ProvidersComponent } from "./providers/providers.component";
import { BreachReportComponent } from "./reports/breach-report.component";
import { ExposedPasswordsReportComponent } from "./reports/exposed-passwords-report.component";
import { InactiveTwoFactorReportComponent } from "./reports/inactive-two-factor-report.component";
import { ReportCardComponent } from "./reports/report-card.component";
import { ReportListComponent } from "./reports/report-list.component";
import { ReportsComponent } from "./reports/reports.component";
import { ReusedPasswordsReportComponent } from "./reports/reused-passwords-report.component";
import { UnsecuredWebsitesReportComponent } from "./reports/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./reports/weak-passwords-report.component";
import { AccessComponent } from "./send/access.component";
import { AddEditComponent as SendAddEditComponent } from "./send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
import { SendComponent } from "./send/send.component";
import { AccountComponent } from "./settings/account.component";
import { AddCreditComponent } from "./settings/add-credit.component";
import { AdjustPaymentComponent } from "./settings/adjust-payment.component";
import { AdjustStorageComponent } from "./settings/adjust-storage.component";
import { ApiKeyComponent } from "./settings/api-key.component";
import { ChangeEmailComponent } from "./settings/change-email.component";
import { ChangeKdfComponent } from "./settings/change-kdf.component";
import { ChangePasswordComponent } from "./settings/change-password.component";
import { CreateOrganizationComponent } from "./settings/create-organization.component";
import { DeauthorizeSessionsComponent } from "./settings/deauthorize-sessions.component";
import { DeleteAccountComponent } from "./settings/delete-account.component";
import { DomainRulesComponent } from "./settings/domain-rules.component";
import { EmergencyAccessAddEditComponent } from "./settings/emergency-access-add-edit.component";
import { EmergencyAccessAttachmentsComponent } from "./settings/emergency-access-attachments.component";
import { EmergencyAccessConfirmComponent } from "./settings/emergency-access-confirm.component";
import { EmergencyAccessTakeoverComponent } from "./settings/emergency-access-takeover.component";
import { EmergencyAccessViewComponent } from "./settings/emergency-access-view.component";
import { EmergencyAccessComponent } from "./settings/emergency-access.component";
import { EmergencyAddEditComponent } from "./settings/emergency-add-edit.component";
import { LinkSsoComponent } from "./settings/link-sso.component";
import { OptionsComponent } from "./settings/options.component";
import { OrganizationPlansComponent } from "./settings/organization-plans.component";
import { OrganizationsComponent } from "./settings/organizations.component";
import { PaymentComponent } from "./settings/payment.component";
import { PremiumComponent } from "./settings/premium.component";
import { ProfileComponent } from "./settings/profile.component";
import { PurgeVaultComponent } from "./settings/purge-vault.component";
import { SettingsComponent } from "./settings/settings.component";
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
import { SponsoringOrgRowComponent } from "./settings/sponsoring-org-row.component";
import { TaxInfoComponent } from "./settings/tax-info.component";
import { TwoFactorAuthenticatorComponent } from "./settings/two-factor-authenticator.component";
import { TwoFactorDuoComponent } from "./settings/two-factor-duo.component";
import { TwoFactorEmailComponent } from "./settings/two-factor-email.component";
import { TwoFactorRecoveryComponent } from "./settings/two-factor-recovery.component";
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
import { TwoFactorVerifyComponent } from "./settings/two-factor-verify.component";
import { TwoFactorWebAuthnComponent } from "./settings/two-factor-webauthn.component";
import { TwoFactorYubiKeyComponent } from "./settings/two-factor-yubikey.component";
import { UpdateKeyComponent } from "./settings/update-key.component";
import { UpdateLicenseComponent } from "./settings/update-license.component";
import { UserBillingComponent } from "./settings/user-billing.component";
import { UserSubscriptionComponent } from "./settings/user-subscription.component";
import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
import { VerifyEmailComponent } from "./settings/verify-email.component";
import { ExportComponent } from "./tools/export.component";
import { GeneratorComponent } from "./tools/generator.component";
import { ImportComponent } from "./tools/import.component";
import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component";
import { ToolsComponent } from "./tools/tools.component";
import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from "./vault/attachments.component";
import { BulkActionsComponent } from "./vault/bulk-actions.component";
import { BulkDeleteComponent } from "./vault/bulk-delete.component";
import { BulkMoveComponent } from "./vault/bulk-move.component";
import { BulkRestoreComponent } from "./vault/bulk-restore.component";
import { BulkShareComponent } from "./vault/bulk-share.component";
import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from "./vault/collections.component";
import { FolderAddEditComponent } from "./vault/folder-add-edit.component";
import { GroupingsComponent } from "./vault/groupings.component";
import { ShareComponent } from "./vault/share.component";
import { VaultComponent } from "./vault/vault.component";
registerLocaleData(localeAf, "af");
registerLocaleData(localeAz, "az");
registerLocaleData(localeBe, "be");
registerLocaleData(localeBg, "bg");
registerLocaleData(localeBn, "bn");
registerLocaleData(localeBs, "bs");
registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, "cs");
registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, "el");
registerLocaleData(localeEnGb, "en-GB");
registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEo, "eo");
registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, "et");
registerLocaleData(localeFi, "fi");
registerLocaleData(localeFil, "fil");
registerLocaleData(localeFr, "fr");
registerLocaleData(localeHe, "he");
registerLocaleData(localeHi, "hi");
registerLocaleData(localeHr, "hr");
registerLocaleData(localeHu, "hu");
registerLocaleData(localeId, "id");
registerLocaleData(localeIt, "it");
registerLocaleData(localeJa, "ja");
registerLocaleData(localeKa, "ka");
registerLocaleData(localeKm, "km");
registerLocaleData(localeKn, "kn");
registerLocaleData(localeKo, "ko");
registerLocaleData(localeLv, "lv");
registerLocaleData(localeMl, "ml");
registerLocaleData(localeNb, "nb");
registerLocaleData(localeNl, "nl");
registerLocaleData(localeNn, "nn");
registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, "pt-PT");
registerLocaleData(localeRo, "ro");
registerLocaleData(localeRu, "ru");
registerLocaleData(localeSi, "si");
registerLocaleData(localeSk, "sk");
registerLocaleData(localeSl, "sl");
registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, "sv");
registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, "uk");
registerLocaleData(localeVi, "vi");
registerLocaleData(localeZhCn, "zh-CN");
registerLocaleData(localeZhTw, "zh-TW");
import { LooseComponentsModule } from "./modules/loose-components.module";
import { PipesModule } from "./modules/pipes/pipes.module";
import { SharedModule } from "./modules/shared.module";
import { VaultFilterModule } from "./modules/vault-filter/vault-filter.module";
import { OrganizationBadgeModule } from "./modules/vault/modules/organization-badge/organization-badge.module";
@NgModule({
imports: [
CommonModule,
DragDropModule,
FormsModule,
InfiniteScrollModule,
JslibModule,
ReactiveFormsModule,
RouterModule,
ToastrModule,
BadgeModule,
ButtonModule,
SharedModule,
LooseComponentsModule,
VaultFilterModule,
OrganizationBadgeModule,
PipesModule,
],
declarations: [
PremiumBadgeComponent,
AcceptEmergencyComponent,
AcceptFamilySponsorshipComponent,
AcceptOrganizationComponent,
AccessComponent,
AccountComponent,
AddCreditComponent,
AddEditComponent,
AddEditCustomFieldsComponent,
AddEditCustomFieldsComponent,
AdjustPaymentComponent,
AdjustStorageComponent,
AdjustSubscription,
ApiKeyComponent,
AttachmentsComponent,
BreachReportComponent,
BulkActionsComponent,
BulkDeleteComponent,
BulkMoveComponent,
BulkRestoreComponent,
BulkShareComponent,
ChangeEmailComponent,
ChangeKdfComponent,
ChangePasswordComponent,
ChangePlanComponent,
CiphersComponent,
CollectionsComponent,
CreateOrganizationComponent,
DeauthorizeSessionsComponent,
DeleteAccountComponent,
DeleteOrganizationComponent,
DisableSendPolicyComponent,
DomainRulesComponent,
DownloadLicenseComponent,
EmergencyAccessAddEditComponent,
EmergencyAccessAttachmentsComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
EmergencyAddEditComponent,
ExportComponent,
ExposedPasswordsReportComponent,
FamiliesForEnterpriseSetupComponent,
FolderAddEditComponent,
FooterComponent,
FrontendLayoutComponent,
GroupingsComponent,
HintComponent,
ImportComponent,
InactiveTwoFactorReportComponent,
LinkSsoComponent,
LockComponent,
LoginComponent,
MasterPasswordPolicyComponent,
NavbarComponent,
NestedCheckboxComponent,
OptionsComponent,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
OrganizationLayoutComponent,
OrganizationPlansComponent,
OrganizationsComponent,
OrganizationSubscriptionComponent,
OrgAttachmentsComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
OrgBulkStatusComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
OrgEntityUsersComponent,
OrgEventsComponent,
OrgExportComponent,
OrgExposedPasswordsReportComponent,
OrgGroupAddEditComponent,
OrgGroupingsComponent,
OrgGroupsComponent,
OrgImportComponent,
OrgInactiveTwoFactorReportComponent,
OrgManageCollectionsComponent,
OrgManageComponent,
OrgPeopleComponent,
OrgPoliciesComponent,
OrgPolicyEditComponent,
OrgResetPasswordComponent,
OrgReusedPasswordsReportComponent,
OrgSettingComponent,
OrgToolsComponent,
OrgTwoFactorSetupComponent,
OrgUnsecuredWebsitesReportComponent,
OrgUserAddEditComponent,
OrgUserConfirmComponent,
OrgUserGroupsComponent,
OrgVaultComponent,
OrgWeakPasswordsReportComponent,
GeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordGeneratorPolicyComponent,
PasswordRepromptComponent,
PasswordStrengthComponent,
PaymentComponent,
PersonalOwnershipPolicyComponent,
PremiumComponent,
ProfileComponent,
ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RegisterComponent,
RemovePasswordComponent,
ReportCardComponent,
ReportListComponent,
ReportsComponent,
RequireSsoPolicyComponent,
ResetPasswordPolicyComponent,
ReusedPasswordsReportComponent,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SendOptionsPolicyComponent,
SetPasswordComponent,
SettingsComponent,
ShareComponent,
SingleOrgPolicyComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
SsoComponent,
TaxInfoComponent,
ToolsComponent,
TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
UpdateLicenseComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
UserBillingComponent,
UserLayoutComponent,
UserSubscriptionComponent,
UserVerificationComponent,
VaultComponent,
VaultTimeoutInputComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent,
],
exports: [FooterComponent, NavbarComponent, OrganizationPlansComponent],
providers: [DatePipe],
exports: [LooseComponentsModule, VaultFilterModule, OrganizationBadgeModule, PipesModule],
bootstrap: [],
})
export class OssModule {}

View File

@@ -1,58 +1,33 @@
<ng-container *ngIf="vault">
<app-navbar></app-navbar>
<div class="container page-content">
<div class="page-header d-flex">
<h1>{{ "providers" | i18n }}</h1>
</div>
<p *ngIf="!loaded" class="text-muted">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p>
<ng-container *ngIf="loaded">
<ul class="bwi-ul card-ul carets" *ngIf="providers && providers.length">
<li *ngFor="let p of providers">
<a [routerLink]="['/providers', p.id]" class="text-body">
<i class="bwi bwi-li bwi-caret-right" aria-hidden="true"></i> {{ p.name }}
<ng-container *ngIf="!p.enabled">
<i
class="bwi bwi-exclamation-triangle text-danger"
title="{{ 'providerIsDisabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "providerIsDisabled" | i18n }}</span>
</ng-container>
</a>
</li>
</ul>
<table class="table table-hover table-list" *ngIf="providers && providers.length">
<tbody>
<tr *ngFor="let p of providers">
<td width="30">
<app-avatar [data]="p.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
</td>
<td>
<a href="#" [routerLink]="['/providers', p.id]">{{ p.name }}</a>
<ng-container *ngIf="!p.enabled">
<i
class="bwi bwi-exclamation-triangle text-danger"
title="{{ 'providerIsDisabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "providerIsDisabled" | i18n }}</span>
</ng-container>
</td>
</tr>
</tbody>
</table>
</ng-container>
</ng-container>
<ng-container *ngIf="!vault">
<app-navbar></app-navbar>
<div class="container page-content">
<div class="page-header d-flex">
<h1>{{ "providers" | i18n }}</h1>
</div>
<p *ngIf="!loaded" class="text-muted">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p>
<ng-container *ngIf="loaded">
<table class="table table-hover table-list" *ngIf="providers && providers.length">
<tbody>
<tr *ngFor="let p of providers">
<td width="30">
<app-avatar [data]="p.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
</td>
<td>
<a href="#" [routerLink]="['/providers', p.id]">{{ p.name }}</a>
<ng-container *ngIf="!p.enabled">
<i
class="bwi bwi-exclamation-triangle text-danger"
title="{{ 'providerIsDisabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "providerIsDisabled" | i18n }}</span>
</ng-container>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<app-footer></app-footer>
</ng-container>
</div>
<app-footer></app-footer>

View File

@@ -1,4 +1,4 @@
import { Component, Input, OnInit } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
@@ -10,8 +10,6 @@ import { Provider } from "jslib-common/models/domain/provider";
templateUrl: "providers.component.html",
})
export class ProvidersComponent implements OnInit {
@Input() vault = false;
providers: Provider[];
loaded = false;
actionPromise: Promise<any>;

View File

@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { BreachReportComponent } from "./breach-report.component";
import { ExposedPasswordsReportComponent } from "./exposed-passwords-report.component";
@@ -16,7 +16,7 @@ const routes: Routes = [
{
path: "",
component: ReportsComponent,
canActivate: [AuthGuardService],
canActivate: [AuthGuard],
children: [
{ path: "", pathMatch: "full", component: ReportListComponent, data: { homepage: true } },
{
@@ -57,4 +57,4 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class ReportsModule {}
export class ReportsRoutingModule {}

View File

@@ -19,26 +19,48 @@
autocomplete="off"
appAutofocus
/>
<ul class="bwi-ul card-ul">
<li [ngClass]="{ active: selectedAll }">
<a href="#" appStopClick (click)="selectAll()">
<i class="bwi bwi-li bwi-fw bwi-filter"></i>{{ "allSends" | i18n }}
</a>
</li>
</ul>
<h3>{{ "types" | i18n }}</h3>
<ul class="bwi-ul card-ul">
<li [ngClass]="{ active: selectedType === sendType.Text }">
<a href="#" appStopClick (click)="selectType(sendType.Text)">
<i class="bwi bwi-li bwi-fw bwi-file-text"></i>{{ "sendTypeText" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedType === sendType.File }">
<a href="#" appStopClick (click)="selectType(sendType.File)">
<i class="bwi bwi-li bwi-fw bwi-file"></i>{{ "sendTypeFile" | i18n }}
</a>
</li>
</ul>
<div class="filter">
<ul class="filter-options">
<li class="filter-option" [ngClass]="{ active: selectedAll }">
<span class="filter-buttons">
<button bit-button class="filter-button" appStopClick (click)="selectAll()">
<i class="bwi bwi-fw bwi-filter"></i>{{ "allSends" | i18n }}
</button>
</span>
</li>
</ul>
</div>
<div class="filter">
<div class="filter-heading">
<h3>{{ "types" | i18n }}</h3>
</div>
<ul class="filter-options">
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.Text }">
<span class="filter-buttons">
<button
bit-button
class="filter-button"
appStopClick
(click)="selectType(sendType.Text)"
>
<i class="bwi bwi-fw bwi-file-text"></i>{{ "sendTypeText" | i18n }}
</button>
</span>
</li>
<li class="filter-option" [ngClass]="{ active: selectedType === sendType.File }">
<span class="filter-buttons">
<button
bit-button
class="filter-button"
appStopClick
(click)="selectType(sendType.File)"
>
<i class="bwi bwi-fw bwi-file"></i>{{ "sendTypeFile" | i18n }}
</button>
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
@@ -129,39 +151,34 @@
<small appStopProp>{{ s.deletionDate | date: "medium" }}</small>
</td>
<td class="table-list-options">
<div class="dropdown" appListDropdown>
<button
class="btn btn-outline-secondary dropdown-toggle"
type="button"
id="dropdownMenuButton"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
appA11yTitle="{{ 'options' | i18n }}"
>
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
<button
[bitMenuTriggerFor]="sendOptions"
class="tw-border-none tw-bg-transparent tw-text-main"
type="button"
appA11yTitle="{{ 'options' | i18n }}"
>
<i class="bwi bwi-ellipsis-v bwi-lg" aria-hidden="true"></i>
</button>
<bit-menu #sendOptions>
<button bit-menu-item (click)="copy(s)">
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
{{ "copySendLink" | i18n }}
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#" appStopClick (click)="copy(s)">
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
{{ "copySendLink" | i18n }}
</a>
<a
class="dropdown-item"
href="#"
appStopClick
(click)="removePassword(s)"
*ngIf="s.password && !disableSend"
>
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
{{ "removePassword" | i18n }}
</a>
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(s)">
<button
bit-menu-item
(click)="removePassword(s)"
*ngIf="s.password && !disableSend"
>
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
{{ "removePassword" | i18n }}
</button>
<button bit-menu-item (click)="delete(s)">
<span class="tw-text-danger">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
{{ "delete" | i18n }}
</a>
</div>
</div>
</span>
</button>
</bit-menu>
</td>
</tr>
</tbody>

View File

@@ -1,40 +0,0 @@
import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { Permissions } from "jslib-common/enums/permissions";
@Injectable()
export class OrganizationTypeGuardService implements CanActivate {
constructor(private organizationService: OrganizationService, private router: Router) {}
async canActivate(route: ActivatedRouteSnapshot) {
const org = await this.organizationService.get(route.params.organizationId);
const permissions = route.data == null ? null : (route.data.permissions as Permissions[]);
if (
(permissions.indexOf(Permissions.AccessEventLogs) !== -1 && org.canAccessEventLogs) ||
(permissions.indexOf(Permissions.AccessImportExport) !== -1 && org.canAccessImportExport) ||
(permissions.indexOf(Permissions.AccessReports) !== -1 && org.canAccessReports) ||
(permissions.indexOf(Permissions.CreateNewCollections) !== -1 &&
org.canCreateNewCollections) ||
(permissions.indexOf(Permissions.EditAnyCollection) !== -1 && org.canEditAnyCollection) ||
(permissions.indexOf(Permissions.DeleteAnyCollection) !== -1 && org.canDeleteAnyCollection) ||
(permissions.indexOf(Permissions.EditAssignedCollections) !== -1 &&
org.canEditAssignedCollections) ||
(permissions.indexOf(Permissions.DeleteAssignedCollections) !== -1 &&
org.canDeleteAssignedCollections) ||
(permissions.indexOf(Permissions.ManageGroups) !== -1 && org.canManageGroups) ||
(permissions.indexOf(Permissions.ManageOrganization) !== -1 && org.isOwner) ||
(permissions.indexOf(Permissions.ManagePolicies) !== -1 && org.canManagePolicies) ||
(permissions.indexOf(Permissions.ManageUsers) !== -1 && org.canManageUsers) ||
(permissions.indexOf(Permissions.ManageUsersPassword) !== -1 && org.canManageUsersPassword) ||
(permissions.indexOf(Permissions.ManageSso) !== -1 && org.canManageSso)
) {
return true;
}
this.router.navigate(["/organizations", org.id]);
return false;
}
}

View File

@@ -39,12 +39,12 @@ import { StateService } from "../../services/state.service";
import { StateMigrationService } from "../../services/stateMigration.service";
import { WebPlatformUtilsService } from "../../services/webPlatformUtils.service";
import { HomeGuard } from "../guards/home.guard";
import { PermissionsGuard as OrgPermissionsGuard } from "../organizations/guards/permissions.guard";
import { NavigationPermissionsService as OrgPermissionsService } from "../organizations/services/navigation-permissions.service";
import { EventService } from "./event.service";
import { InitService } from "./init.service";
import { ModalService } from "./modal.service";
import { OrganizationGuardService } from "./organization-guard.service";
import { OrganizationTypeGuardService } from "./organization-type-guard.service";
import { PolicyListService } from "./policy-list.service";
import { RouterService } from "./router.service";
@@ -52,9 +52,9 @@ import { RouterService } from "./router.service";
imports: [ToastrModule, JslibServicesModule],
declarations: [],
providers: [
OrgPermissionsService,
OrgPermissionsGuard,
InitService,
OrganizationGuardService,
OrganizationTypeGuardService,
RouterService,
EventService,
PolicyListService,

View File

@@ -8,43 +8,19 @@
</div>
<app-change-email></app-change-email>
</ng-container>
<ng-container *ngIf="showChangePassword">
<div class="secondary-header">
<h1>{{ "changeMasterPassword" | i18n }}</h1>
</div>
<app-change-password></app-change-password>
</ng-container>
<ng-container *ngIf="showChangeKdf">
<div class="secondary-header">
<h1>{{ "encKeySettings" | i18n }}</h1>
</div>
<app-change-kdf></app-change-kdf>
</ng-container>
<div class="secondary-header border-0 mb-0">
<h1>{{ "apiKey" | i18n }}</h1>
</div>
<p>
{{ "userApiKeyDesc" | i18n }}
</p>
<button type="button" class="btn btn-outline-secondary" (click)="viewUserApiKey()">
{{ "viewApiKey" | i18n }}
</button>
<button type="button" class="btn btn-outline-secondary" (click)="rotateUserApiKey()">
{{ "rotateApiKey" | i18n }}
</button>
<div class="secondary-header text-danger border-0 mb-0">
<h1>{{ "dangerZone" | i18n }}</h1>
</div>
<div class="card border-danger">
<div class="card-body">
<p>{{ "dangerZoneDesc" | i18n }}</p>
<button type="button" class="btn btn-outline-danger" (click)="deauthorizeSessions()">
<button bit-button buttonType="danger" (click)="deauthorizeSessions()">
{{ "deauthorizeSessions" | i18n }}
</button>
<button type="button" class="btn btn-outline-danger" (click)="purgeVault()">
<button bit-button buttonType="danger" (click)="purgeVault()">
{{ "purgeVault" | i18n }}
</button>
<button type="button" class="btn btn-outline-danger" (click)="deleteAccount()">
<button bit-button buttonType="danger" (click)="deleteAccount()">
{{ "deleteAccount" | i18n }}
</button>
</div>

View File

@@ -5,7 +5,6 @@ import { ApiService } from "jslib-common/abstractions/api.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { ApiKeyComponent } from "./api-key.component";
import { DeauthorizeSessionsComponent } from "./deauthorize-sessions.component";
import { DeleteAccountComponent } from "./delete-account.component";
import { PurgeVaultComponent } from "./purge-vault.component";
@@ -21,13 +20,7 @@ export class AccountComponent {
purgeModalRef: ViewContainerRef;
@ViewChild("deleteAccountTemplate", { read: ViewContainerRef, static: true })
deleteModalRef: ViewContainerRef;
@ViewChild("viewUserApiKeyTemplate", { read: ViewContainerRef, static: true })
viewUserApiKeyModalRef: ViewContainerRef;
@ViewChild("rotateUserApiKeyTemplate", { read: ViewContainerRef, static: true })
rotateUserApiKeyModalRef: ViewContainerRef;
showChangePassword = true;
showChangeKdf = true;
showChangeEmail = true;
constructor(
@@ -38,10 +31,7 @@ export class AccountComponent {
) {}
async ngOnInit() {
this.showChangeEmail =
this.showChangeKdf =
this.showChangePassword =
!(await this.keyConnectorService.getUsesKeyConnector());
this.showChangeEmail = !(await this.keyConnectorService.getUsesKeyConnector());
}
async deauthorizeSessions() {
@@ -55,33 +45,4 @@ export class AccountComponent {
async deleteAccount() {
await this.modalService.openViewRef(DeleteAccountComponent, this.deleteModalRef);
}
async viewUserApiKey() {
const entityId = await this.stateService.getUserId();
await this.modalService.openViewRef(ApiKeyComponent, this.viewUserApiKeyModalRef, (comp) => {
comp.keyType = "user";
comp.entityId = entityId;
comp.postKey = this.apiService.postUserApiKey.bind(this.apiService);
comp.scope = "api";
comp.grantType = "client_credentials";
comp.apiKeyTitle = "apiKey";
comp.apiKeyWarning = "userApiKeyWarning";
comp.apiKeyDescription = "userApiKeyDesc";
});
}
async rotateUserApiKey() {
const entityId = await this.stateService.getUserId();
await this.modalService.openViewRef(ApiKeyComponent, this.rotateUserApiKeyModalRef, (comp) => {
comp.keyType = "user";
comp.isRotation = true;
comp.entityId = entityId;
comp.postKey = this.apiService.postUserRotateApiKey.bind(this.apiService);
comp.scope = "api";
comp.grantType = "client_credentials";
comp.apiKeyTitle = "apiKey";
comp.apiKeyWarning = "userApiKeyWarning";
comp.apiKeyDescription = "apiKeyRotateDesc";
});
}
}

View File

@@ -1,4 +1,7 @@
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
<div class="tabbed-header">
<h1>{{ "encKeySettings" | i18n }}</h1>
</div>
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row">
<div class="col-6">
@@ -68,7 +71,7 @@
</div>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<button bit-button buttonType="primary" class="btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "changeKdf" | i18n }}</span>
</button>

View File

@@ -1,4 +1,8 @@
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
<div class="tabbed-header">
<h1>{{ "changeMasterPassword" | i18n }}</h1>
</div>
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
@@ -83,7 +87,7 @@
</a>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<button bit-button buttonType="primary" class="btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "changeMasterPassword" | i18n }}</span>
</button>

View File

@@ -1,4 +1,5 @@
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "jslib-angular/components/change-password.component";
import { ApiService } from "jslib-common/abstractions/api.service";
@@ -6,6 +7,7 @@ import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
@@ -47,7 +49,9 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
private syncService: SyncService,
private apiService: ApiService,
private sendService: SendService,
private organizationService: OrganizationService
private organizationService: OrganizationService,
private keyConnectorService: KeyConnectorService,
private router: Router
) {
super(
i18nService,
@@ -60,6 +64,12 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
);
}
async ngOnInit() {
if (await this.keyConnectorService.getUsesKeyConnector()) {
this.router.navigate(["/settings/security/two-factor"]);
}
}
async rotateEncKeyClicked() {
if (this.rotateEncKey) {
const ciphers = await this.cipherService.getAllDecrypted();

View File

@@ -1,155 +0,0 @@
<ng-container *ngIf="vault">
<p *ngIf="!loaded" class="text-muted">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p>
<ng-container *ngIf="loaded">
<ul class="bwi-ul card-ul carets" *ngIf="organizations && organizations.length">
<li *ngFor="let o of organizations">
<a [routerLink]="['/organizations', o.id]" class="text-body">
<i class="bwi bwi-li bwi-caret-right" aria-hidden="true"></i> {{ o.name }}
<ng-container *ngIf="!o.enabled">
<i
class="bwi bwi-exclamation-triangle text-danger"
title="{{ 'organizationIsDisabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "organizationIsDisabled" | i18n }}</span>
</ng-container>
</a>
</li>
</ul>
<p *ngIf="!organizations || !organizations.length">{{ "noOrganizationsList" | i18n }}</p>
</ng-container>
<a href="#" routerLink="/settings/create-organization" class="btn btn-block btn-outline-primary">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newOrganization" | i18n }}
</a>
</ng-container>
<ng-container *ngIf="!vault">
<div class="page-header d-flex">
<h1>
{{ "organizations" | i18n }}
<small [appApiAction]="actionPromise" #action>
<ng-container *ngIf="action.loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
</small>
</h1>
<a
href="#"
routerLink="/settings/create-organization"
class="btn btn-sm btn-outline-primary ml-auto"
*ngIf="!loaded || (organizations && organizations.length)"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newOrganization" | i18n }}
</a>
</div>
<ng-container *ngIf="!loaded">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<ng-container *ngIf="loaded">
<ng-container *ngIf="!organizations || !organizations.length">
<p>{{ "noOrganizationsList" | i18n }}</p>
<a href="#" routerLink="/settings/create-organization" class="btn btn-outline-primary">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newOrganization" | i18n }}
</a>
</ng-container>
<table class="table table-hover table-list" *ngIf="organizations && organizations.length">
<tbody>
<tr *ngFor="let o of organizations">
<td width="30">
<app-avatar [data]="o.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
</td>
<td>
<a href="#" [routerLink]="['/organizations', o.id]">{{ o.name }}</a>
<ng-container *ngIf="!o.enabled">
<i
class="bwi bwi-exclamation-triangle text-danger"
title="{{ 'organizationIsDisabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "organizationIsDisabled" | i18n }}</span>
</ng-container>
<ng-container *ngIf="showEnrolledStatus(o)">
<i
class="bwi bwi-key"
appStopProp
title="{{ 'enrolledPasswordReset' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "enrolledPasswordReset" | i18n }}</span>
</ng-container>
</td>
<td class="table-list-options">
<div class="dropdown" appListDropdown>
<button
class="btn btn-outline-secondary dropdown-toggle"
type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
appA11yTitle="{{ 'options' | i18n }}"
>
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a
*ngIf="allowEnrollmentChanges(o) && !o.resetPasswordEnrolled"
class="dropdown-item"
href="#"
appStopClick
(click)="toggleResetPasswordEnrollment(o)"
>
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
{{ "enrollPasswordReset" | i18n }}
</a>
<a
*ngIf="allowEnrollmentChanges(o) && o.resetPasswordEnrolled"
class="dropdown-item"
href="#"
appStopClick
(click)="toggleResetPasswordEnrollment(o)"
>
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
{{ "withdrawPasswordReset" | i18n }}
</a>
<ng-container *ngIf="o.useSso && o.identifier">
<a
*ngIf="o.ssoBound; else linkSso"
class="dropdown-item"
href="#"
appStopClick
(click)="unlinkSso(o)"
>
<i class="bwi bwi-fw bwi-chain-broken" aria-hidden="true"></i>
{{ "unlinkSso" | i18n }}
</a>
<ng-template #linkSso>
<app-link-sso [organization]="o"> </app-link-sso>
</ng-template>
</ng-container>
<a class="dropdown-item text-danger" href="#" appStopClick (click)="leave(o)">
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
{{ "leave" | i18n }}
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</ng-container>
</ng-container>

View File

@@ -0,0 +1,69 @@
<div class="tabbed-header d-flex">
<h1>
{{ "paymentMethod" | i18n }}
</h1>
<button
bit-button
buttonType="secondary"
(click)="load()"
class="tw-ml-auto"
*ngIf="firstLoaded"
[disabled]="loading"
>
<i class="bwi bwi-refresh bwi-fw" [ngClass]="{ 'bwi-spin': loading }" aria-hidden="true"></i>
{{ "refresh" | i18n }}
</button>
</div>
<ng-container *ngIf="!firstLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<ng-container *ngIf="billing">
<h2>{{ (isCreditBalance ? "accountCredit" : "accountBalance") | i18n }}</h2>
<p class="text-lg">
<strong>{{ creditOrBalance | currency: "$" }}</strong>
</p>
<p>{{ "creditAppliedDesc" | i18n }}</p>
<button bit-button buttonType="secondary" (click)="addCredit()" *ngIf="!showAddCredit">
{{ "addCredit" | i18n }}
</button>
<app-add-credit
(onAdded)="closeAddCredit(true)"
(onCanceled)="closeAddCredit(false)"
*ngIf="showAddCredit"
>
</app-add-credit>
<h2 class="spaced-header">{{ "paymentMethod" | i18n }}</h2>
<p *ngIf="!paymentSource">{{ "noPaymentMethod" | i18n }}</p>
<ng-container *ngIf="paymentSource">
<p>
<i
class="bwi bwi-fw"
[ngClass]="{
'bwi-credit-card': paymentSource.type === paymentMethodType.Card,
'bwi-bank': paymentSource.type === paymentMethodType.BankAccount,
'bwi-money': paymentSource.type === paymentMethodType.Check,
'bwi-paypal text-primary': paymentSource.type === paymentMethodType.PayPal,
'bwi-apple text-muted': paymentSource.type === paymentMethodType.AppleInApp,
'bwi-google text-muted': paymentSource.type === paymentMethodType.GoogleInApp
}"
></i>
<span *ngIf="paymentSourceInApp">{{ "inAppPurchase" | i18n }}</span>
{{ paymentSource.description }}
</p>
</ng-container>
<button bit-button buttonType="secondary" (click)="changePayment()" *ngIf="!showAdjustPayment">
{{ (paymentSource ? "changePaymentMethod" : "addPaymentMethod") | i18n }}
</button>
<app-adjust-payment
[currentType]="paymentSource != null ? paymentSource.type : null"
(onAdjusted)="closePayment(true)"
(onCanceled)="closePayment(false)"
*ngIf="showAdjustPayment"
>
</app-adjust-payment>
</ng-container>

View File

@@ -1,40 +1,35 @@
import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PaymentMethodType } from "jslib-common/enums/paymentMethodType";
import { TransactionType } from "jslib-common/enums/transactionType";
import { VerifyBankRequest } from "jslib-common/models/request/verifyBankRequest";
import { BillingResponse } from "jslib-common/models/response/billingResponse";
import { BillingPaymentResponse } from "jslib-common/models/response/billingPaymentResponse";
@Component({
selector: "app-user-billing",
templateUrl: "user-billing.component.html",
selector: "app-payment-method",
templateUrl: "payment-method.component.html",
})
export class UserBillingComponent implements OnInit {
export class PaymentMethodComponent implements OnInit {
loading = false;
firstLoaded = false;
showAdjustPayment = false;
showAddCredit = false;
billing: BillingResponse;
billing: BillingPaymentResponse;
paymentMethodType = PaymentMethodType;
transactionType = TransactionType;
organizationId: string;
verifyAmount1: number;
verifyAmount2: number;
verifyBankPromise: Promise<any>;
constructor(
protected apiService: ApiService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
private logService: LogService
private router: Router
) {}
async ngOnInit() {
if (this.platformUtilsService.isSelfHost()) {
this.router.navigate(["/settings/subscription"]);
}
await this.load();
this.firstLoaded = true;
}
@@ -44,39 +39,10 @@ export class UserBillingComponent implements OnInit {
return;
}
this.loading = true;
if (this.organizationId != null) {
this.billing = await this.apiService.getOrganizationBilling(this.organizationId);
} else {
this.billing = await this.apiService.getUserBilling();
}
this.billing = await this.apiService.getUserBillingPayment();
this.loading = false;
}
async verifyBank() {
if (this.loading) {
return;
}
try {
const request = new VerifyBankRequest();
request.amount1 = this.verifyAmount1;
request.amount2 = this.verifyAmount2;
this.verifyBankPromise = this.apiService.postOrganizationVerifyBank(
this.organizationId,
request
);
await this.verifyBankPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("verifiedBankAccount")
);
this.load();
} catch (e) {
this.logService.error(e);
}
}
addCredit() {
if (this.paymentSourceInApp) {
this.platformUtilsService.showDialog(
@@ -138,12 +104,4 @@ export class UserBillingComponent implements OnInit {
this.paymentSource.type === PaymentMethodType.GoogleInApp)
);
}
get invoices() {
return this.billing != null ? this.billing.invoices : null;
}
get transactions() {
return this.billing != null ? this.billing.transactions : null;
}
}

View File

@@ -1,7 +1,7 @@
<div class="page-header">
<h1>{{ "options" | i18n }}</h1>
<h1>{{ "preferences" | i18n }}</h1>
</div>
<p>{{ "optionsDesc" | i18n }}</p>
<p>{{ "preferencesDesc" | i18n }}</p>
<form (ngSubmit)="submit()" ngNativeValidate>
<div class="row">
<div class="col-6">

View File

@@ -10,10 +10,10 @@ import { ThemeType } from "jslib-common/enums/themeType";
import { Utils } from "jslib-common/misc/utils";
@Component({
selector: "app-options",
templateUrl: "options.component.html",
selector: "app-preferences",
templateUrl: "preferences.component.html",
})
export class OptionsComponent implements OnInit {
export class PreferencesComponent implements OnInit {
vaultTimeoutAction = "lock";
disableIcons: boolean;
enableGravatars: boolean;
@@ -107,7 +107,11 @@ export class OptionsComponent implements OnInit {
if (this.locale !== this.startingLocale) {
window.location.reload();
} else {
this.platformUtilsService.showToast("success", null, this.i18nService.t("optionsUpdated"));
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("preferencesUpdated")
);
}
}

View File

@@ -1,15 +1,18 @@
<div class="page-header">
<div *ngIf="selfHosted" class="page-header">
<h1>{{ "subscription" | i18n }}</h1>
</div>
<div *ngIf="!selfHosted" class="tabbed-header">
<h1>{{ "goPremium" | i18n }}</h1>
</div>
<app-callout
<bit-callout
type="info"
*ngIf="canAccessPremium"
title="{{ 'youHavePremiumAccess' | i18n }}"
icon="bwi bwi-star-f"
>
{{ "alreadyPremiumFromOrg" | i18n }}
</app-callout>
<app-callout type="success">
</bit-callout>
<bit-callout type="success">
<p>{{ "premiumUpgradeUnlockFeatures" | i18n }}</p>
<ul class="bwi-ul">
<li>
@@ -45,15 +48,16 @@
{{ "premiumPrice" | i18n: (premiumPrice | currency: "$") }}
</p>
<a
bit-button
href="https://vault.bitwarden.com/#/settings/premium"
target="_blank"
rel="noopener"
class="btn btn-outline-secondary"
buttonType="secondary"
*ngIf="selfHosted"
>
{{ "purchasePremium" | i18n }}
</a>
</app-callout>
</bit-callout>
<ng-container *ngIf="selfHosted">
<p>{{ "uploadLicenseFilePremium" | i18n }}</p>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
@@ -64,7 +68,13 @@
"licenseFileDesc" | i18n: "bitwarden_premium_license.json"
}}</small>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<button
bit-button
buttonType="primary"
type="submit"
class="btn-submit"
[disabled]="form.loading"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "submit" | i18n }}</span>
</button>
@@ -115,7 +125,13 @@
</p>
</div>
<small class="text-muted font-italic">{{ "paymentChargedAnnually" | i18n }}</small>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<button
bit-button
buttonType="primary"
type="submit"
class="btn-submit"
[disabled]="form.loading"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "submit" | i18n }}</span>
</button>

View File

@@ -47,7 +47,7 @@ export class PremiumComponent implements OnInit {
this.canAccessPremium = await this.stateService.getCanAccessPremium();
const premium = await this.tokenService.getPremium();
if (premium) {
this.router.navigate(["/settings/subscription"]);
this.router.navigate(["/settings/subscription/user-subscription"]);
return;
}
}
@@ -119,7 +119,7 @@ export class PremiumComponent implements OnInit {
await this.syncService.fullSync(true);
this.platformUtilsService.showToast("success", null, this.i18nService.t("premiumUpdated"));
this.messagingService.send("purchasedPremium");
this.router.navigate(["/settings/subscription"]);
this.router.navigate(["/settings/subscription/user-subscription"]);
}
get additionalStorageTotal(): number {

View File

@@ -0,0 +1,18 @@
<app-change-kdf *ngIf="showChangeKdf"></app-change-kdf>
<div
[ngClass]="{ 'tabbed-header': !showChangeKdf, 'secondary-header': showChangeKdf }"
class="border-0 mb-0"
>
<h1>{{ "apiKey" | i18n }}</h1>
</div>
<p>
{{ "userApiKeyDesc" | i18n }}
</p>
<button bit-button buttonType="secondary" (click)="viewUserApiKey()">
{{ "viewApiKey" | i18n }}
</button>
<button bit-button buttonType="secondary" (click)="rotateUserApiKey()">
{{ "rotateApiKey" | i18n }}
</button>
<ng-template #viewUserApiKeyTemplate></ng-template>
<ng-template #rotateUserApiKeyTemplate></ng-template>

View File

@@ -0,0 +1,61 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ModalService } from "jslib-angular/services/modal.service";
import { ApiService } from "jslib-common/abstractions/api.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { ApiKeyComponent } from "./api-key.component";
@Component({
selector: "app-security-keys",
templateUrl: "security-keys.component.html",
})
export class SecurityKeysComponent implements OnInit {
@ViewChild("viewUserApiKeyTemplate", { read: ViewContainerRef, static: true })
viewUserApiKeyModalRef: ViewContainerRef;
@ViewChild("rotateUserApiKeyTemplate", { read: ViewContainerRef, static: true })
rotateUserApiKeyModalRef: ViewContainerRef;
showChangeKdf = true;
constructor(
private keyConnectorService: KeyConnectorService,
private stateService: StateService,
private modalService: ModalService,
private apiService: ApiService
) {}
async ngOnInit() {
this.showChangeKdf = !(await this.keyConnectorService.getUsesKeyConnector());
}
async viewUserApiKey() {
const entityId = await this.stateService.getUserId();
await this.modalService.openViewRef(ApiKeyComponent, this.viewUserApiKeyModalRef, (comp) => {
comp.keyType = "user";
comp.entityId = entityId;
comp.postKey = this.apiService.postUserApiKey.bind(this.apiService);
comp.scope = "api";
comp.grantType = "client_credentials";
comp.apiKeyTitle = "apiKey";
comp.apiKeyWarning = "userApiKeyWarning";
comp.apiKeyDescription = "userApiKeyDesc";
});
}
async rotateUserApiKey() {
const entityId = await this.stateService.getUserId();
await this.modalService.openViewRef(ApiKeyComponent, this.rotateUserApiKeyModalRef, (comp) => {
comp.keyType = "user";
comp.isRotation = true;
comp.entityId = entityId;
comp.postKey = this.apiService.postUserRotateApiKey.bind(this.apiService);
comp.scope = "api";
comp.grantType = "client_credentials";
comp.apiKeyTitle = "apiKey";
comp.apiKeyWarning = "userApiKeyWarning";
comp.apiKeyDescription = "apiKeyRotateDesc";
});
}
}

View File

@@ -0,0 +1,39 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { ChangePasswordComponent } from "./change-password.component";
import { SecurityKeysComponent } from "./security-keys.component";
import { SecurityComponent } from "./security.component";
import { TwoFactorSetupComponent } from "./two-factor-setup.component";
const routes: Routes = [
{
path: "",
component: SecurityComponent,
data: { titleId: "security" },
children: [
{ path: "", pathMatch: "full", redirectTo: "change-password" },
{
path: "change-password",
component: ChangePasswordComponent,
data: { titleId: "masterPassword" },
},
{
path: "two-factor",
component: TwoFactorSetupComponent,
data: { titleId: "twoStepLogin" },
},
{
path: "security-keys",
component: SecurityKeysComponent,
data: { titleId: "keys" },
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class SecurityRoutingModule {}

View File

@@ -0,0 +1,22 @@
<div class="tabbed-nav d-flex flex-column">
<ul class="nav nav-tabs">
<ng-container *ngIf="showChangePassword">
<li class="nav-item">
<a class="nav-link" routerLink="change-password" routerLinkActive="active">
{{ "masterPassword" | i18n }}
</a>
</li>
</ng-container>
<li class="nav-item">
<a class="nav-link" routerLink="two-factor" routerLinkActive="active">
{{ "twoStepLogin" | i18n }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="security-keys" routerLinkActive="active">
{{ "keys" | i18n }}
</a>
</li>
</ul>
</div>
<router-outlet></router-outlet>

View File

@@ -0,0 +1,17 @@
import { Component } from "@angular/core";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
@Component({
selector: "app-security",
templateUrl: "security.component.html",
})
export class SecurityComponent {
showChangePassword = true;
constructor(private keyConnectorService: KeyConnectorService) {}
async ngOnInit() {
this.showChangePassword = !(await this.keyConnectorService.getUsesKeyConnector());
}
}

View File

@@ -2,43 +2,24 @@
<div class="row">
<div class="col-3">
<div class="card">
<div class="card-header">{{ "settings" | i18n }}</div>
<div class="card-header">{{ "accountSettings" | i18n }}</div>
<div class="list-group list-group-flush">
<a routerLink="account" class="list-group-item" routerLinkActive="active">
{{ "myAccount" | i18n }}
</a>
<a routerLink="options" class="list-group-item" routerLinkActive="active">
{{ "options" | i18n }}
<a routerLink="security" class="list-group-item" routerLinkActive="active">
{{ "security" | i18n }}
</a>
<a routerLink="organizations" class="list-group-item" routerLinkActive="active">
{{ "organizations" | i18n }}
<a routerLink="preferences" class="list-group-item" routerLinkActive="active">
{{ "preferences" | i18n }}
</a>
<a
routerLink="subscription"
class="list-group-item"
routerLinkActive="active"
*ngIf="premium"
*ngIf="!hideSubscription"
>
{{ "premiumMembership" | i18n }}
</a>
<a
routerLink="premium"
class="list-group-item"
routerLinkActive="active"
*ngIf="!premium"
>
{{ "goPremium" | i18n }}
</a>
<a
routerLink="billing"
class="list-group-item"
routerLinkActive="active"
*ngIf="!selfHosted"
>
{{ "billing" | i18n }}
</a>
<a routerLink="two-factor" class="list-group-item" routerLinkActive="active">
{{ "twoStepLogin" | i18n }}
{{ "subscription" | i18n }}
</a>
<a routerLink="domain-rules" class="list-group-item" routerLinkActive="active">
{{ "domainRules" | i18n }}

View File

@@ -1,10 +1,13 @@
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { ApiService } from "jslib-common/abstractions/api.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { StateService } from "../../abstractions/state.service";
const BroadcasterSubscriptionId = "SettingsComponent";
@Component({
@@ -15,13 +18,16 @@ export class SettingsComponent implements OnInit, OnDestroy {
premium: boolean;
selfHosted: boolean;
hasFamilySponsorshipAvailable: boolean;
hideSubscription: boolean;
constructor(
private tokenService: TokenService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
private platformUtilsService: PlatformUtilsService,
private organizationService: OrganizationService
private organizationService: OrganizationService,
private stateService: StateService,
private apiService: ApiService
) {}
async ngOnInit() {
@@ -47,5 +53,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
async load() {
this.premium = await this.tokenService.getPremium();
this.hasFamilySponsorshipAvailable = await this.organizationService.canManageSponsorships();
const hasPremiumFromOrg = await this.stateService.getCanAccessPremium();
const billing = await this.apiService.getUserBillingHistory();
this.hideSubscription = !this.premium && hasPremiumFromOrg && billing.hasNoHistory;
}
}

View File

@@ -0,0 +1,45 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { PaymentMethodComponent } from "./payment-method.component";
import { PremiumComponent } from "./premium.component";
import { SubscriptionComponent } from "./subscription.component";
import { UserBillingHistoryComponent } from "./user-billing-history.component";
import { UserSubscriptionComponent } from "./user-subscription.component";
const routes: Routes = [
{
path: "",
component: SubscriptionComponent,
data: { titleId: "subscription" },
children: [
{ path: "", pathMatch: "full", redirectTo: "premium" },
{
path: "user-subscription",
component: UserSubscriptionComponent,
data: { titleId: "premiumMembership" },
},
{
path: "premium",
component: PremiumComponent,
data: { titleId: "goPremium" },
},
{
path: "payment-method",
component: PaymentMethodComponent,
data: { titleId: "paymentMethod" },
},
{
path: "billing-history",
component: UserBillingHistoryComponent,
data: { titleId: "billingHistory" },
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class SubscriptionRoutingModule {}

View File

@@ -0,0 +1,20 @@
<div class="tabbed-nav d-flex flex-column" *ngIf="!selfHosted">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" [routerLink]="subscriptionRoute" routerLinkActive="active">
{{ "subscription" | i18n }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="payment-method" routerLinkActive="active">
{{ "paymentMethod" | i18n }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="billing-history" routerLinkActive="active">
{{ "billingHistory" | i18n }}
</a>
</li>
</ul>
</div>
<router-outlet></router-outlet>

View File

@@ -0,0 +1,27 @@
import { Component } from "@angular/core";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TokenService } from "jslib-common/abstractions/token.service";
@Component({
selector: "app-subscription",
templateUrl: "subscription.component.html",
})
export class SubscriptionComponent {
hasPremium: boolean;
selfHosted: boolean;
constructor(
private tokenService: TokenService,
private platformUtilsService: PlatformUtilsService
) {}
async ngOnInit() {
this.hasPremium = await this.tokenService.getPremium();
this.selfHosted = this.platformUtilsService.isSelfHost();
}
get subscriptionRoute(): string {
return this.hasPremium ? "user-subscription" : "premium";
}
}

View File

@@ -1,14 +1,14 @@
<div class="page-header">
<div class="tabbed-header">
<h1>{{ "twoStepLogin" | i18n }}</h1>
</div>
<p *ngIf="!organizationId">{{ "twoStepLoginDesc" | i18n }}</p>
<p *ngIf="organizationId">{{ "twoStepLoginOrganizationDesc" | i18n }}</p>
<app-callout type="warning" *ngIf="!organizationId">
<bit-callout type="warning" *ngIf="!organizationId">
<p>{{ "twoStepLoginRecoveryWarning" | i18n }}</p>
<button type="button" class="btn btn-outline-secondary" (click)="recoveryCode()">
<button bit-button buttonType="secondary" (click)="recoveryCode()">
{{ "viewRecoveryCode" | i18n }}
</button>
</app-callout>
</bit-callout>
<h2 [ngClass]="{ 'mt-5': !organizationId }">
{{ "providers" | i18n }}
<small *ngIf="loading">
@@ -20,9 +20,9 @@
<span class="sr-only">{{ "loading" | i18n }}</span>
</small>
</h2>
<app-callout type="warning" *ngIf="showPolicyWarning">
<bit-callout type="warning" *ngIf="showPolicyWarning">
{{ "twoStepLoginPolicyUserWarning" | i18n }}
</app-callout>
</bit-callout>
<ul class="list-group list-group-2fa">
<li *ngFor="let p of providers" class="list-group-item d-flex align-items-center">
<div class="logo-2fa d-flex justify-content-center">
@@ -45,8 +45,8 @@
</div>
<div class="ml-auto">
<button
type="button"
class="btn btn-outline-secondary btn-sm"
bit-button
buttonType="secondary"
[disabled]="!canAccessPremium && p.premium"
(click)="manage(p.type)"
>

View File

@@ -0,0 +1,98 @@
<div class="tabbed-header d-flex">
<h1>
{{ "billingHistory" | i18n }}
</h1>
<button
bit-button
buttonType="secondary"
(click)="load()"
class="tw-ml-auto"
*ngIf="firstLoaded"
[disabled]="loading"
>
<i class="bwi bwi-refresh bwi-fw" [ngClass]="{ 'bwi-spin': loading }" aria-hidden="true"></i>
{{ "refresh" | i18n }}
</button>
</div>
<ng-container *ngIf="!firstLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<ng-container *ngIf="billing">
<h2>{{ "invoices" | i18n }}</h2>
<p *ngIf="!invoices || !invoices.length">{{ "noInvoices" | i18n }}</p>
<table class="table mb-2" *ngIf="invoices && invoices.length">
<tbody>
<tr *ngFor="let i of invoices">
<td>{{ i.date | date: "mediumDate" }}</td>
<td>
<a
href="{{ i.pdfUrl }}"
target="_blank"
rel="noopener"
class="mr-2"
appA11yTitle="{{ 'downloadInvoice' | i18n }}"
>
<i class="bwi bwi-file-pdf" aria-hidden="true"></i
></a>
<a href="{{ i.url }}" target="_blank" rel="noopener" title="{{ 'viewInvoice' | i18n }}">
{{ "invoiceNumber" | i18n: i.number }}</a
>
</td>
<td>{{ i.amount | currency: "$" }}</td>
<td>
<span *ngIf="i.paid">
<i class="bwi bwi-check text-success" aria-hidden="true"></i>
{{ "paid" | i18n }}
</span>
<span *ngIf="!i.paid">
<i class="bwi bwi-exclamation-circle text-muted" aria-hidden="true"></i>
{{ "unpaid" | i18n }}
</span>
</td>
</tr>
</tbody>
</table>
<h2 class="spaced-header">{{ "transactions" | i18n }}</h2>
<p *ngIf="!transactions || !transactions.length">{{ "noTransactions" | i18n }}</p>
<table class="table mb-2" *ngIf="transactions && transactions.length">
<tbody>
<tr *ngFor="let t of transactions">
<td>{{ t.createdDate | date: "mediumDate" }}</td>
<td>
<span *ngIf="t.type === transactionType.Charge || t.type === transactionType.Credit">
{{ "chargeNoun" | i18n }}
</span>
<span *ngIf="t.type === transactionType.Refund">{{ "refundNoun" | i18n }}</span>
</td>
<td>
<i
class="bwi bwi-fw"
*ngIf="t.paymentMethodType"
aria-hidden="true"
[ngClass]="{
'bwi-credit-card': t.paymentMethodType === paymentMethodType.Card,
'bwi-bank':
t.paymentMethodType === paymentMethodType.BankAccount ||
t.paymentMethodType === paymentMethodType.WireTransfer,
'bwi-bitcoin text-warning': t.paymentMethodType === paymentMethodType.BitPay,
'bwi-paypal text-primary': t.paymentMethodType === paymentMethodType.PayPal
}"
></i>
{{ t.details }}
</td>
<td
[ngClass]="{ 'text-strike': t.refunded }"
title="{{ (t.refunded ? 'refunded' : '') | i18n }}"
>
{{ t.amount | currency: "$" }}
</td>
</tr>
</tbody>
</table>
<small class="text-muted">* {{ "chargesStatement" | i18n: "BITWARDEN" }}</small>
</ng-container>

View File

@@ -0,0 +1,53 @@
import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PaymentMethodType } from "jslib-common/enums/paymentMethodType";
import { TransactionType } from "jslib-common/enums/transactionType";
import { BillingHistoryResponse } from "jslib-common/models/response/billingHistoryResponse";
@Component({
selector: "app-user-billing",
templateUrl: "user-billing-history.component.html",
})
export class UserBillingHistoryComponent implements OnInit {
loading = false;
firstLoaded = false;
billing: BillingHistoryResponse;
paymentMethodType = PaymentMethodType;
transactionType = TransactionType;
constructor(
protected apiService: ApiService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
private router: Router
) {}
async ngOnInit() {
if (this.platformUtilsService.isSelfHost()) {
this.router.navigate(["/settings/subscription"]);
}
await this.load();
this.firstLoaded = true;
}
async load() {
if (this.loading) {
return;
}
this.loading = true;
this.billing = await this.apiService.getUserBillingHistory();
this.loading = false;
}
get invoices() {
return this.billing != null ? this.billing.invoices : null;
}
get transactions() {
return this.billing != null ? this.billing.transactions : null;
}
}

View File

@@ -1,6 +1,11 @@
<div class="page-header">
<div
[ngClass]="{
'page-header': selfHosted,
'tabbed-header': !selfHosted
}"
>
<h1>
{{ "premiumMembership" | i18n }}
{{ title }}
<small *ngIf="firstLoaded && loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
@@ -20,23 +25,25 @@
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<ng-container *ngIf="sub">
<app-callout
<bit-callout
type="warning"
title="{{ 'canceled' | i18n }}"
*ngIf="subscription && subscription.cancelled"
>
{{ "subscriptionCanceled" | i18n }}</app-callout
{{ "subscriptionCanceled" | i18n }}</bit-callout
>
<app-callout
<bit-callout
type="warning"
title="{{ 'pendingCancellation' | i18n }}"
*ngIf="subscriptionMarkedForCancel"
>
<p>{{ "subscriptionPendingCanceled" | i18n }}</p>
<button
#reinstateBtn
bit-button
type="button"
class="btn btn-outline-secondary btn-submit"
buttonType="secondary"
#reinstateBtn
class="btn-submit"
(click)="reinstate()"
[appApiAction]="reinstatePromise"
[disabled]="reinstateBtn.loading"
@@ -44,7 +51,7 @@
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "reinstateSubscription" | i18n }}</span>
</button>
</app-callout>
</bit-callout>
<dl *ngIf="selfHosted">
<dt>{{ "expiration" | i18n }}</dt>
<dd *ngIf="sub.expiration">{{ sub.expiration | date: "mediumDate" }}</dd>
@@ -56,7 +63,7 @@
<dt>{{ "status" | i18n }}</dt>
<dd>
<span class="text-capitalize">{{ (subscription && subscription.status) || "-" }}</span>
<span class="badge badge-warning" *ngIf="subscriptionMarkedForCancel">{{
<span bit-badge badgeType="warning" *ngIf="subscriptionMarkedForCancel">{{
"pendingCancellation" | i18n
}}</span>
</dd>
@@ -89,14 +96,15 @@
</div>
<ng-container *ngIf="selfHosted">
<div>
<button type="button" class="btn btn-outline-secondary" (click)="updateLicense()">
<button type="button" bit-button buttonType="secondary" (click)="updateLicense()">
{{ "updateLicense" | i18n }}
</button>
<a
bit-button
buttonType="secondary"
href="https://vault.bitwarden.com/#/settings/subscription"
target="_blank"
rel="noopener"
class="btn btn-outline-secondary"
>
{{ "manageSubscription" | i18n }}
</a>
@@ -123,17 +131,20 @@
<ng-container *ngIf="!selfHosted">
<div class="d-flex">
<button
bit-button
type="button"
class="btn btn-outline-secondary"
buttonType="secondary"
(click)="downloadLicense()"
*ngIf="!subscription || !subscription.cancelled"
>
{{ "downloadLicense" | i18n }}
</button>
<button
bit-button
#cancelBtn
type="button"
class="btn btn-outline-danger btn-submit ml-auto"
buttonType="danger"
class="btn-submit tw-ml-auto"
(click)="cancel()"
[appApiAction]="cancelPromise"
[disabled]="cancelBtn.loading"
@@ -160,12 +171,14 @@
<ng-container *ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
<div class="mt-3">
<div class="d-flex" *ngIf="!showAdjustStorage">
<button type="button" class="btn btn-outline-secondary" (click)="adjustStorage(true)">
<button bit-button type="button" buttonType="secondary" (click)="adjustStorage(true)">
{{ "addStorage" | i18n }}
</button>
<button
bit-button
type="button"
class="ml-1 btn btn-outline-secondary"
buttonType="secondary"
class="tw-ml-1"
(click)="adjustStorage(false)"
>
{{ "removeStorage" | i18n }}

View File

@@ -49,7 +49,7 @@ export class UserSubscriptionComponent implements OnInit {
this.loading = true;
this.sub = await this.apiService.getUserSubscription();
} else {
this.router.navigate(["/settings/premium"]);
this.router.navigate(["/settings/subscription/premium"]);
return;
}
@@ -210,4 +210,8 @@ export class UserSubscriptionComponent implements OnInit {
get usingInAppPurchase() {
return this.sub != null ? this.sub.usingInAppPurchase : false;
}
get title(): string {
return this.i18nService.t(this.selfHosted ? "subscription" : "premiumMembership");
}
}

Some files were not shown because too many files have changed in this diff Show More