1
0
mirror of https://github.com/bitwarden/web synced 2026-01-04 09:33:32 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
snyk-bot
63e24770e1 fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-BRAINTREESANITIZEURL-2339882
2022-04-08 20:10:38 +00:00
71 changed files with 1237 additions and 1927 deletions

View File

@@ -49,15 +49,6 @@ jobs:
- name: Install dependencies
run: npm ci
- name: TEST STEP
run: ls -alh
- name: TEST STEP JSLIB
run: ls -alh jslib
- name: TEST STEP MODULES
run: ls -alh .git/modules
- name: Run linter
run: npm run lint

View File

@@ -188,7 +188,7 @@ jobs:
git config --global url."https://".insteadOf ssh://
- name: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
with:
workflow: build.yml
workflow_conclusion: success

View File

@@ -7,7 +7,6 @@ import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { JslibModule } from "jslib-angular/jslib.module";
import { VaultFilterModule } from "src/app/modules/vault-filter/vault-filter.module";
import { OssRoutingModule } from "src/app/oss-routing.module";
import { ServicesModule } from "src/app/services/services.module";
import { WildcardRoutingModule } from "src/app/wildcard-routing.module";
@@ -21,7 +20,6 @@ import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-tim
@NgModule({
imports: [
JslibModule,
VaultFilterModule,
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,

View File

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

View File

@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
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: [AuthGuard],
canActivate: [AuthGuardService],
component: ProvidersComponent,
},
{
@@ -45,7 +45,7 @@ const routes: Routes = [
},
{
path: "",
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
children: [
{
path: "setup",
@@ -54,7 +54,7 @@ const routes: Routes = [
{
path: ":providerId",
component: ProvidersLayoutComponent,
canActivate: [ProviderGuard],
canActivate: [ProviderGuardService],
children: [
{ path: "", pathMatch: "full", redirectTo: "clients" },
{ path: "clients/create", component: CreateOrganizationComponent },
@@ -71,7 +71,7 @@ const routes: Routes = [
{
path: "people",
component: PeopleComponent,
canActivate: [PermissionsGuard],
canActivate: [ProviderTypeGuardService],
data: {
titleId: "people",
permissions: [Permissions.ManageUsers],
@@ -80,7 +80,7 @@ const routes: Routes = [
{
path: "events",
component: EventsComponent,
canActivate: [PermissionsGuard],
canActivate: [ProviderTypeGuardService],
data: {
titleId: "eventLogs",
permissions: [Permissions.AccessEventLogs],
@@ -100,7 +100,7 @@ const routes: Routes = [
{
path: "account",
component: AccountComponent,
canActivate: [PermissionsGuard],
canActivate: [ProviderTypeGuardService],
data: {
titleId: "myProvider",
permissions: [Permissions.ManageProvider],

View File

@@ -10,8 +10,6 @@ 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";
@@ -21,6 +19,8 @@ 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, ProviderGuard, PermissionsGuard],
providers: [WebProviderService, ProviderGuardService, ProviderTypeGuardService],
})
export class ProvidersModule {
constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) {

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 ProviderGuard implements CanActivate {
export class ProviderGuardService implements CanActivate {
constructor(
private router: Router,
private platformUtilsService: PlatformUtilsService,

View File

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

2
jslib

Submodule jslib updated: ad37de9373...3b9ef68f4b

138
package-lock.json generated
View File

@@ -22,7 +22,7 @@
"@bitwarden/jslib-angular": "file:jslib/angular",
"@bitwarden/jslib-common": "file:jslib/common",
"bootstrap": "4.6.0",
"braintree-web-drop-in": "1.30.1",
"braintree-web-drop-in": "^1.33.1",
"browser-hrtime": "^1.1.8",
"core-js": "^3.11.0",
"date-input-polyfill": "^2.14.0",
@@ -139,14 +139,6 @@
"typescript": "4.3.5"
}
},
"jslib/common/node_modules/node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
"engines": {
"node": ">= 6.13.0"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz",
@@ -675,9 +667,9 @@
}
},
"node_modules/@braintree/browser-detection": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/@braintree/browser-detection/-/browser-detection-1.12.0.tgz",
"integrity": "sha512-fmZcaXYkXr9b0J+3HwXLQogIYV+xSS6aBG7LGu0OjLkSD/k62EAu2xLGEDFHUu6siH/I7vvGoC0/Ds3f3RnS6g=="
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@braintree/browser-detection/-/browser-detection-1.12.1.tgz",
"integrity": "sha512-i/54qrax5o/WbJJhsE/7qqKE594/kGhR+xSu/w13rT7Mlr/uITkWDXzxffcKQ6l6FQxK0IG0EfgT6TJpWgZcUQ=="
},
"node_modules/@braintree/class-list": {
"version": "0.2.0",
@@ -700,10 +692,9 @@
"integrity": "sha512-tVpr7U6u6bqeQlHreEjYMNtnHX62vLnNWziY2kQLqkWhvusPuY5DfuGEIPpWqsd+V/a1slyTQaxK6HWTlH6A/Q=="
},
"node_modules/@braintree/sanitize-url": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-5.0.2.tgz",
"integrity": "sha512-NBEJlHWrhQucLhZGHtSxM2loSaNUMajC7KOYJLyfcdW/6goVoff2HoYI3bz8YCDN0wKGbxtUL0gx2dvHpvnWlw==",
"deprecated": "Potential XSS vulnerability patched in v6.0.0."
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.0.tgz",
"integrity": "sha512-mgmE7XBYY/21erpzhexk4Cj1cyTQ9LzvnTxtzM17BJ7ERMNE6W72mQRo0I1Ud8eFJ+RVVIcBNhLFZ3GX4XFz5w=="
},
"node_modules/@braintree/uuid": {
"version": "0.1.0",
@@ -2096,40 +2087,40 @@
}
},
"node_modules/braintree-web": {
"version": "3.78.2",
"resolved": "https://registry.npmjs.org/braintree-web/-/braintree-web-3.78.2.tgz",
"integrity": "sha512-aYKlog3j4BBAgUjRq52gn54/6Kp9zR7PalAzfhych/OArNq9xL0kO+OuQM7V0AxkTyoOHZ1qswIQ9MYPRws/ag==",
"version": "3.85.3",
"resolved": "https://registry.npmjs.org/braintree-web/-/braintree-web-3.85.3.tgz",
"integrity": "sha512-slCnjD/YLFDmiOU0vxL7i4uifjRQV5Cw7dSkhRdXiIT+a8iQ7NxtL5FSomv45wuHqgdilZeQ8iB8guIrn6QgwA==",
"dependencies": {
"@braintree/asset-loader": "0.4.4",
"@braintree/browser-detection": "1.12.0",
"@braintree/browser-detection": "1.12.1",
"@braintree/class-list": "0.2.0",
"@braintree/event-emitter": "0.4.1",
"@braintree/extended-promise": "0.4.1",
"@braintree/iframer": "1.1.0",
"@braintree/sanitize-url": "5.0.2",
"@braintree/sanitize-url": "6.0.0",
"@braintree/uuid": "0.1.0",
"@braintree/wrap-promise": "2.1.0",
"card-validator": "8.1.1",
"credit-card-type": "9.1.0",
"framebus": "5.1.2",
"inject-stylesheet": "4.0.0",
"promise-polyfill": "8.2.0",
"restricted-input": "3.0.3"
"inject-stylesheet": "5.0.0",
"promise-polyfill": "8.2.3",
"restricted-input": "3.0.5"
}
},
"node_modules/braintree-web-drop-in": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/braintree-web-drop-in/-/braintree-web-drop-in-1.30.1.tgz",
"integrity": "sha512-W4sbiHZwrK16RWxBe7iYv986N1TlgO+eBdRdukW0qYNakTWSvgNzB9xk8RNpd1RB3Wt6zb64i4ZMU8A/MW+WAQ==",
"version": "1.33.1",
"resolved": "https://registry.npmjs.org/braintree-web-drop-in/-/braintree-web-drop-in-1.33.1.tgz",
"integrity": "sha512-/gVWpFIGATxVBqBCp7ZTg2vPsH5aZXAASiBVWZ8Tqebntmj0F0KlCtxfvq8k2qMSwKBzhDEgmMp2o0hAcsfM6g==",
"dependencies": {
"@braintree/asset-loader": "0.4.4",
"@braintree/browser-detection": "1.12.0",
"@braintree/browser-detection": "1.12.1",
"@braintree/class-list": "0.2.0",
"@braintree/event-emitter": "0.4.1",
"@braintree/uuid": "0.1.0",
"@braintree/wrap-promise": "2.1.0",
"braintree-web": "3.78.2",
"promise-polyfill": "8.2.0"
"braintree-web": "3.85.3",
"promise-polyfill": "8.2.3"
}
},
"node_modules/browser-hrtime": {
@@ -4851,9 +4842,9 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/inject-stylesheet": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/inject-stylesheet/-/inject-stylesheet-4.0.0.tgz",
"integrity": "sha512-EULSmN+gdAMR4w9kk57HJ1Lz6Xp+9OGgTbxpNV2QSncG+LWlihH1d/Clm8ui6b+LAqmIVcrtWfwDrMEgDiUpjg=="
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/inject-stylesheet/-/inject-stylesheet-5.0.0.tgz",
"integrity": "sha512-GzncrJP8E/pavMQzoO93CXoYCfTttwVm2cX2TyXJdgtVE0cCvWSFCn1/uMsM6ZkEg7LUsOcKuamcLiGWlv2p9A=="
},
"node_modules/internal-slot": {
"version": "1.0.3",
@@ -6971,9 +6962,9 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"node_modules/promise-polyfill": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.0.tgz",
"integrity": "sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g=="
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.3.tgz",
"integrity": "sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg=="
},
"node_modules/proxy-addr": {
"version": "2.0.7",
@@ -7289,11 +7280,11 @@
}
},
"node_modules/restricted-input": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/restricted-input/-/restricted-input-3.0.3.tgz",
"integrity": "sha512-n+h80svtx0yHd6kr7b1nGJ87MSJKiX2lRD9BOOzgC7dzx+57qymdbEnZjdbv5po+4iW5nsKZx984mliALLv2eQ==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/restricted-input/-/restricted-input-3.0.5.tgz",
"integrity": "sha512-lUuXZ3wUnHURRarj5/0C8vomWIfWJO+p7T6RYwB46v7Oyuyr3yyupU+i7SjqUv4S6RAeAAZt1C/QCLJ9xhQBow==",
"dependencies": {
"@braintree/browser-detection": "^1.10.0"
"@braintree/browser-detection": "^1.12.1"
}
},
"node_modules/retry": {
@@ -9665,13 +9656,6 @@
"tldjs": "^2.3.1",
"typescript": "4.3.5",
"zxcvbn": "^4.4.2"
},
"dependencies": {
"node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
}
}
},
"@braintree/asset-loader": {
@@ -9683,9 +9667,9 @@
}
},
"@braintree/browser-detection": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/@braintree/browser-detection/-/browser-detection-1.12.0.tgz",
"integrity": "sha512-fmZcaXYkXr9b0J+3HwXLQogIYV+xSS6aBG7LGu0OjLkSD/k62EAu2xLGEDFHUu6siH/I7vvGoC0/Ds3f3RnS6g=="
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@braintree/browser-detection/-/browser-detection-1.12.1.tgz",
"integrity": "sha512-i/54qrax5o/WbJJhsE/7qqKE594/kGhR+xSu/w13rT7Mlr/uITkWDXzxffcKQ6l6FQxK0IG0EfgT6TJpWgZcUQ=="
},
"@braintree/class-list": {
"version": "0.2.0",
@@ -9708,9 +9692,9 @@
"integrity": "sha512-tVpr7U6u6bqeQlHreEjYMNtnHX62vLnNWziY2kQLqkWhvusPuY5DfuGEIPpWqsd+V/a1slyTQaxK6HWTlH6A/Q=="
},
"@braintree/sanitize-url": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-5.0.2.tgz",
"integrity": "sha512-NBEJlHWrhQucLhZGHtSxM2loSaNUMajC7KOYJLyfcdW/6goVoff2HoYI3bz8YCDN0wKGbxtUL0gx2dvHpvnWlw=="
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.0.tgz",
"integrity": "sha512-mgmE7XBYY/21erpzhexk4Cj1cyTQ9LzvnTxtzM17BJ7ERMNE6W72mQRo0I1Ud8eFJ+RVVIcBNhLFZ3GX4XFz5w=="
},
"@braintree/uuid": {
"version": "0.1.0",
@@ -10819,40 +10803,40 @@
}
},
"braintree-web": {
"version": "3.78.2",
"resolved": "https://registry.npmjs.org/braintree-web/-/braintree-web-3.78.2.tgz",
"integrity": "sha512-aYKlog3j4BBAgUjRq52gn54/6Kp9zR7PalAzfhych/OArNq9xL0kO+OuQM7V0AxkTyoOHZ1qswIQ9MYPRws/ag==",
"version": "3.85.3",
"resolved": "https://registry.npmjs.org/braintree-web/-/braintree-web-3.85.3.tgz",
"integrity": "sha512-slCnjD/YLFDmiOU0vxL7i4uifjRQV5Cw7dSkhRdXiIT+a8iQ7NxtL5FSomv45wuHqgdilZeQ8iB8guIrn6QgwA==",
"requires": {
"@braintree/asset-loader": "0.4.4",
"@braintree/browser-detection": "1.12.0",
"@braintree/browser-detection": "1.12.1",
"@braintree/class-list": "0.2.0",
"@braintree/event-emitter": "0.4.1",
"@braintree/extended-promise": "0.4.1",
"@braintree/iframer": "1.1.0",
"@braintree/sanitize-url": "5.0.2",
"@braintree/sanitize-url": "6.0.0",
"@braintree/uuid": "0.1.0",
"@braintree/wrap-promise": "2.1.0",
"card-validator": "8.1.1",
"credit-card-type": "9.1.0",
"framebus": "5.1.2",
"inject-stylesheet": "4.0.0",
"promise-polyfill": "8.2.0",
"restricted-input": "3.0.3"
"inject-stylesheet": "5.0.0",
"promise-polyfill": "8.2.3",
"restricted-input": "3.0.5"
}
},
"braintree-web-drop-in": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/braintree-web-drop-in/-/braintree-web-drop-in-1.30.1.tgz",
"integrity": "sha512-W4sbiHZwrK16RWxBe7iYv986N1TlgO+eBdRdukW0qYNakTWSvgNzB9xk8RNpd1RB3Wt6zb64i4ZMU8A/MW+WAQ==",
"version": "1.33.1",
"resolved": "https://registry.npmjs.org/braintree-web-drop-in/-/braintree-web-drop-in-1.33.1.tgz",
"integrity": "sha512-/gVWpFIGATxVBqBCp7ZTg2vPsH5aZXAASiBVWZ8Tqebntmj0F0KlCtxfvq8k2qMSwKBzhDEgmMp2o0hAcsfM6g==",
"requires": {
"@braintree/asset-loader": "0.4.4",
"@braintree/browser-detection": "1.12.0",
"@braintree/browser-detection": "1.12.1",
"@braintree/class-list": "0.2.0",
"@braintree/event-emitter": "0.4.1",
"@braintree/uuid": "0.1.0",
"@braintree/wrap-promise": "2.1.0",
"braintree-web": "3.78.2",
"promise-polyfill": "8.2.0"
"braintree-web": "3.85.3",
"promise-polyfill": "8.2.3"
}
},
"browser-hrtime": {
@@ -12880,9 +12864,9 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"inject-stylesheet": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/inject-stylesheet/-/inject-stylesheet-4.0.0.tgz",
"integrity": "sha512-EULSmN+gdAMR4w9kk57HJ1Lz6Xp+9OGgTbxpNV2QSncG+LWlihH1d/Clm8ui6b+LAqmIVcrtWfwDrMEgDiUpjg=="
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/inject-stylesheet/-/inject-stylesheet-5.0.0.tgz",
"integrity": "sha512-GzncrJP8E/pavMQzoO93CXoYCfTttwVm2cX2TyXJdgtVE0cCvWSFCn1/uMsM6ZkEg7LUsOcKuamcLiGWlv2p9A=="
},
"internal-slot": {
"version": "1.0.3",
@@ -14391,9 +14375,9 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"promise-polyfill": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.0.tgz",
"integrity": "sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g=="
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.3.tgz",
"integrity": "sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg=="
},
"proxy-addr": {
"version": "2.0.7",
@@ -14626,11 +14610,11 @@
}
},
"restricted-input": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/restricted-input/-/restricted-input-3.0.3.tgz",
"integrity": "sha512-n+h80svtx0yHd6kr7b1nGJ87MSJKiX2lRD9BOOzgC7dzx+57qymdbEnZjdbv5po+4iW5nsKZx984mliALLv2eQ==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/restricted-input/-/restricted-input-3.0.5.tgz",
"integrity": "sha512-lUuXZ3wUnHURRarj5/0C8vomWIfWJO+p7T6RYwB46v7Oyuyr3yyupU+i7SjqUv4S6RAeAAZt1C/QCLJ9xhQBow==",
"requires": {
"@braintree/browser-detection": "^1.10.0"
"@braintree/browser-detection": "^1.12.1"
}
},
"retry": {

View File

@@ -4,7 +4,7 @@
"license": "GPL-3.0",
"repository": "https://github.com/bitwarden/web",
"scripts": {
"sub:init": "rm -rf jslib; git submodule sync --recursive; git -c protocol.version=2 submodule update --init --force --depth=1 --recursive",
"sub:init": "git submodule update --init --recursive",
"sub:update": "git submodule update --remote",
"sub:pull": "git submodule foreach git pull origin master",
"preinstall": "npm run sub:init",
@@ -90,7 +90,7 @@
"@bitwarden/jslib-angular": "file:jslib/angular",
"@bitwarden/jslib-common": "file:jslib/common",
"bootstrap": "4.6.0",
"braintree-web-drop-in": "1.30.1",
"braintree-web-drop-in": "1.33.1",
"browser-hrtime": "^1.1.8",
"core-js": "^3.11.0",
"date-input-polyfill": "^2.14.0",

View File

@@ -6,7 +6,7 @@
<div class="collapse navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/vault">{{ "vaults" | i18n }}</a>
<a class="nav-link" routerLink="/vault">{{ "myVault" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/sends">{{ "send" | i18n }}</a>
@@ -27,10 +27,8 @@
<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 class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/settings">{{ "settings" | i18n }}</a>
</li>
</ul>
</div>

View File

@@ -1,18 +1,12 @@
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",
@@ -22,16 +16,13 @@ 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 organizationService: OrganizationService,
private i18nService: I18nService
private syncService: SyncService
) {
this.selfHosted = this.platformUtilsService.isSelfHost();
}
@@ -43,16 +34,11 @@ export class NavbarComponent implements OnInit {
this.name = this.email;
}
// Ensure providers and organizations are loaded
// Ensure provides 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,3 +1,4 @@
<app-navbar></app-navbar>
<div class="org-nav" *ngIf="organization">
<div class="container d-flex">
<div class="d-flex flex-column">
@@ -26,7 +27,7 @@
</div>
</div>
</div>
<ul class="nav nav-tabs">
<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>
@@ -45,7 +46,7 @@
{{ "tools" | i18n }}
</a>
</li>
<li class="nav-item" *ngIf="showSettingsTab">
<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 }}
@@ -56,3 +57,4 @@
</div>
</div>
<router-outlet></router-outlet>
<app-footer></app-footer>

View File

@@ -5,8 +5,6 @@ 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({
@@ -27,7 +25,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
ngOnInit() {
document.body.classList.remove("layout_frontend");
this.route.params.subscribe(async (params: any) => {
this.route.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
await this.load();
});
@@ -50,16 +48,23 @@ 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 NavigationPermissionsService.canAccessManage(this.organization);
return (
this.organization.canManageUsers ||
this.organization.canViewAllCollections ||
this.organization.canViewAssignedCollections ||
this.organization.canManageGroups ||
this.organization.canManagePolicies ||
this.organization.canAccessEventLogs
);
}
get showToolsTab(): boolean {
return NavigationPermissionsService.canAccessTools(this.organization);
}
get showSettingsTab(): boolean {
return NavigationPermissionsService.canAccessSettings(this.organization);
return this.organization.canAccessImportExport || this.organization.canAccessReports;
}
get toolsRoute(): string {

View File

@@ -1,55 +0,0 @@
<ng-container *ngIf="show">
<div class="collapsable-row">
<i
class="bwi"
title="{{ 'toggleCollapse' | i18n }}"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed(collectionsGrouping),
'bwi-angle-down': !isCollapsed(collectionsGrouping)
}"
(click)="toggleCollapse(collectionsGrouping)"
appStopProp
></i>
<h3>&nbsp;{{ collectionsGrouping.name | i18n }}</h3>
</div>
<ul *ngIf="!isCollapsed(collectionsGrouping)" class="bwi-ul card-ul">
<ng-template #recursiveCollections let-collections>
<li
*ngFor="let c of collections"
[ngClass]="{
active: c.node.id === activeFilter.selectedCollectionId
}"
>
<i
*ngIf="c.children.length"
class="bwi-li bwi"
title="{{ 'toggleCollapse' | i18n }}"
[ngClass]="{
'bwi-angle-right': isCollapsed(c.node),
'bwi-angle-down': !isCollapsed(c.node)
}"
(click)="collapse(c.node)"
></i>
<a href="#" class="text-break" appStopClick (click)="applyFilter(c.node)">
<i
*ngIf="c.children.length === 0"
class="bwi bwi-li bwi-collection"
aria-hidden="true"
></i
>{{ c.node.name }}
</a>
<ul class="bwi-ul card-ul carets" *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

@@ -1,9 +0,0 @@
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

@@ -1,71 +0,0 @@
<ng-container *ngIf="!hide && !activeFilter.selectedOrganizationId">
<div class="collapsable-row">
<i
class="bwi bwi-fw"
title="{{ 'toggleCollapse' | i18n }}"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed(foldersGrouping),
'bwi-angle-down': !isCollapsed(foldersGrouping)
}"
(click)="toggleCollapse(foldersGrouping)"
appStopProp
></i>
<h3 class="filter-title">
{{ "folders" | i18n }}
</h3>
<a
href="#"
class="text-muted ml-auto"
appStopClick
(click)="addFolder()"
appA11yTitle="{{ 'addFolder' | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</a>
</div>
<ul *ngIf="!isCollapsed(foldersGrouping)" class="bwi-ul card-ul">
<ng-template #recursiveFolders let-folders>
<li
*ngFor="let f of folders"
[ngClass]="{
active: f.node.id === activeFilter.selectedFolderId && activeFilter.selectedFolder
}"
>
<div class="d-flex">
<i
*ngIf="f.children.length"
class="bwi-li bwi"
title="{{ 'toggleCollapse' | i18n }}"
[ngClass]="{
'bwi-angle-right': isCollapsed(f.node),
'bwi-angle-down': !isCollapsed(f.node)
}"
(click)="collapse(f.node)"
></i>
<a href="#" class="text-break" appStopClick (click)="applyFilter(f.node)">
<i *ngIf="f.children.length === 0" class="bwi bwi-li bwi-folder" aria-hidden="true"></i
>{{ f.node.name }}
</a>
<a
href="#"
class="text-muted ml-auto show-active"
appStopClick
(click)="editFolder(f.node)"
appA11yTitle="{{ 'editFolder' | i18n }}"
*ngIf="f.node.id"
>
<i class="bwi bwi-pencil bwi-fw" aria-hidden="true"></i>
</a>
</div>
<ul class="bwi-ul" *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

@@ -1,9 +0,0 @@
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

@@ -1,175 +0,0 @@
<ng-container *ngIf="!hide">
<ng-container [ngSwitch]="displayMode">
<ng-container *ngSwitchCase="'noOrganizations'">
<div class="vault-filter-option active">
<span>
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
&nbsp;{{ "myVault" | i18n }}
</span>
</div>
<a
href="#"
routerLink="/settings/create-organization"
class="text-muted ml-auto"
appStopClick
appA11yTitle="{{ 'addOrganization' | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
<span>{{ "newOrganization" | i18n }}</span>
</a>
</ng-container>
<ng-container *ngSwitchCase="'personalOwnershipPolicy'">
<div class="collapsable-row" [ngClass]="{ active: !hasActiveFilter }">
<i
class="bwi bwi-fw"
title="{{ 'toggleCollapse' | i18n }}"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed
}"
(click)="toggleCollapse()"
appStopProp
></i>
<a href="#" (click)="clearFilter()">
<span class="org-filter-heading" [ngClass]="{ active: !hasActiveFilter }"
>&nbsp;{{ organizationGrouping.name | i18n }}</span
>
</a>
<a
href="#"
routerLink="/settings/create-organization"
class="text-muted ml-auto"
appStopClick
appA11yTitle="{{ 'addOrganization' | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</a>
</div>
<ul *ngIf="!isCollapsed" class="bwi-ul card-ul">
<li
class="vault-filter-option"
*ngFor="let organization of organizations"
[ngClass]="{ active: organization.id === activeFilter.selectedOrganizationId }"
>
<div class="d-flex">
<a href="#" appStopClick appBlurClick (click)="applyOrganizationFilter(organization)">
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
&nbsp;{{ organization.name }}
</a>
<!-- TODO: Remove once jslib is updated and has new menu component -->
<a
href="#"
class="text-muted ml-auto show-active"
appStopClick
appA11yTitle="{{ 'addFolder' | i18n }}"
href="#"
id="nav-profile"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<div class="dropdown-menu dropdown-menu-left" aria-labelledby="nav-profile">
<app-organization-options [organization]="organization"></app-organization-options>
</div>
<i class="bwi bwi-ellipsis-v bwi-fw" aria-hidden="true" *ngIf="organization.id"></i>
</a>
<!-- Using new dropdow menu component from component library -->
<!-- <button [bitMenuTriggerFor]="orgMenu" class="text-muted ml-auto show-active">
<i class="bwi bwi-ellipsis-v" aria-hidden="true"></i>
</button>
<bit-menu class="dropdown-menu" #orgMenu>
<app-organization-options [organization]="organization"></app-organization-options>
</bit-menu> -->
</div>
</li>
</ul>
</ng-container>
<ng-container *ngSwitchCase="'singleOrganizationAndPersonalOwnershipPolicies'">
<ul class="bwi-ul card-ul">
<li class="vault-filter-option active">
<a href="#">
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
&nbsp;{{ organizations[0].name }}
</a>
</li>
</ul>
</ng-container>
<ng-container *ngSwitchCase="'organizationMember'">
<div class="collapsable-row" [ngClass]="{ active: !hasActiveFilter }">
<i
class="bwi bwi-fw"
title="{{ 'toggleCollapse' | i18n }}"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed
}"
(click)="toggleCollapse()"
appStopProp
></i>
<a href="#" (click)="clearFilter()">
<span class="org-filter-heading" [ngClass]="{ active: !hasActiveFilter }"
>&nbsp;{{ organizationGrouping.name | i18n }}</span
>
</a>
<a
href="#"
routerLink="/settings/create-organization"
class="text-muted ml-auto"
appStopClick
appA11yTitle="{{ 'addOrganization' | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</a>
</div>
<ul *ngIf="!isCollapsed" class="bwi-ul card-ul no-margin">
<li class="vault-filter-option" [ngClass]="{ active: activeFilter.myVaultOnly }">
<a href="#" (click)="applyMyVaultFilter()">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
&nbsp;{{ "myVault" | i18n }}
</a>
</li>
<li
class="vault-filter-option"
*ngFor="let organization of organizations"
[ngClass]="{ active: organization.id === activeFilter.selectedOrganizationId }"
>
<div class="d-flex">
<a href="#" appStopClick appBlurClick (click)="applyOrganizationFilter(organization)">
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
&nbsp;{{ organization.name }}
</a>
<!-- TODO: Remove once jslib is updated and has new menu component -->
<a
href="#"
class="text-muted ml-auto show-active"
appStopClick
appA11yTitle="{{ 'addFolder' | i18n }}"
href="#"
id="nav-profile"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<div class="dropdown-menu dropdown-menu-left" aria-labelledby="nav-profile">
<app-organization-options [organization]="organization"></app-organization-options>
</div>
<i class="bwi bwi-ellipsis-v bwi-fw" aria-hidden="true" *ngIf="organization.id"></i>
</a>
<!-- Using new dropdow menu component from component library -->
<!-- <button [bitMenuTriggerFor]="orgMenu" class="org-options ml-auto show-active">
<i class="bwi bwi-ellipsis-v" aria-hidden="true"></i>
</button>
<bit-menu class="dropdown-menu" #orgMenu>
<app-organization-options [organization]="organization"></app-organization-options>
</bit-menu> -->
</div>
</li>
</ul>
</ng-container>
</ng-container>
<hr />
</ng-container>

View File

@@ -1,11 +0,0 @@
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

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

View File

@@ -1,180 +0,0 @@
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 { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { PolicyType } from "jslib-common/enums/policyType";
import { Utils } from "jslib-common/misc/utils";
import { Organization } from "jslib-common/models/domain/organization";
import { Policy } from "jslib-common/models/domain/policy";
import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
@Component({
selector: "app-organization-options",
templateUrl: "organization-options.component.html",
})
export class OrganizationOptionsComponent {
actionPromise: Promise<any>;
policies: Policy[];
loaded = false;
@Input() organization: Organization;
constructor(
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private apiService: ApiService,
private syncService: SyncService,
private cryptoService: CryptoService,
private policyService: PolicyService,
private logService: LogService
) {}
async ngOnInit() {
await this.syncService.fullSync(true);
await this.load();
}
async load() {
this.policies = await this.policyService.getAll(PolicyType.ResetPassword);
this.loaded = true;
}
allowEnrollmentChanges(org: Organization): boolean {
if (org.usePolicies && org.useResetPassword && org.hasPublicAndPrivateKeys) {
const policy = this.policies.find((p) => p.organizationId === org.id);
if (policy != null && policy.enabled) {
return org.resetPasswordEnrolled && policy.data.autoEnrollEnabled ? false : true;
}
}
return false;
}
showEnrolledStatus(org: Organization): boolean {
return (
org.useResetPassword &&
org.resetPasswordEnrolled &&
this.policies.some((p) => p.organizationId === org.id && p.enabled)
);
}
async unlinkSso(org: Organization) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("unlinkSsoConfirmation"),
org.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
try {
this.actionPromise = this.apiService.deleteSsoUser(org.id).then(() => {
return this.syncService.fullSync(true);
});
await this.actionPromise;
this.platformUtilsService.showToast("success", null, "Unlinked SSO");
await this.load();
} catch (e) {
this.logService.error(e);
}
}
async leave(org: Organization) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("leaveOrganizationConfirmation"),
org.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
try {
this.actionPromise = this.apiService.postLeaveOrganization(org.id).then(() => {
return this.syncService.fullSync(true);
});
await this.actionPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization"));
await this.load();
} catch (e) {
this.logService.error(e);
}
}
async toggleResetPasswordEnrollment(org: Organization) {
// Set variables
let keyString: string = null;
let toastStringRef = "withdrawPasswordResetSuccess";
// Enrolling
if (!org.resetPasswordEnrolled) {
// Alert user about enrollment
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("resetPasswordEnrollmentWarning"),
null,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return;
}
// Retrieve Public Key
this.actionPromise = this.apiService
.getOrganizationKeys(org.id)
.then(async (response) => {
if (response == null) {
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
}
const publicKey = Utils.fromB64ToArray(response.publicKey);
// RSA Encrypt user's encKey.key with organization public key
const encKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
keyString = encryptedKey.encryptedString;
toastStringRef = "enrollPasswordResetSuccess";
// Create request and execute enrollment
const request = new OrganizationUserResetPasswordEnrollmentRequest();
request.resetPasswordKey = keyString;
return this.apiService.putOrganizationUserResetPasswordEnrollment(
org.id,
org.userId,
request
);
})
.then(() => {
return this.syncService.fullSync(true);
});
} else {
// Withdrawal
const request = new OrganizationUserResetPasswordEnrollmentRequest();
request.resetPasswordKey = keyString;
this.actionPromise = this.apiService
.putOrganizationUserResetPasswordEnrollment(org.id, org.userId, request)
.then(() => {
return this.syncService.fullSync(true);
});
}
try {
await this.actionPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t(toastStringRef));
await this.load();
} catch (e) {
this.logService.error(e);
}
}
}

View File

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

View File

@@ -1,9 +0,0 @@
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

@@ -1,38 +0,0 @@
<div class="collapsable-row">
<i
class="bwi bwi-fw"
title="{{ 'toggleCollapse' | i18n }}"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed
}"
(click)="toggleCollapse()"
appStopProp
></i>
<h3 class="filter-title">
{{ "types" | i18n }}
</h3>
</div>
<ul *ngIf="!isCollapsed" class="bwi-ul card-ul">
<li [ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Login }">
<a href="#" appStopClick (click)="applyFilter(cipherTypeEnum.Login)">
<i class="bwi bwi-li bwi-fw bwi-globe"></i>{{ "typeLogin" | i18n }}
</a>
</li>
<li [ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Card }">
<a href="#" appStopClick (click)="applyFilter(cipherTypeEnum.Card)">
<i class="bwi bwi-li bwi-fw bwi-credit-card"></i>{{ "typeCard" | i18n }}
</a>
</li>
<li [ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.Identity }">
<a href="#" appStopClick (click)="applyFilter(cipherTypeEnum.Identity)">
<i class="bwi bwi-li bwi-fw bwi-id-card"></i>{{ "typeIdentity" | i18n }}
</a>
</li>
<li [ngClass]="{ active: activeFilter.cipherType === cipherTypeEnum.SecureNote }">
<a href="#" appStopClick (click)="applyFilter(cipherTypeEnum.SecureNote)">
<i class="bwi bwi-li bwi-fw bwi-sticky-note"></i>{{ "typeSecureNote" | i18n }}
</a>
</li>
</ul>

View File

@@ -1,9 +0,0 @@
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

@@ -1,73 +0,0 @@
<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
/>
<app-organization-filter
*ngIf="showOrgFilter"
class="filter"
[hide]="hideOrganizations"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[organizations]="organizations"
[activePersonalOwnershipPolicy]="activePersonalOwnershipPolicy"
[activeSingleOrganizationPolicy]="activeSingleOrganizationPolicy"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-organization-filter>
<app-status-filter
[hideFavorites]="!showFavorites"
[hideTrash]="hideTrash"
[activeFilter]="activeFilter"
(onFilterChange)="applyFilter($event)"
></app-status-filter>
<app-type-filter
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-type-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>
<app-collection-filter
[hide]="hideCollections"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[collectionNodes]="collections"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-collection-filter>
</div>
</div>
</div>

View File

@@ -1,27 +0,0 @@
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";
@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 = "";
constructor(vaultFilterService: VaultFilterService) {
super(vaultFilterService);
}
searchTextChanged() {
this.onSearchTextChanged.emit(this.searchText);
}
}

View File

@@ -1,50 +0,0 @@
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { BrowserModule } from "@angular/platform-browser";
import { RouterModule } from "@angular/router";
import { JslibModule } from "jslib-angular/jslib.module";
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 { 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: [BrowserModule, JslibModule, RouterModule, FormsModule],
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

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

View File

@@ -1,217 +0,0 @@
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";
import { VaultComponent } from "./vault/vault.component";
const routes: Routes = [
{
path: ":organizationId",
component: OrganizationLayoutComponent,
canActivate: [AuthGuard, PermissionsGuard],
data: {
permissions: NavigationPermissionsService.getPermissions("admin"),
},
children: [
{ path: "", pathMatch: "full", redirectTo: "vault" },
{ path: "vault", component: VaultComponent, data: { titleId: "vault" } },
{
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,
data: { titleId: "billing" },
},
{
path: "subscription",
component: OrganizationSubscriptionComponent,
data: { titleId: "subscription" },
},
],
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class OrganizationsRoutingModule {}

View File

@@ -1,48 +0,0 @@
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

@@ -0,0 +1,65 @@
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,18 +0,0 @@
import { Component } from "@angular/core";
import { VaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
import { Organization } from "jslib-common/models/domain/organization";
import { VaultFilterComponent as BaseVaultFilterComponent } from "../../../modules/vault-filter/vault-filter.component";
@Component({
selector: "app-org-vault-filter",
templateUrl: "../../../modules/vault-filter/vault-filter.component.html",
})
export class VaultFilterComponent extends BaseVaultFilterComponent {
organization: Organization;
constructor(vaultFilterService: VaultFilterService) {
super(vaultFilterService);
}
}

View File

@@ -1,26 +1,22 @@
<div class="container page-content">
<div class="row">
<div class="col-3">
<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>
<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>
<div class="col-9">
<div class="page-header d-flex">
<h1>
{{ "vaultItems" | i18n }}
{{ "vault" | i18n }}
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
<ng-container *ngIf="actionSpinner.loading">
<i

View File

@@ -10,7 +10,6 @@ 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 { I18nService } from "jslib-common/abstractions/i18n.service";
@@ -28,7 +27,7 @@ import { AddEditComponent } from "./add-edit.component";
import { AttachmentsComponent } from "./attachments.component";
import { CiphersComponent } from "./ciphers.component";
import { CollectionsComponent } from "./collections.component";
import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
import { GroupingsComponent } from "./groupings.component";
const BroadcasterSubscriptionId = "OrgVaultComponent";
@@ -37,7 +36,7 @@ const BroadcasterSubscriptionId = "OrgVaultComponent";
templateUrl: "vault.component.html",
})
export class VaultComponent implements OnInit, OnDestroy {
@ViewChild("vaultFilter", { static: true }) vaultFilterComponent: VaultFilterComponent;
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
@ViewChild("attachments", { read: ViewContainerRef, static: true })
attachmentsModalRef: ViewContainerRef;
@@ -53,7 +52,6 @@ export class VaultComponent implements OnInit, OnDestroy {
type: CipherType = null;
deleted = false;
trashCleanupWarning: string = null;
activeFilter: VaultFilter = new VaultFilter();
constructor(
private route: ActivatedRoute,
@@ -77,11 +75,11 @@ export class VaultComponent implements OnInit, OnDestroy {
);
this.route.parent.params.pipe(first()).subscribe(async (params) => {
this.organization = await this.organizationService.get(params.organizationId);
this.vaultFilterComponent.organization = this.organization;
this.groupingsComponent.organization = this.organization;
this.ciphersComponent.organization = this.organization;
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
// this.ciphersComponent.searchText = this.vaultFilterComponent.search = qParams.search;
this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search;
if (!this.organization.canViewAllCollections) {
await this.syncService.fullSync(false);
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
@@ -90,11 +88,7 @@ export class VaultComponent implements OnInit, OnDestroy {
case "syncCompleted":
if (message.successfully) {
await Promise.all([
this.vaultFilterComponent.reloadCollectionsAndFolders(
new VaultFilter({
selectedOrganizationId: this.organization.id,
} as Partial<VaultFilter>)
),
this.groupingsComponent.load(),
this.ciphersComponent.refresh(),
]);
this.changeDetectorRef.detectChanges();
@@ -104,10 +98,27 @@ export class VaultComponent implements OnInit, OnDestroy {
});
});
}
await this.vaultFilterComponent.reloadCollectionsAndFolders(
new VaultFilter({ selectedOrganizationId: this.organization.id } as Partial<VaultFilter>)
);
await this.ciphersComponent.reload();
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();
}
}
if (qParams.viewEvents != null) {
const cipher = this.ciphersComponent.ciphers.filter((c) => c.id === qParams.viewEvents);
@@ -123,46 +134,63 @@ export class VaultComponent implements OnInit, OnDestroy {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async applyVaultFilter(vaultFilter: VaultFilter) {
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
this.activeFilter = vaultFilter;
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
this.go();
async clearGroupingFilters() {
this.ciphersComponent.showAddNew = true;
this.ciphersComponent.deleted = false;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchVault");
await this.ciphersComponent.applyFilter();
this.clearFilters();
this.go();
}
private buildFilter(): (cipher: CipherView) => boolean {
return (cipher) => {
let cipherPassesFilter = true;
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
cipherPassesFilter = cipher.favorite;
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;
}
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) {
@@ -204,9 +232,7 @@ export class VaultComponent implements OnInit, OnDestroy {
(comp) => {
if (this.organization.canEditAnyCollection) {
comp.collectionIds = cipher.collectionIds;
comp.collections = this.vaultFilterComponent.collections.fullList.filter(
(c) => !c.readOnly
);
comp.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
}
comp.organization = this.organization;
comp.cipherId = cipher.id;
@@ -223,9 +249,7 @@ export class VaultComponent implements OnInit, OnDestroy {
component.organizationId = this.organization.id;
component.type = this.type;
if (this.organization.canEditAnyCollection) {
component.collections = this.vaultFilterComponent.collections.fullList.filter(
(c) => !c.readOnly
);
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
}
if (this.collectionId != null) {
component.collectionIds = [this.collectionId];
@@ -262,9 +286,7 @@ export class VaultComponent implements OnInit, OnDestroy {
component.cloneMode = true;
component.organizationId = this.organization.id;
if (this.organization.canEditAnyCollection) {
component.collections = this.vaultFilterComponent.collections.fullList.filter(
(c) => !c.readOnly
);
component.collections = this.groupingsComponent.collections.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

@@ -1,9 +1,10 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
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 { 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 { AcceptEmergencyComponent } from "./accounts/accept-emergency.component";
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
@@ -22,20 +23,44 @@ import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.com
import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component";
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 { 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 { PreferencesComponent } from "./settings/preferences.component";
import { PremiumComponent } from "./settings/premium.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";
@@ -49,18 +74,18 @@ const routes: Routes = [
path: "",
component: FrontendLayoutComponent,
children: [
{ path: "", pathMatch: "full", component: LoginComponent, canActivate: [UnauthGuard] },
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard] },
{ path: "", pathMatch: "full", component: LoginComponent, canActivate: [UnauthGuardService] },
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuardService] },
{
path: "register",
component: RegisterComponent,
canActivate: [UnauthGuard],
canActivate: [UnauthGuardService],
data: { titleId: "createAccount" },
},
{
path: "sso",
component: SsoComponent,
canActivate: [UnauthGuard],
canActivate: [UnauthGuardService],
data: { titleId: "enterpriseSingleSignOn" },
},
{
@@ -71,13 +96,13 @@ const routes: Routes = [
{
path: "hint",
component: HintComponent,
canActivate: [UnauthGuard],
canActivate: [UnauthGuardService],
data: { titleId: "passwordHint" },
},
{
path: "lock",
component: LockComponent,
canActivate: [LockGuard],
canActivate: [LockGuardService],
},
{ path: "verify-email", component: VerifyEmailTokenComponent },
{
@@ -94,19 +119,19 @@ const routes: Routes = [
{
path: "recover-2fa",
component: RecoverTwoFactorComponent,
canActivate: [UnauthGuard],
canActivate: [UnauthGuardService],
data: { titleId: "recoverAccountTwoStep" },
},
{
path: "recover-delete",
component: RecoverDeleteComponent,
canActivate: [UnauthGuard],
canActivate: [UnauthGuardService],
data: { titleId: "deleteAccount" },
},
{
path: "verify-recover-delete",
component: VerifyRecoverDeleteComponent,
canActivate: [UnauthGuard],
canActivate: [UnauthGuardService],
data: { titleId: "deleteAccount" },
},
{
@@ -117,19 +142,19 @@ const routes: Routes = [
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
data: { titleId: "updateTempPassword" },
},
{
path: "update-password",
component: UpdatePasswordComponent,
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
data: { titleId: "updatePassword" },
},
{
path: "remove-password",
component: RemovePasswordComponent,
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
data: { titleId: "removeMasterPassword" },
},
],
@@ -137,9 +162,9 @@ const routes: Routes = [
{
path: "",
component: UserLayoutComponent,
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
children: [
{ path: "vault", component: VaultComponent, data: { titleId: "vaults" } },
{ path: "vault", component: VaultComponent, data: { titleId: "myVault" } },
{ path: "sends", component: SendComponent, data: { title: "Send" } },
{
path: "settings",
@@ -147,20 +172,17 @@ const routes: Routes = [
children: [
{ path: "", pathMatch: "full", redirectTo: "account" },
{ path: "account", component: AccountComponent, data: { titleId: "myAccount" } },
{
path: "preferences",
component: PreferencesComponent,
data: { titleId: "preferences" },
},
{
path: "security",
loadChildren: async () => (await import("./settings/security.module")).SecurityModule,
},
{ path: "options", component: OptionsComponent, data: { titleId: "options" } },
{
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" } },
{
@@ -203,7 +225,7 @@ const routes: Routes = [
{
path: "tools",
component: ToolsComponent,
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
children: [
{ path: "", pathMatch: "full", redirectTo: "generator" },
{ path: "import", component: ImportComponent, data: { titleId: "importData" } },
@@ -220,12 +242,191 @@ const routes: Routes = [
loadChildren: async () => (await import("./reports/reports.module")).ReportsModule,
},
{ 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: "organizations",
loadChildren: () =>
import("./organizations/organization-routing.module").then(
(m) => m.OrganizationsRoutingModule
),
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" },
},
],
},
],
},
@@ -236,7 +437,7 @@ const routes: Routes = [
RouterModule.forRoot(routes, {
useHash: true,
paramsInheritanceStrategy: "always",
// enableTracing: true,
/*enableTracing: true,*/
}),
],
exports: [RouterModule],

View File

@@ -53,7 +53,7 @@ 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 } from "@bitwarden/components";
import { BadgeModule, ButtonModule } from "@bitwarden/components";
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { ToastrModule } from "ngx-toastr";
@@ -84,9 +84,8 @@ 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 { VaultFilterModule } from "./modules/vault-filter/vault-filter.module";
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";
@@ -136,7 +135,7 @@ import { AddEditComponent as OrgAddEditComponent } from "./organizations/vault/a
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 { VaultFilterComponent as OrgGroupingsComponent } from "./organizations/vault/vault-filter/vault-filter.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";
@@ -172,15 +171,13 @@ import { EmergencyAccessViewComponent } from "./settings/emergency-access-view.c
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 { 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";
@@ -215,6 +212,7 @@ 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";
@@ -275,13 +273,9 @@ registerLocaleData(localeZhTw, "zh-TW");
DragDropModule,
FormsModule,
InfiniteScrollModule,
VaultFilterModule,
JslibModule,
ReactiveFormsModule,
RouterModule,
BadgeModule,
ButtonModule,
CalloutModule,
ToastrModule,
BadgeModule,
ButtonModule,
@@ -333,6 +327,7 @@ registerLocaleData(localeZhTw, "zh-TW");
FolderAddEditComponent,
FooterComponent,
FrontendLayoutComponent,
GroupingsComponent,
HintComponent,
ImportComponent,
InactiveTwoFactorReportComponent,
@@ -342,6 +337,7 @@ registerLocaleData(localeZhTw, "zh-TW");
MasterPasswordPolicyComponent,
NavbarComponent,
NestedCheckboxComponent,
OptionsComponent,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
@@ -389,8 +385,6 @@ registerLocaleData(localeZhTw, "zh-TW");
PasswordStrengthComponent,
PaymentComponent,
PersonalOwnershipPolicyComponent,
PreferencesComponent,
PremiumBadgeComponent,
PremiumComponent,
ProfileComponent,
ProvidersComponent,
@@ -405,8 +399,6 @@ registerLocaleData(localeZhTw, "zh-TW");
RequireSsoPolicyComponent,
ResetPasswordPolicyComponent,
ReusedPasswordsReportComponent,
SecurityComponent,
SecurityKeysComponent,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,

View File

@@ -1,33 +1,58 @@
<app-navbar></app-navbar>
<div class="container page-content">
<div class="page-header d-flex">
<h1>{{ "providers" | i18n }}</h1>
</div>
<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">
<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>
<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>
</ng-container>
</div>
<app-footer></app-footer>
</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>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from "@angular/core";
import { Component, Input, OnInit } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
@@ -10,6 +10,8 @@ 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 { AuthGuard } from "jslib-angular/guards/auth.guard";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { BreachReportComponent } from "./breach-report.component";
import { ExposedPasswordsReportComponent } from "./exposed-passwords-report.component";
@@ -16,7 +16,7 @@ const routes: Routes = [
{
path: "",
component: ReportsComponent,
canActivate: [AuthGuard],
canActivate: [AuthGuardService],
children: [
{ path: "", pathMatch: "full", component: ReportListComponent, data: { homepage: true } },
{

View File

@@ -1,42 +1,33 @@
import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
import { BaseGuard } from "jslib-angular/guards/base.guard";
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 { Permissions } from "jslib-common/enums/permissions";
@Injectable()
export class PermissionsGuard extends BaseGuard implements CanActivate {
export class OrganizationGuardService implements CanActivate {
constructor(
router: Router,
private organizationService: OrganizationService,
private router: Router,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService
) {
super(router);
}
private i18nService: I18nService,
private organizationService: OrganizationService
) {}
async canActivate(route: ActivatedRouteSnapshot) {
const org = await this.organizationService.get(route.params.organizationId);
if (org == null) {
return this.redirect();
this.router.navigate(["/"]);
return false;
}
if (!org.isOwner && !org.enabled) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("organizationIsDisabled")
);
return this.redirect();
}
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.redirect();
this.router.navigate(["/"]);
return false;
}
return true;

View File

@@ -0,0 +1,40 @@
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

@@ -45,11 +45,11 @@ import { PasswordRepromptService } from "../../services/passwordReprompt.service
import { StateService } from "../../services/state.service";
import { StateMigrationService } from "../../services/stateMigration.service";
import { WebPlatformUtilsService } from "../../services/webPlatformUtils.service";
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 { 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";
@@ -100,7 +100,6 @@ export function initFactory(
imports: [ToastrModule, JslibServicesModule],
declarations: [],
providers: [
OrgPermissionsService,
{
provide: APP_INITIALIZER,
useFactory: initFactory,
@@ -118,7 +117,8 @@ export function initFactory(
],
multi: true,
},
OrgPermissionsGuard,
OrganizationGuardService,
OrganizationTypeGuardService,
RouterService,
EventService,
PolicyListService,

View File

@@ -8,19 +8,43 @@
</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 bit-button buttonType="danger" (click)="deauthorizeSessions()">
<button type="button" class="btn btn-outline-danger" (click)="deauthorizeSessions()">
{{ "deauthorizeSessions" | i18n }}
</button>
<button bit-button buttonType="danger" (click)="purgeVault()">
<button type="button" class="btn btn-outline-danger" (click)="purgeVault()">
{{ "purgeVault" | i18n }}
</button>
<button bit-button buttonType="danger" (click)="deleteAccount()">
<button type="button" class="btn btn-outline-danger" (click)="deleteAccount()">
{{ "deleteAccount" | i18n }}
</button>
</div>

View File

@@ -5,6 +5,7 @@ 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";
@@ -20,7 +21,13 @@ 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(
@@ -31,7 +38,10 @@ export class AccountComponent {
) {}
async ngOnInit() {
this.showChangeEmail = !(await this.keyConnectorService.getUsesKeyConnector());
this.showChangeEmail =
this.showChangeKdf =
this.showChangePassword =
!(await this.keyConnectorService.getUsesKeyConnector());
}
async deauthorizeSessions() {
@@ -45,4 +55,33 @@ 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,7 +1,4 @@
<div class="tabbed-header">
<h1>{{ "encKeySettings" | i18n }}</h1>
</div>
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row">
<div class="col-6">
@@ -71,7 +68,7 @@
</div>
</div>
</div>
<button bit-button buttonType="primary" class="btn-submit" [disabled]="form.loading">
<button type="submit" class="btn btn-primary 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,8 +1,4 @@
<div class="tabbed-header">
<h1>{{ "changeMasterPassword" | i18n }}</h1>
</div>
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
@@ -87,7 +83,7 @@
</a>
</div>
</div>
<button bit-button buttonType="primary" class="btn-submit" [disabled]="form.loading">
<button type="submit" class="btn btn-primary 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,5 +1,4 @@
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";
@@ -7,7 +6,6 @@ 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";
@@ -49,9 +47,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
private syncService: SyncService,
private apiService: ApiService,
private sendService: SendService,
private organizationService: OrganizationService,
private keyConnectorService: KeyConnectorService,
private router: Router
private organizationService: OrganizationService
) {
super(
i18nService,
@@ -64,12 +60,6 @@ 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,7 +1,7 @@
<div class="page-header">
<h1>{{ "preferences" | i18n }}</h1>
<h1>{{ "options" | i18n }}</h1>
</div>
<p>{{ "preferencesDesc" | i18n }}</p>
<p>{{ "optionsDesc" | 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-preferences",
templateUrl: "preferences.component.html",
selector: "app-options",
templateUrl: "options.component.html",
})
export class PreferencesComponent implements OnInit {
export class OptionsComponent implements OnInit {
vaultTimeoutAction = "lock";
disableIcons: boolean;
enableGravatars: boolean;
@@ -107,11 +107,7 @@ export class PreferencesComponent implements OnInit {
if (this.locale !== this.startingLocale) {
window.location.reload();
} else {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("preferencesUpdated")
);
this.platformUtilsService.showToast("success", null, this.i18nService.t("optionsUpdated"));
}
}

View File

@@ -1,51 +1,13 @@
<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="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"
@@ -54,72 +16,140 @@
></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)"
</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-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">
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a
*ngIf="o.ssoBound; else linkSso"
*ngIf="allowEnrollmentChanges(o) && !o.resetPasswordEnrolled"
class="dropdown-item"
href="#"
appStopClick
(click)="unlinkSso(o)"
(click)="toggleResetPasswordEnrollment(o)"
>
<i class="bwi bwi-fw bwi-chain-broken" aria-hidden="true"></i>
{{ "unlinkSso" | i18n }}
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
{{ "enrollPasswordReset" | 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>
<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>
</div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</ng-container>
</ng-container>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from "@angular/core";
import { Component, Input, OnInit } from "@angular/core";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
@@ -19,6 +19,8 @@ import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/mod
templateUrl: "organizations.component.html",
})
export class OrganizationsComponent implements OnInit {
@Input() vault = false;
organizations: Organization[];
policies: Policy[];
loaded = false;
@@ -36,8 +38,10 @@ export class OrganizationsComponent implements OnInit {
) {}
async ngOnInit() {
await this.syncService.fullSync(true);
await this.load();
if (!this.vault) {
await this.syncService.fullSync(true);
await this.load();
}
}
async load() {

View File

@@ -1,18 +0,0 @@
<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

@@ -1,61 +0,0 @@
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

@@ -1,22 +0,0 @@
<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

@@ -1,17 +0,0 @@
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

@@ -1,39 +0,0 @@
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 SecurityModule {}

View File

@@ -7,11 +7,8 @@
<a routerLink="account" class="list-group-item" routerLinkActive="active">
{{ "myAccount" | i18n }}
</a>
<a routerLink="security" class="list-group-item" routerLinkActive="active">
{{ "security" | i18n }}
</a>
<a routerLink="preferences" class="list-group-item" routerLinkActive="active">
{{ "preferences" | i18n }}
<a routerLink="options" class="list-group-item" routerLinkActive="active">
{{ "options" | i18n }}
</a>
<a routerLink="organizations" class="list-group-item" routerLinkActive="active">
{{ "organizations" | i18n }}
@@ -40,6 +37,9 @@
>
{{ "billing" | i18n }}
</a>
<a routerLink="two-factor" class="list-group-item" routerLinkActive="active">
{{ "twoStepLogin" | i18n }}
</a>
<a routerLink="domain-rules" class="list-group-item" routerLinkActive="active">
{{ "domainRules" | i18n }}
</a>

View File

@@ -1,14 +1,14 @@
<div class="tabbed-header">
<div class="page-header">
<h1>{{ "twoStepLogin" | i18n }}</h1>
</div>
<p *ngIf="!organizationId">{{ "twoStepLoginDesc" | i18n }}</p>
<p *ngIf="organizationId">{{ "twoStepLoginOrganizationDesc" | i18n }}</p>
<bit-callout type="warning" *ngIf="!organizationId">
<app-callout type="warning" *ngIf="!organizationId">
<p>{{ "twoStepLoginRecoveryWarning" | i18n }}</p>
<button bit-button buttonType="secondary" (click)="recoveryCode()">
<button type="button" class="btn btn-outline-secondary" (click)="recoveryCode()">
{{ "viewRecoveryCode" | i18n }}
</button>
</bit-callout>
</app-callout>
<h2 [ngClass]="{ 'mt-5': !organizationId }">
{{ "providers" | i18n }}
<small *ngIf="loading">
@@ -20,9 +20,9 @@
<span class="sr-only">{{ "loading" | i18n }}</span>
</small>
</h2>
<bit-callout type="warning" *ngIf="showPolicyWarning">
<app-callout type="warning" *ngIf="showPolicyWarning">
{{ "twoStepLoginPolicyUserWarning" | i18n }}
</bit-callout>
</app-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
bit-button
buttonType="secondary"
type="button"
class="btn btn-outline-secondary btn-sm"
[disabled]="!canAccessPremium && p.premium"
(click)="manage(p.type)"
>

View File

@@ -47,9 +47,7 @@ export class UserBillingComponent implements OnInit {
if (this.organizationId != null) {
this.billing = await this.apiService.getOrganizationBilling(this.organizationId);
} else {
// let history = await this.apiService.getUserBillingHistory();
// let payment = await this.apiService.getUserBillingPayment();
this.billing = new BillingResponse(null);
this.billing = await this.apiService.getUserBilling();
}
this.loading = false;
}

View File

@@ -0,0 +1,173 @@
<div class="card vault-filters">
<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 || ('searchVault' | i18n) }}"
id="search"
class="form-control"
[(ngModel)]="searchText"
(input)="searchTextChanged()"
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>{{ "allItems" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedFavorites }" *ngIf="showFavorites">
<a href="#" appStopClick (click)="selectFavorites()">
<i class="bwi bwi-li bwi-fw bwi-star"></i>{{ "favorites" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedTrash }" *ngIf="showTrash">
<a href="#" appStopClick (click)="selectTrash()">
<i class="bwi bwi-li bwi-fw bwi-trash"></i>{{ "trash" | i18n }}
</a>
</li>
</ul>
<h3>{{ "types" | i18n }}</h3>
<ul class="bwi-ul card-ul">
<li [ngClass]="{ active: selectedType === cipherType.Login }">
<a href="#" appStopClick (click)="selectType(cipherType.Login)">
<i class="bwi bwi-li bwi-fw bwi-globe"></i>{{ "typeLogin" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedType === cipherType.Card }">
<a href="#" appStopClick (click)="selectType(cipherType.Card)">
<i class="bwi bwi-li bwi-fw bwi-credit-card"></i>{{ "typeCard" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedType === cipherType.Identity }">
<a href="#" appStopClick (click)="selectType(cipherType.Identity)">
<i class="bwi bwi-li bwi-fw bwi-id-card"></i>{{ "typeIdentity" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedType === cipherType.SecureNote }">
<a href="#" appStopClick (click)="selectType(cipherType.SecureNote)">
<i class="bwi bwi-li bwi-fw bwi-sticky-note"></i>{{ "typeSecureNote" | i18n }}
</a>
</li>
</ul>
<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">
<ng-container *ngIf="showFolders">
<h3 class="d-flex">
{{ "folders" | i18n }}
<a
href="#"
class="text-muted ml-auto"
appStopClick
(click)="addFolder()"
appA11yTitle="{{ 'addFolder' | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</a>
</h3>
<ul class="bwi-ul card-ul">
<ng-template #recursiveFolders let-folders>
<li
*ngFor="let f of folders"
[ngClass]="{ active: selectedFolder && f.node.id === selectedFolderId }"
>
<div class="d-flex">
<i
*ngIf="f.children.length"
class="bwi-li bwi"
title="{{ 'toggleCollapse' | i18n }}"
[ngClass]="{
'bwi-angle-right': isCollapsed(f.node),
'bwi-angle-down': !isCollapsed(f.node)
}"
(click)="collapse(f.node)"
></i>
<a href="#" class="text-break" appStopClick (click)="selectFolder(f.node)">
<i
*ngIf="f.children.length === 0"
class="bwi bwi-li bwi-folder"
aria-hidden="true"
></i
>{{ f.node.name }}
</a>
<a
href="#"
class="text-muted ml-auto show-active"
appStopClick
(click)="editFolder(f.node)"
appA11yTitle="{{ 'editFolder' | i18n }}"
*ngIf="f.node.id"
>
<i class="bwi bwi-pencil bwi-fw" aria-hidden="true"></i>
</a>
</div>
<ul class="bwi-ul card-ul carets" *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>
<ng-container *ngIf="showCollections && collections && collections.length">
<h3>{{ "collections" | i18n }}</h3>
<ul class="bwi-ul card-ul">
<ng-template #recursiveCollections let-collections>
<li
*ngFor="let c of collections"
[ngClass]="{ active: c.node.id === selectedCollectionId }"
>
<i
*ngIf="c.children.length"
class="bwi-li bwi"
title="{{ 'toggleCollapse' | i18n }}"
[ngClass]="{
'bwi-angle-right': isCollapsed(c.node),
'bwi-angle-down': !isCollapsed(c.node)
}"
(click)="collapse(c.node)"
></i>
<a href="#" class="text-break" appStopClick (click)="selectCollection(c.node)">
<i
*ngIf="c.children.length === 0"
class="bwi bwi-li bwi-collection"
aria-hidden="true"
></i
>{{ c.node.name }}
</a>
<ul class="bwi-ul card-ul carets" *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>
</ng-container>
</div>
</div>

View File

@@ -0,0 +1,29 @@
import { Component, EventEmitter, Output } from "@angular/core";
import { GroupingsComponent as BaseGroupingsComponent } from "jslib-angular/components/groupings.component";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { StateService } from "jslib-common/abstractions/state.service";
@Component({
selector: "app-vault-groupings",
templateUrl: "groupings.component.html",
})
export class GroupingsComponent extends BaseGroupingsComponent {
@Output() onSearchTextChanged = new EventEmitter<string>();
searchText = "";
searchPlaceholder: string = null;
constructor(
collectionService: CollectionService,
folderService: FolderService,
stateService: StateService
) {
super(collectionService, folderService, stateService);
}
searchTextChanged() {
this.onSearchTextChanged.emit(this.searchText);
}
}

View File

@@ -1,25 +1,23 @@
<div class="container page-content">
<div class="row">
<div class="col-3">
<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>
<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>
<div [ngClass]="{ 'col-6': isShowingCards, 'col-9': !isShowingCards }">
<div class="col-6">
<div class="page-header d-flex">
<h1>
{{ "vaultItems" | i18n }}
{{ "myVault" | i18n }}
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
<ng-container *ngIf="actionSpinner.loading">
<i
@@ -99,6 +97,40 @@
</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,7 +10,6 @@ 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 { CryptoService } from "jslib-common/abstractions/crypto.service";
@@ -18,13 +17,14 @@ 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 { 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 { VaultFilterComponent } from "../modules/vault-filter/vault-filter.component";
import { OrganizationsComponent } from "../settings/organizations.component";
import { UpdateKeyComponent } from "../settings/update-key.component";
import { AddEditComponent } from "./add-edit.component";
@@ -32,6 +32,7 @@ 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";
const BroadcasterSubscriptionId = "VaultComponent";
@@ -41,8 +42,10 @@ const BroadcasterSubscriptionId = "VaultComponent";
templateUrl: "vault.component.html",
})
export class VaultComponent implements OnInit, OnDestroy {
@ViewChild("vaultFilter", { static: true }) filterComponent: VaultFilterComponent;
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
@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 })
@@ -59,15 +62,13 @@ 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,
@@ -83,7 +84,8 @@ export class VaultComponent implements OnInit, OnDestroy {
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
private stateService: StateService,
private organizationService: OrganizationService
private organizationService: OrganizationService,
private providerService: ProviderService
) {}
async ngOnInit() {
@@ -97,23 +99,42 @@ 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.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
this.showProviders = (await this.providerService.getAll()).length > 0;
await Promise.all([this.groupingsComponent.load(), this.organizationsComponent.load()]);
this.showUpdateKey = !(await this.cryptoService.hasEncKey());
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);
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();
}
}
await this.ciphersComponent.reload();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
@@ -121,7 +142,8 @@ export class VaultComponent implements OnInit, OnDestroy {
case "syncCompleted":
if (message.successfully) {
await Promise.all([
this.filterComponent.reloadCollectionsAndFolders(this.activeFilter),
this.groupingsComponent.load(),
this.organizationsComponent.load(),
this.ciphersComponent.load(this.ciphersComponent.filter),
]);
this.changeDetectorRef.detectChanges();
@@ -133,26 +155,64 @@ export class VaultComponent implements OnInit, OnDestroy {
});
}
get isShowingCards() {
return (
this.showBrowserOutdated ||
this.showPremiumCallout ||
this.showUpdateKey ||
this.showVerifyEmail
);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
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.calculateSearchBarLocalizationString(
this.activeFilter
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
);
this.clearFilters();
this.collectionId = collectionId;
this.go();
}
@@ -161,40 +221,6 @@ export class VaultComponent implements OnInit, OnDestroy {
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) {
@@ -266,7 +292,7 @@ export class VaultComponent implements OnInit, OnDestroy {
comp.folderId = null;
comp.onSavedFolder.subscribe(async () => {
modal.close();
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
await this.groupingsComponent.loadFolders();
});
}
);
@@ -280,11 +306,13 @@ export class VaultComponent implements OnInit, OnDestroy {
comp.folderId = folderId;
comp.onSavedFolder.subscribe(async () => {
modal.close();
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
await this.groupingsComponent.loadFolders();
});
comp.onDeletedFolder.subscribe(async () => {
modal.close();
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
await this.groupingsComponent.loadFolders();
await this.filterFolder("none");
this.groupingsComponent.selectedFolderId = null;
});
}
);
@@ -294,21 +322,15 @@ 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.activeFilter.selectedCollectionId != null) {
const collection = this.filterComponent.collections.fullList.filter(
(c) => c.id === this.activeFilter.selectedCollectionId
if (this.collectionId != null) {
const collection = this.groupingsComponent.collections.filter(
(c) => c.id === this.collectionId
);
if (collection.length > 0) {
component.organizationId = collection[0].organizationId;
component.collectionIds = [this.activeFilter.selectedCollectionId];
component.collectionIds = [this.collectionId];
}
}
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) {
@@ -344,30 +366,12 @@ export class VaultComponent implements OnInit, OnDestroy {
await this.modalService.openViewRef(UpdateKeyComponent, this.updateKeyModalRef);
}
private 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";
private clearFilters() {
this.folderId = null;
this.collectionId = null;
this.favorites = false;
this.type = null;
this.deleted = false;
}
private go(queryParams: any = null) {

View File

@@ -424,18 +424,9 @@
"myVault": {
"message": "My Vault"
},
"allVaults": {
"message": "All Vaults"
},
"vault": {
"message": "Vault"
},
"vaults": {
"message": "Vaults"
},
"vaultItems": {
"message": "Vault Items"
},
"moveSelectedToOrg": {
"message": "Move Selected to Organization"
},
@@ -1119,14 +1110,11 @@
"options": {
"message": "Options"
},
"preferences": {
"message": "Preferences"
},
"preferencesDesc": {
"optionsDesc": {
"message": "Customize your web vault experience."
},
"preferencesUpdated": {
"message": "Preferences updated"
"optionsUpdated": {
"message": "Options updated"
},
"language": {
"message": "Language"
@@ -4848,21 +4836,6 @@
}
}
},
"accessDenied": {
"message": "Access Denied. You do not have permission to view this page."
},
"backToReports": {
"message": "Back to Reports"
},
"masterPassword": {
"message": "Master Password"
},
"security": {
"message": "Security"
},
"keys": {
"message": "Keys"
},
"backToReports": {
"message": "Back to Reports"
},

View File

@@ -39,8 +39,7 @@ body {
}
.page-header,
.secondary-header,
.tabbed-header {
.secondary-header {
margin-bottom: 0.5rem;
padding-bottom: 0.6rem;
@include themify($themes) {
@@ -65,11 +64,6 @@ body {
margin-top: 4rem;
}
.tabbed-header {
margin-top: 2rem;
margin-bottom: 1rem;
}
img.logo {
display: block;
height: 43px;

View File

@@ -71,10 +71,6 @@
margin-left: 0.85em;
}
}
&.no-margin {
margin-left: 0;
}
}
.card-org-plans {

View File

@@ -74,27 +74,6 @@
height: 100%;
}
.org-name {
line-height: 1;
span {
display: block;
font-size: $font-size-lg;
@include themify($themes) {
color: themed("textHeadingColor");
}
}
}
}
.tabbed-nav {
@include themify($themes) {
border-bottom: 1px solid themed("borderColor");
color: themed("textColor");
}
}
.org-nav,
.tabbed-nav {
.nav-tabs {
border-bottom: none;
@@ -111,7 +90,6 @@
padding-top: calc(#{$nav-link-padding-y} - 2px);
@include themify($themes) {
border-top: 3px solid themed("primary");
border-bottom: 1px solid themed("backgroundColor");
color: themed("linkColor");
}
}
@@ -123,4 +101,15 @@
}
}
}
.org-name {
line-height: 1;
span {
display: block;
font-size: $font-size-lg;
@include themify($themes) {
color: themed("textHeadingColor");
}
}
}
}

View File

@@ -260,45 +260,3 @@ app-sponsored-families {
}
}
}
.collapsable-row {
display: flex;
padding-top: 15px;
i {
margin-top: 3px;
}
.filter-title {
padding-left: 5px;
}
&.active {
@include themify($themes) {
color: themed("primary");
}
}
}
.vault-filter-option {
padding-bottom: 3px;
&.active {
@include themify($themes) {
color: themed("primary");
font-weight: bold;
}
}
button.org-options {
background: none;
border: none;
}
}
.org-filter-heading {
@include themify($themes) {
color: themed("textColor");
}
&.active {
@include themify($themes) {
color: themed("primary");
font-weight: bold;
}
}
}