mirror of
https://github.com/bitwarden/web
synced 2025-12-16 16:23:31 +00:00
Compare commits
30 Commits
PS-589-add
...
vgrassia-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6814a9bf4e | ||
|
|
44548d5c55 | ||
|
|
efcf975167 | ||
|
|
a584469917 | ||
|
|
d0992049b6 | ||
|
|
d816841985 | ||
|
|
69e599a40d | ||
|
|
300b2b168b | ||
|
|
7280a58478 | ||
|
|
581885ab87 | ||
|
|
1647114d08 | ||
|
|
c3e7b93a03 | ||
|
|
882470a4d3 | ||
|
|
5599a29a2e | ||
|
|
d4e5597320 | ||
|
|
c11f69f48d | ||
|
|
d6995cdbf9 | ||
|
|
8671cd49e7 | ||
|
|
5e85cea01b | ||
|
|
4fd601bdc2 | ||
|
|
cf9df7bf98 | ||
|
|
b64404863f | ||
|
|
f8ff75aa1b | ||
|
|
9b742ed0d7 | ||
|
|
3a96e4ab76 | ||
|
|
8a36536d40 | ||
|
|
5c5ae81fdf | ||
|
|
7dc32270c5 | ||
|
|
41ad89e901 | ||
|
|
c2308a64bf |
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@@ -49,6 +49,15 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
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
|
- name: Run linter
|
||||||
run: npm run lint
|
run: npm run lint
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { InfiniteScrollModule } from "ngx-infinite-scroll";
|
|||||||
|
|
||||||
import { JslibModule } from "jslib-angular/jslib.module";
|
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 { OssRoutingModule } from "src/app/oss-routing.module";
|
||||||
import { ServicesModule } from "src/app/services/services.module";
|
import { ServicesModule } from "src/app/services/services.module";
|
||||||
import { WildcardRoutingModule } from "src/app/wildcard-routing.module";
|
import { WildcardRoutingModule } from "src/app/wildcard-routing.module";
|
||||||
@@ -20,6 +21,7 @@ import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-tim
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
JslibModule,
|
JslibModule,
|
||||||
|
VaultFilterModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
import { RouterModule, Routes } from "@angular/router";
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
|
|
||||||
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
|
import { AuthGuard } from "jslib-angular/guards/auth.guard";
|
||||||
import { Permissions } from "jslib-common/enums/permissions";
|
import { Permissions } from "jslib-common/enums/permissions";
|
||||||
|
|
||||||
import { OrganizationLayoutComponent } from "src/app/layouts/organization-layout.component";
|
import { PermissionsGuard } from "src/app/organizations/guards/permissions.guard";
|
||||||
|
import { OrganizationLayoutComponent } from "src/app/organizations/layouts/organization-layout.component";
|
||||||
import { ManageComponent } from "src/app/organizations/manage/manage.component";
|
import { ManageComponent } from "src/app/organizations/manage/manage.component";
|
||||||
import { OrganizationGuardService } from "src/app/services/organization-guard.service";
|
import { NavigationPermissionsService } from "src/app/organizations/services/navigation-permissions.service";
|
||||||
import { OrganizationTypeGuardService } from "src/app/services/organization-type-guard.service";
|
|
||||||
|
|
||||||
import { SsoComponent } from "./manage/sso.component";
|
import { SsoComponent } from "./manage/sso.component";
|
||||||
|
|
||||||
@@ -15,24 +15,15 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "organizations/:organizationId",
|
path: "organizations/:organizationId",
|
||||||
component: OrganizationLayoutComponent,
|
component: OrganizationLayoutComponent,
|
||||||
canActivate: [AuthGuardService, OrganizationGuardService],
|
canActivate: [AuthGuard, PermissionsGuard],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "manage",
|
path: "manage",
|
||||||
component: ManageComponent,
|
component: ManageComponent,
|
||||||
canActivate: [OrganizationTypeGuardService],
|
canActivate: [PermissionsGuard],
|
||||||
data: {
|
data: {
|
||||||
permissions: [
|
permissions: [
|
||||||
Permissions.CreateNewCollections,
|
NavigationPermissionsService.getPermissions("manage").concat(Permissions.ManageSso),
|
||||||
Permissions.EditAnyCollection,
|
|
||||||
Permissions.DeleteAnyCollection,
|
|
||||||
Permissions.EditAssignedCollections,
|
|
||||||
Permissions.DeleteAssignedCollections,
|
|
||||||
Permissions.AccessEventLogs,
|
|
||||||
Permissions.ManageGroups,
|
|
||||||
Permissions.ManageUsers,
|
|
||||||
Permissions.ManagePolicies,
|
|
||||||
Permissions.ManageSso,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { ProviderService } from "jslib-common/abstractions/provider.service";
|
|||||||
import { Permissions } from "jslib-common/enums/permissions";
|
import { Permissions } from "jslib-common/enums/permissions";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProviderTypeGuardService implements CanActivate {
|
export class PermissionsGuard implements CanActivate {
|
||||||
constructor(private providerService: ProviderService, private router: Router) {}
|
constructor(private providerService: ProviderService, private router: Router) {}
|
||||||
|
|
||||||
async canActivate(route: ActivatedRouteSnapshot) {
|
async canActivate(route: ActivatedRouteSnapshot) {
|
||||||
@@ -6,7 +6,7 @@ import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.se
|
|||||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProviderGuardService implements CanActivate {
|
export class ProviderGuard implements CanActivate {
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
import { RouterModule, Routes } from "@angular/router";
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
|
|
||||||
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
|
import { AuthGuard } from "jslib-angular/guards/auth.guard";
|
||||||
import { Permissions } from "jslib-common/enums/permissions";
|
import { Permissions } from "jslib-common/enums/permissions";
|
||||||
|
|
||||||
import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component";
|
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 { ClientsComponent } from "./clients/clients.component";
|
||||||
import { CreateOrganizationComponent } from "./clients/create-organization.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 { AcceptProviderComponent } from "./manage/accept-provider.component";
|
||||||
import { EventsComponent } from "./manage/events.component";
|
import { EventsComponent } from "./manage/events.component";
|
||||||
import { ManageComponent } from "./manage/manage.component";
|
import { ManageComponent } from "./manage/manage.component";
|
||||||
import { PeopleComponent } from "./manage/people.component";
|
import { PeopleComponent } from "./manage/people.component";
|
||||||
import { ProvidersLayoutComponent } from "./providers-layout.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 { AccountComponent } from "./settings/account.component";
|
||||||
import { SettingsComponent } from "./settings/settings.component";
|
import { SettingsComponent } from "./settings/settings.component";
|
||||||
import { SetupProviderComponent } from "./setup/setup-provider.component";
|
import { SetupProviderComponent } from "./setup/setup-provider.component";
|
||||||
@@ -24,7 +24,7 @@ import { SetupComponent } from "./setup/setup.component";
|
|||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuard],
|
||||||
component: ProvidersComponent,
|
component: ProvidersComponent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -45,7 +45,7 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuard],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "setup",
|
path: "setup",
|
||||||
@@ -54,7 +54,7 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: ":providerId",
|
path: ":providerId",
|
||||||
component: ProvidersLayoutComponent,
|
component: ProvidersLayoutComponent,
|
||||||
canActivate: [ProviderGuardService],
|
canActivate: [ProviderGuard],
|
||||||
children: [
|
children: [
|
||||||
{ path: "", pathMatch: "full", redirectTo: "clients" },
|
{ path: "", pathMatch: "full", redirectTo: "clients" },
|
||||||
{ path: "clients/create", component: CreateOrganizationComponent },
|
{ path: "clients/create", component: CreateOrganizationComponent },
|
||||||
@@ -71,7 +71,7 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "people",
|
path: "people",
|
||||||
component: PeopleComponent,
|
component: PeopleComponent,
|
||||||
canActivate: [ProviderTypeGuardService],
|
canActivate: [PermissionsGuard],
|
||||||
data: {
|
data: {
|
||||||
titleId: "people",
|
titleId: "people",
|
||||||
permissions: [Permissions.ManageUsers],
|
permissions: [Permissions.ManageUsers],
|
||||||
@@ -80,7 +80,7 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "events",
|
path: "events",
|
||||||
component: EventsComponent,
|
component: EventsComponent,
|
||||||
canActivate: [ProviderTypeGuardService],
|
canActivate: [PermissionsGuard],
|
||||||
data: {
|
data: {
|
||||||
titleId: "eventLogs",
|
titleId: "eventLogs",
|
||||||
permissions: [Permissions.AccessEventLogs],
|
permissions: [Permissions.AccessEventLogs],
|
||||||
@@ -100,7 +100,7 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "account",
|
path: "account",
|
||||||
component: AccountComponent,
|
component: AccountComponent,
|
||||||
canActivate: [ProviderTypeGuardService],
|
canActivate: [PermissionsGuard],
|
||||||
data: {
|
data: {
|
||||||
titleId: "myProvider",
|
titleId: "myProvider",
|
||||||
permissions: [Permissions.ManageProvider],
|
permissions: [Permissions.ManageProvider],
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { OssModule } from "src/app/oss.module";
|
|||||||
import { AddOrganizationComponent } from "./clients/add-organization.component";
|
import { AddOrganizationComponent } from "./clients/add-organization.component";
|
||||||
import { ClientsComponent } from "./clients/clients.component";
|
import { ClientsComponent } from "./clients/clients.component";
|
||||||
import { CreateOrganizationComponent } from "./clients/create-organization.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 { AcceptProviderComponent } from "./manage/accept-provider.component";
|
||||||
import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component";
|
import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component";
|
||||||
import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component";
|
import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component";
|
||||||
@@ -19,8 +21,6 @@ import { PeopleComponent } from "./manage/people.component";
|
|||||||
import { UserAddEditComponent } from "./manage/user-add-edit.component";
|
import { UserAddEditComponent } from "./manage/user-add-edit.component";
|
||||||
import { ProvidersLayoutComponent } from "./providers-layout.component";
|
import { ProvidersLayoutComponent } from "./providers-layout.component";
|
||||||
import { ProvidersRoutingModule } from "./providers-routing.module";
|
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 { WebProviderService } from "./services/webProvider.service";
|
||||||
import { AccountComponent } from "./settings/account.component";
|
import { AccountComponent } from "./settings/account.component";
|
||||||
import { SettingsComponent } from "./settings/settings.component";
|
import { SettingsComponent } from "./settings/settings.component";
|
||||||
@@ -46,7 +46,7 @@ import { SetupComponent } from "./setup/setup.component";
|
|||||||
SetupProviderComponent,
|
SetupProviderComponent,
|
||||||
UserAddEditComponent,
|
UserAddEditComponent,
|
||||||
],
|
],
|
||||||
providers: [WebProviderService, ProviderGuardService, ProviderTypeGuardService],
|
providers: [WebProviderService, ProviderGuard, PermissionsGuard],
|
||||||
})
|
})
|
||||||
export class ProvidersModule {
|
export class ProvidersModule {
|
||||||
constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) {
|
constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) {
|
||||||
|
|||||||
2
jslib
2
jslib
Submodule jslib updated: 3b9ef68f4b...ad37de9373
15
package-lock.json
generated
15
package-lock.json
generated
@@ -139,6 +139,14 @@
|
|||||||
"typescript": "4.3.5"
|
"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": {
|
"node_modules/@ampproject/remapping": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz",
|
||||||
@@ -9657,6 +9665,13 @@
|
|||||||
"tldjs": "^2.3.1",
|
"tldjs": "^2.3.1",
|
||||||
"typescript": "4.3.5",
|
"typescript": "4.3.5",
|
||||||
"zxcvbn": "^4.4.2"
|
"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": {
|
"@braintree/asset-loader": {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"repository": "https://github.com/bitwarden/web",
|
"repository": "https://github.com/bitwarden/web",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"sub:init": "git submodule update --init --recursive",
|
"sub:init": "rm -rf jslib; git submodule sync --recursive; git -c protocol.version=2 submodule update --init --force --depth=1 --recursive",
|
||||||
"sub:update": "git submodule update --remote",
|
"sub:update": "git submodule update --remote",
|
||||||
"sub:pull": "git submodule foreach git pull origin master",
|
"sub:pull": "git submodule foreach git pull origin master",
|
||||||
"preinstall": "npm run sub:init",
|
"preinstall": "npm run sub:init",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="collapse navbar-collapse">
|
<div class="collapse navbar-collapse">
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<li class="nav-item" routerLinkActive="active">
|
<li class="nav-item" routerLinkActive="active">
|
||||||
<a class="nav-link" routerLink="/vault">{{ "myVault" | i18n }}</a>
|
<a class="nav-link" routerLink="/vault">{{ "vaults" | i18n }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active">
|
<li class="nav-item" routerLinkActive="active">
|
||||||
<a class="nav-link" routerLink="/sends">{{ "send" | i18n }}</a>
|
<a class="nav-link" routerLink="/sends">{{ "send" | i18n }}</a>
|
||||||
@@ -27,8 +27,10 @@
|
|||||||
<li class="nav-item" routerLinkActive="active">
|
<li class="nav-item" routerLinkActive="active">
|
||||||
<a class="nav-link" routerLink="/reports">{{ "reports" | i18n }}</a>
|
<a class="nav-link" routerLink="/reports">{{ "reports" | i18n }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active">
|
<li *ngIf="organizations.length >= 1" class="nav-item" routerLinkActive="active">
|
||||||
<a class="nav-link" routerLink="/settings">{{ "settings" | i18n }}</a>
|
<a class="nav-link" [routerLink]="['/organizations', organizations[0].id]">{{
|
||||||
|
"organizations" | i18n
|
||||||
|
}}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { MessagingService } from "jslib-common/abstractions/messaging.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 { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||||
import { SyncService } from "jslib-common/abstractions/sync.service";
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
import { TokenService } from "jslib-common/abstractions/token.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 { Provider } from "jslib-common/models/domain/provider";
|
||||||
|
|
||||||
|
import { NavigationPermissionsService as OrgNavigationPermissionsService } from "../organizations/services/navigation-permissions.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-navbar",
|
selector: "app-navbar",
|
||||||
templateUrl: "navbar.component.html",
|
templateUrl: "navbar.component.html",
|
||||||
@@ -16,13 +22,16 @@ export class NavbarComponent implements OnInit {
|
|||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
providers: Provider[] = [];
|
providers: Provider[] = [];
|
||||||
|
organizations: Organization[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private tokenService: TokenService,
|
private tokenService: TokenService,
|
||||||
private providerService: ProviderService,
|
private providerService: ProviderService,
|
||||||
private syncService: SyncService
|
private syncService: SyncService,
|
||||||
|
private organizationService: OrganizationService,
|
||||||
|
private i18nService: I18nService
|
||||||
) {
|
) {
|
||||||
this.selfHosted = this.platformUtilsService.isSelfHost();
|
this.selfHosted = this.platformUtilsService.isSelfHost();
|
||||||
}
|
}
|
||||||
@@ -34,11 +43,16 @@ export class NavbarComponent implements OnInit {
|
|||||||
this.name = this.email;
|
this.name = this.email;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure provides are loaded
|
// Ensure providers and organizations are loaded
|
||||||
if ((await this.syncService.getLastSync()) == null) {
|
if ((await this.syncService.getLastSync()) == null) {
|
||||||
await this.syncService.fullSync(false);
|
await this.syncService.fullSync(false);
|
||||||
}
|
}
|
||||||
this.providers = await this.providerService.getAll();
|
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() {
|
lock() {
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<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> {{ 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>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
|
import { CollectionFilterComponent as BaseCollectionFilterComponent } from "jslib-angular/modules/vault-filter/components/collection-filter.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-collection-filter",
|
||||||
|
templateUrl: "collection-filter.component.html",
|
||||||
|
})
|
||||||
|
export class CollectionFilterComponent extends BaseCollectionFilterComponent {}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
|
import { FolderFilterComponent as BaseFolderFilterComponent } from "jslib-angular/modules/vault-filter/components/folder-filter.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-folder-filter",
|
||||||
|
templateUrl: "folder-filter.component.html",
|
||||||
|
})
|
||||||
|
export class FolderFilterComponent extends BaseFolderFilterComponent {}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
<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>
|
||||||
|
{{ "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 }"
|
||||||
|
> {{ 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>
|
||||||
|
{{ 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>
|
||||||
|
{{ 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 }"
|
||||||
|
> {{ 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>
|
||||||
|
{{ "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>
|
||||||
|
{{ 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>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
|
import { OrganizationFilterComponent as BaseOrganizationFilterComponent } from "jslib-angular/modules/vault-filter/components/organization-filter.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-organization-filter",
|
||||||
|
templateUrl: "organization-filter.component.html",
|
||||||
|
})
|
||||||
|
export class OrganizationFilterComponent extends BaseOrganizationFilterComponent {
|
||||||
|
displayText = "allVaults";
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<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> {{ "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> {{ "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> {{ "trash" | i18n }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
|
import { StatusFilterComponent as BaseStatusFilterComponent } from "jslib-angular/modules/vault-filter/components/status-filter.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-status-filter",
|
||||||
|
templateUrl: "status-filter.component.html",
|
||||||
|
})
|
||||||
|
export class StatusFilterComponent extends BaseStatusFilterComponent {}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
|
import { TypeFilterComponent as BaseTypeFilterComponent } from "jslib-angular/modules/vault-filter/components/type-filter.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-type-filter",
|
||||||
|
templateUrl: "type-filter.component.html",
|
||||||
|
})
|
||||||
|
export class TypeFilterComponent extends BaseTypeFilterComponent {}
|
||||||
73
src/app/modules/vault-filter/vault-filter.component.html
Normal file
73
src/app/modules/vault-filter/vault-filter.component.html
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<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>
|
||||||
27
src/app/modules/vault-filter/vault-filter.component.ts
Normal file
27
src/app/modules/vault-filter/vault-filter.component.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/app/modules/vault-filter/vault-filter.module.ts
Normal file
50
src/app/modules/vault-filter/vault-filter.module.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
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 {}
|
||||||
3
src/app/modules/vault-filter/vault-filter.service.ts
Normal file
3
src/app/modules/vault-filter/vault-filter.service.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { VaultFilterService as BaseVaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
|
||||||
|
|
||||||
|
export class VaultFilterService extends BaseVaultFilterService {}
|
||||||
@@ -1,33 +1,42 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
|
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
|
||||||
|
|
||||||
|
import { BaseGuard } from "jslib-angular/guards/base.guard";
|
||||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
import { Permissions } from "jslib-common/enums/permissions";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OrganizationGuardService implements CanActivate {
|
export class PermissionsGuard extends BaseGuard implements CanActivate {
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
router: Router,
|
||||||
|
private organizationService: OrganizationService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService
|
||||||
private organizationService: OrganizationService
|
) {
|
||||||
) {}
|
super(router);
|
||||||
|
}
|
||||||
|
|
||||||
async canActivate(route: ActivatedRouteSnapshot) {
|
async canActivate(route: ActivatedRouteSnapshot) {
|
||||||
const org = await this.organizationService.get(route.params.organizationId);
|
const org = await this.organizationService.get(route.params.organizationId);
|
||||||
if (org == null) {
|
if (org == null) {
|
||||||
this.router.navigate(["/"]);
|
return this.redirect();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!org.isOwner && !org.enabled) {
|
if (!org.isOwner && !org.enabled) {
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"error",
|
"error",
|
||||||
null,
|
null,
|
||||||
this.i18nService.t("organizationIsDisabled")
|
this.i18nService.t("organizationIsDisabled")
|
||||||
);
|
);
|
||||||
this.router.navigate(["/"]);
|
return this.redirect();
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
<app-navbar></app-navbar>
|
|
||||||
<div class="org-nav" *ngIf="organization">
|
<div class="org-nav" *ngIf="organization">
|
||||||
<div class="container d-flex">
|
<div class="container d-flex">
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
@@ -27,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav nav-tabs" *ngIf="showMenuBar">
|
<ul class="nav nav-tabs">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" routerLink="vault" routerLinkActive="active">
|
<a class="nav-link" routerLink="vault" routerLinkActive="active">
|
||||||
<i class="bwi bwi-lock" aria-hidden="true"></i>
|
<i class="bwi bwi-lock" aria-hidden="true"></i>
|
||||||
@@ -46,7 +45,7 @@
|
|||||||
{{ "tools" | i18n }}
|
{{ "tools" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" *ngIf="organization.isOwner">
|
<li class="nav-item" *ngIf="showSettingsTab">
|
||||||
<a class="nav-link" routerLink="settings" routerLinkActive="active">
|
<a class="nav-link" routerLink="settings" routerLinkActive="active">
|
||||||
<i class="bwi bwi-cogs" aria-hidden="true"></i>
|
<i class="bwi bwi-cogs" aria-hidden="true"></i>
|
||||||
{{ "settings" | i18n }}
|
{{ "settings" | i18n }}
|
||||||
@@ -57,4 +56,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
<app-footer></app-footer>
|
|
||||||
@@ -5,6 +5,8 @@ import { BroadcasterService } from "jslib-common/abstractions/broadcaster.servic
|
|||||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||||
import { Organization } from "jslib-common/models/domain/organization";
|
import { Organization } from "jslib-common/models/domain/organization";
|
||||||
|
|
||||||
|
import { NavigationPermissionsService } from "../services/navigation-permissions.service";
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = "OrganizationLayoutComponent";
|
const BroadcasterSubscriptionId = "OrganizationLayoutComponent";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -25,7 +27,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
document.body.classList.remove("layout_frontend");
|
document.body.classList.remove("layout_frontend");
|
||||||
this.route.params.subscribe(async (params) => {
|
this.route.params.subscribe(async (params: any) => {
|
||||||
this.organizationId = params.organizationId;
|
this.organizationId = params.organizationId;
|
||||||
await this.load();
|
await this.load();
|
||||||
});
|
});
|
||||||
@@ -48,23 +50,16 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
|||||||
this.organization = await this.organizationService.get(this.organizationId);
|
this.organization = await this.organizationService.get(this.organizationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
get showMenuBar() {
|
|
||||||
return this.showManageTab || this.showToolsTab || this.organization.isOwner;
|
|
||||||
}
|
|
||||||
|
|
||||||
get showManageTab(): boolean {
|
get showManageTab(): boolean {
|
||||||
return (
|
return NavigationPermissionsService.canAccessManage(this.organization);
|
||||||
this.organization.canManageUsers ||
|
|
||||||
this.organization.canViewAllCollections ||
|
|
||||||
this.organization.canViewAssignedCollections ||
|
|
||||||
this.organization.canManageGroups ||
|
|
||||||
this.organization.canManagePolicies ||
|
|
||||||
this.organization.canAccessEventLogs
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get showToolsTab(): boolean {
|
get showToolsTab(): boolean {
|
||||||
return this.organization.canAccessImportExport || this.organization.canAccessReports;
|
return NavigationPermissionsService.canAccessTools(this.organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
get showSettingsTab(): boolean {
|
||||||
|
return NavigationPermissionsService.canAccessSettings(this.organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
get toolsRoute(): string {
|
get toolsRoute(): string {
|
||||||
217
src/app/organizations/organization-routing.module.ts
Normal file
217
src/app/organizations/organization-routing.module.ts
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
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 {}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Permissions } from "jslib-common/enums/permissions";
|
||||||
|
import { Organization } from "jslib-common/models/domain/organization";
|
||||||
|
|
||||||
|
const permissions = {
|
||||||
|
manage: [
|
||||||
|
Permissions.CreateNewCollections,
|
||||||
|
Permissions.EditAnyCollection,
|
||||||
|
Permissions.DeleteAnyCollection,
|
||||||
|
Permissions.EditAssignedCollections,
|
||||||
|
Permissions.DeleteAssignedCollections,
|
||||||
|
Permissions.AccessEventLogs,
|
||||||
|
Permissions.ManageGroups,
|
||||||
|
Permissions.ManageUsers,
|
||||||
|
Permissions.ManagePolicies,
|
||||||
|
],
|
||||||
|
tools: [Permissions.AccessImportExport, Permissions.AccessReports],
|
||||||
|
settings: [Permissions.ManageOrganization],
|
||||||
|
};
|
||||||
|
|
||||||
|
export class NavigationPermissionsService {
|
||||||
|
static getPermissions(route: keyof typeof permissions | "admin") {
|
||||||
|
if (route === "admin") {
|
||||||
|
return Object.values(permissions).reduce((previous, current) => previous.concat(current), []);
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions[route];
|
||||||
|
}
|
||||||
|
|
||||||
|
static canAccessAdmin(organization: Organization): boolean {
|
||||||
|
return (
|
||||||
|
this.canAccessTools(organization) ||
|
||||||
|
this.canAccessSettings(organization) ||
|
||||||
|
this.canAccessManage(organization)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static canAccessTools(organization: Organization): boolean {
|
||||||
|
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("tools"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static canAccessSettings(organization: Organization): boolean {
|
||||||
|
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("settings"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static canAccessManage(organization: Organization): boolean {
|
||||||
|
return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("manage"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { Component } from "@angular/core";
|
|
||||||
|
|
||||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
|
||||||
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
|
||||||
import { FolderService } from "jslib-common/abstractions/folder.service";
|
|
||||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
|
||||||
import { StateService } from "jslib-common/abstractions/state.service";
|
|
||||||
import { CollectionData } from "jslib-common/models/data/collectionData";
|
|
||||||
import { Collection } from "jslib-common/models/domain/collection";
|
|
||||||
import { Organization } from "jslib-common/models/domain/organization";
|
|
||||||
import { CollectionDetailsResponse } from "jslib-common/models/response/collectionResponse";
|
|
||||||
import { CollectionView } from "jslib-common/models/view/collectionView";
|
|
||||||
|
|
||||||
import { GroupingsComponent as BaseGroupingsComponent } from "../../vault/groupings.component";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "app-org-vault-groupings",
|
|
||||||
templateUrl: "../../vault/groupings.component.html",
|
|
||||||
})
|
|
||||||
export class GroupingsComponent extends BaseGroupingsComponent {
|
|
||||||
organization: Organization;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
collectionService: CollectionService,
|
|
||||||
folderService: FolderService,
|
|
||||||
stateService: StateService,
|
|
||||||
private apiService: ApiService,
|
|
||||||
private i18nService: I18nService
|
|
||||||
) {
|
|
||||||
super(collectionService, folderService, stateService);
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadCollections() {
|
|
||||||
if (!this.organization.canEditAnyCollection) {
|
|
||||||
await super.loadCollections(this.organization.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const collections = await this.apiService.getCollections(this.organization.id);
|
|
||||||
if (collections != null && collections.data != null && collections.data.length) {
|
|
||||||
const collectionDomains = collections.data.map(
|
|
||||||
(r) => new Collection(new CollectionData(r as CollectionDetailsResponse))
|
|
||||||
);
|
|
||||||
this.collections = await this.collectionService.decryptMany(collectionDomains);
|
|
||||||
} else {
|
|
||||||
this.collections = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const unassignedCollection = new CollectionView();
|
|
||||||
unassignedCollection.name = this.i18nService.t("unassigned");
|
|
||||||
unassignedCollection.id = "unassigned";
|
|
||||||
unassignedCollection.organizationId = this.organization.id;
|
|
||||||
unassignedCollection.readOnly = true;
|
|
||||||
this.collections.push(unassignedCollection);
|
|
||||||
this.nestedCollections = await this.collectionService.getAllNested(this.collections);
|
|
||||||
}
|
|
||||||
|
|
||||||
async collapse(grouping: CollectionView) {
|
|
||||||
await super.collapse(grouping, "org_");
|
|
||||||
}
|
|
||||||
|
|
||||||
isCollapsed(grouping: CollectionView) {
|
|
||||||
return super.isCollapsed(grouping, "org_");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,26 @@
|
|||||||
<div class="container page-content">
|
<div class="container page-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
<app-org-vault-groupings
|
<div class="groupings">
|
||||||
|
<div class="content">
|
||||||
|
<div class="inner-content">
|
||||||
|
<app-vault-filter
|
||||||
|
#vaultFilter
|
||||||
[showFolders]="false"
|
[showFolders]="false"
|
||||||
[showFavorites]="false"
|
[showFavorites]="false"
|
||||||
[showTrash]="true"
|
[activeFilter]="activeFilter"
|
||||||
(onAllClicked)="clearGroupingFilters()"
|
[showOrgFilter]="false"
|
||||||
(onCipherTypeClicked)="filterCipherType($event)"
|
(onFilterChange)="applyVaultFilter($event)"
|
||||||
(onCollectionClicked)="filterCollection($event.id)"
|
|
||||||
(onSearchTextChanged)="filterSearchText($event)"
|
(onSearchTextChanged)="filterSearchText($event)"
|
||||||
(onTrashClicked)="filterDeleted()"
|
></app-vault-filter>
|
||||||
>
|
</div>
|
||||||
</app-org-vault-groupings>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-9">
|
<div class="col-9">
|
||||||
<div class="page-header d-flex">
|
<div class="page-header d-flex">
|
||||||
<h1>
|
<h1>
|
||||||
{{ "vault" | i18n }}
|
{{ "vaultItems" | i18n }}
|
||||||
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
|
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
|
||||||
<ng-container *ngIf="actionSpinner.loading">
|
<ng-container *ngIf="actionSpinner.loading">
|
||||||
<i
|
<i
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { first } from "rxjs/operators";
|
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 { ModalService } from "jslib-angular/services/modal.service";
|
||||||
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
@@ -27,7 +28,7 @@ import { AddEditComponent } from "./add-edit.component";
|
|||||||
import { AttachmentsComponent } from "./attachments.component";
|
import { AttachmentsComponent } from "./attachments.component";
|
||||||
import { CiphersComponent } from "./ciphers.component";
|
import { CiphersComponent } from "./ciphers.component";
|
||||||
import { CollectionsComponent } from "./collections.component";
|
import { CollectionsComponent } from "./collections.component";
|
||||||
import { GroupingsComponent } from "./groupings.component";
|
import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = "OrgVaultComponent";
|
const BroadcasterSubscriptionId = "OrgVaultComponent";
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ const BroadcasterSubscriptionId = "OrgVaultComponent";
|
|||||||
templateUrl: "vault.component.html",
|
templateUrl: "vault.component.html",
|
||||||
})
|
})
|
||||||
export class VaultComponent implements OnInit, OnDestroy {
|
export class VaultComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
|
@ViewChild("vaultFilter", { static: true }) vaultFilterComponent: VaultFilterComponent;
|
||||||
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
|
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
|
||||||
@ViewChild("attachments", { read: ViewContainerRef, static: true })
|
@ViewChild("attachments", { read: ViewContainerRef, static: true })
|
||||||
attachmentsModalRef: ViewContainerRef;
|
attachmentsModalRef: ViewContainerRef;
|
||||||
@@ -52,6 +53,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
type: CipherType = null;
|
type: CipherType = null;
|
||||||
deleted = false;
|
deleted = false;
|
||||||
trashCleanupWarning: string = null;
|
trashCleanupWarning: string = null;
|
||||||
|
activeFilter: VaultFilter = new VaultFilter();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@@ -75,11 +77,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
this.route.parent.params.pipe(first()).subscribe(async (params) => {
|
this.route.parent.params.pipe(first()).subscribe(async (params) => {
|
||||||
this.organization = await this.organizationService.get(params.organizationId);
|
this.organization = await this.organizationService.get(params.organizationId);
|
||||||
this.groupingsComponent.organization = this.organization;
|
this.vaultFilterComponent.organization = this.organization;
|
||||||
this.ciphersComponent.organization = this.organization;
|
this.ciphersComponent.organization = this.organization;
|
||||||
|
|
||||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||||
this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search;
|
// this.ciphersComponent.searchText = this.vaultFilterComponent.search = qParams.search;
|
||||||
if (!this.organization.canViewAllCollections) {
|
if (!this.organization.canViewAllCollections) {
|
||||||
await this.syncService.fullSync(false);
|
await this.syncService.fullSync(false);
|
||||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||||
@@ -88,7 +90,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
case "syncCompleted":
|
case "syncCompleted":
|
||||||
if (message.successfully) {
|
if (message.successfully) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.groupingsComponent.load(),
|
this.vaultFilterComponent.reloadCollectionsAndFolders(
|
||||||
|
new VaultFilter({
|
||||||
|
selectedOrganizationId: this.organization.id,
|
||||||
|
} as Partial<VaultFilter>)
|
||||||
|
),
|
||||||
this.ciphersComponent.refresh(),
|
this.ciphersComponent.refresh(),
|
||||||
]);
|
]);
|
||||||
this.changeDetectorRef.detectChanges();
|
this.changeDetectorRef.detectChanges();
|
||||||
@@ -98,27 +104,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await this.groupingsComponent.load();
|
await this.vaultFilterComponent.reloadCollectionsAndFolders(
|
||||||
|
new VaultFilter({ selectedOrganizationId: this.organization.id } as Partial<VaultFilter>)
|
||||||
if (qParams == null) {
|
);
|
||||||
this.groupingsComponent.selectedAll = true;
|
|
||||||
await this.ciphersComponent.reload();
|
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) {
|
if (qParams.viewEvents != null) {
|
||||||
const cipher = this.ciphersComponent.ciphers.filter((c) => c.id === qParams.viewEvents);
|
const cipher = this.ciphersComponent.ciphers.filter((c) => c.id === qParams.viewEvents);
|
||||||
@@ -134,63 +123,46 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearGroupingFilters() {
|
async applyVaultFilter(vaultFilter: VaultFilter) {
|
||||||
this.ciphersComponent.showAddNew = true;
|
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
|
||||||
this.ciphersComponent.deleted = false;
|
this.activeFilter = vaultFilter;
|
||||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchVault");
|
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
|
||||||
await this.ciphersComponent.applyFilter();
|
this.go();
|
||||||
this.clearFilters();
|
|
||||||
this.go();
|
this.go();
|
||||||
}
|
}
|
||||||
|
|
||||||
async filterCipherType(type: CipherType, load = false) {
|
private buildFilter(): (cipher: CipherView) => boolean {
|
||||||
this.ciphersComponent.showAddNew = true;
|
return (cipher) => {
|
||||||
this.ciphersComponent.deleted = false;
|
let cipherPassesFilter = true;
|
||||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchType");
|
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
|
||||||
const filter = (c: CipherView) => c.type === type;
|
cipherPassesFilter = cipher.favorite;
|
||||||
if (load) {
|
|
||||||
await this.ciphersComponent.reload(filter);
|
|
||||||
} else {
|
|
||||||
await this.ciphersComponent.applyFilter(filter);
|
|
||||||
}
|
}
|
||||||
this.clearFilters();
|
if (this.activeFilter.status === "trash" && cipherPassesFilter) {
|
||||||
this.type = type;
|
cipherPassesFilter = cipher.isDeleted;
|
||||||
this.go();
|
|
||||||
}
|
}
|
||||||
|
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
|
||||||
async filterCollection(collectionId: string, load = false) {
|
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
|
||||||
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.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) {
|
filterSearchText(searchText: string) {
|
||||||
@@ -232,7 +204,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
(comp) => {
|
(comp) => {
|
||||||
if (this.organization.canEditAnyCollection) {
|
if (this.organization.canEditAnyCollection) {
|
||||||
comp.collectionIds = cipher.collectionIds;
|
comp.collectionIds = cipher.collectionIds;
|
||||||
comp.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
comp.collections = this.vaultFilterComponent.collections.fullList.filter(
|
||||||
|
(c) => !c.readOnly
|
||||||
|
);
|
||||||
}
|
}
|
||||||
comp.organization = this.organization;
|
comp.organization = this.organization;
|
||||||
comp.cipherId = cipher.id;
|
comp.cipherId = cipher.id;
|
||||||
@@ -249,7 +223,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
component.organizationId = this.organization.id;
|
component.organizationId = this.organization.id;
|
||||||
component.type = this.type;
|
component.type = this.type;
|
||||||
if (this.organization.canEditAnyCollection) {
|
if (this.organization.canEditAnyCollection) {
|
||||||
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
component.collections = this.vaultFilterComponent.collections.fullList.filter(
|
||||||
|
(c) => !c.readOnly
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (this.collectionId != null) {
|
if (this.collectionId != null) {
|
||||||
component.collectionIds = [this.collectionId];
|
component.collectionIds = [this.collectionId];
|
||||||
@@ -286,7 +262,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
component.cloneMode = true;
|
component.cloneMode = true;
|
||||||
component.organizationId = this.organization.id;
|
component.organizationId = this.organization.id;
|
||||||
if (this.organization.canEditAnyCollection) {
|
if (this.organization.canEditAnyCollection) {
|
||||||
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
component.collections = this.vaultFilterComponent.collections.fullList.filter(
|
||||||
|
(c) => !c.readOnly
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
|
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
|
||||||
// in the add-edit componenet
|
// in the add-edit componenet
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
import { RouterModule, Routes } from "@angular/router";
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
|
|
||||||
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
|
import { AuthGuard } from "jslib-angular/guards/auth.guard";
|
||||||
import { LockGuardService } from "jslib-angular/services/lock-guard.service";
|
import { LockGuard } from "jslib-angular/guards/lock.guard";
|
||||||
import { UnauthGuardService } from "jslib-angular/services/unauth-guard.service";
|
import { UnauthGuard } from "jslib-angular/guards/unauth.guard";
|
||||||
import { Permissions } from "jslib-common/enums/permissions";
|
|
||||||
|
|
||||||
import { AcceptEmergencyComponent } from "./accounts/accept-emergency.component";
|
import { AcceptEmergencyComponent } from "./accounts/accept-emergency.component";
|
||||||
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
|
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
|
||||||
@@ -23,44 +22,20 @@ import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.com
|
|||||||
import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.component";
|
import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.component";
|
||||||
import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component";
|
import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component";
|
||||||
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
|
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
|
||||||
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
|
|
||||||
import { UserLayoutComponent } from "./layouts/user-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 { 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 { AccessComponent } from "./send/access.component";
|
||||||
import { SendComponent } from "./send/send.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 { AccountComponent } from "./settings/account.component";
|
||||||
import { CreateOrganizationComponent } from "./settings/create-organization.component";
|
import { CreateOrganizationComponent } from "./settings/create-organization.component";
|
||||||
import { DomainRulesComponent } from "./settings/domain-rules.component";
|
import { DomainRulesComponent } from "./settings/domain-rules.component";
|
||||||
import { EmergencyAccessViewComponent } from "./settings/emergency-access-view.component";
|
import { EmergencyAccessViewComponent } from "./settings/emergency-access-view.component";
|
||||||
import { EmergencyAccessComponent } from "./settings/emergency-access.component";
|
import { EmergencyAccessComponent } from "./settings/emergency-access.component";
|
||||||
import { OptionsComponent } from "./settings/options.component";
|
|
||||||
import { OrganizationsComponent } from "./settings/organizations.component";
|
import { OrganizationsComponent } from "./settings/organizations.component";
|
||||||
|
import { PreferencesComponent } from "./settings/preferences.component";
|
||||||
import { PremiumComponent } from "./settings/premium.component";
|
import { PremiumComponent } from "./settings/premium.component";
|
||||||
import { SettingsComponent } from "./settings/settings.component";
|
import { SettingsComponent } from "./settings/settings.component";
|
||||||
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
|
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
|
||||||
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
|
|
||||||
import { UserBillingComponent } from "./settings/user-billing.component";
|
import { UserBillingComponent } from "./settings/user-billing.component";
|
||||||
import { UserSubscriptionComponent } from "./settings/user-subscription.component";
|
import { UserSubscriptionComponent } from "./settings/user-subscription.component";
|
||||||
import { ExportComponent } from "./tools/export.component";
|
import { ExportComponent } from "./tools/export.component";
|
||||||
@@ -74,18 +49,18 @@ const routes: Routes = [
|
|||||||
path: "",
|
path: "",
|
||||||
component: FrontendLayoutComponent,
|
component: FrontendLayoutComponent,
|
||||||
children: [
|
children: [
|
||||||
{ path: "", pathMatch: "full", component: LoginComponent, canActivate: [UnauthGuardService] },
|
{ path: "", pathMatch: "full", component: LoginComponent, canActivate: [UnauthGuard] },
|
||||||
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuardService] },
|
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard] },
|
||||||
{
|
{
|
||||||
path: "register",
|
path: "register",
|
||||||
component: RegisterComponent,
|
component: RegisterComponent,
|
||||||
canActivate: [UnauthGuardService],
|
canActivate: [UnauthGuard],
|
||||||
data: { titleId: "createAccount" },
|
data: { titleId: "createAccount" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "sso",
|
path: "sso",
|
||||||
component: SsoComponent,
|
component: SsoComponent,
|
||||||
canActivate: [UnauthGuardService],
|
canActivate: [UnauthGuard],
|
||||||
data: { titleId: "enterpriseSingleSignOn" },
|
data: { titleId: "enterpriseSingleSignOn" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -96,13 +71,13 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "hint",
|
path: "hint",
|
||||||
component: HintComponent,
|
component: HintComponent,
|
||||||
canActivate: [UnauthGuardService],
|
canActivate: [UnauthGuard],
|
||||||
data: { titleId: "passwordHint" },
|
data: { titleId: "passwordHint" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "lock",
|
path: "lock",
|
||||||
component: LockComponent,
|
component: LockComponent,
|
||||||
canActivate: [LockGuardService],
|
canActivate: [LockGuard],
|
||||||
},
|
},
|
||||||
{ path: "verify-email", component: VerifyEmailTokenComponent },
|
{ path: "verify-email", component: VerifyEmailTokenComponent },
|
||||||
{
|
{
|
||||||
@@ -119,19 +94,19 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "recover-2fa",
|
path: "recover-2fa",
|
||||||
component: RecoverTwoFactorComponent,
|
component: RecoverTwoFactorComponent,
|
||||||
canActivate: [UnauthGuardService],
|
canActivate: [UnauthGuard],
|
||||||
data: { titleId: "recoverAccountTwoStep" },
|
data: { titleId: "recoverAccountTwoStep" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "recover-delete",
|
path: "recover-delete",
|
||||||
component: RecoverDeleteComponent,
|
component: RecoverDeleteComponent,
|
||||||
canActivate: [UnauthGuardService],
|
canActivate: [UnauthGuard],
|
||||||
data: { titleId: "deleteAccount" },
|
data: { titleId: "deleteAccount" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "verify-recover-delete",
|
path: "verify-recover-delete",
|
||||||
component: VerifyRecoverDeleteComponent,
|
component: VerifyRecoverDeleteComponent,
|
||||||
canActivate: [UnauthGuardService],
|
canActivate: [UnauthGuard],
|
||||||
data: { titleId: "deleteAccount" },
|
data: { titleId: "deleteAccount" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -142,19 +117,19 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "update-temp-password",
|
path: "update-temp-password",
|
||||||
component: UpdateTempPasswordComponent,
|
component: UpdateTempPasswordComponent,
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuard],
|
||||||
data: { titleId: "updateTempPassword" },
|
data: { titleId: "updateTempPassword" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "update-password",
|
path: "update-password",
|
||||||
component: UpdatePasswordComponent,
|
component: UpdatePasswordComponent,
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuard],
|
||||||
data: { titleId: "updatePassword" },
|
data: { titleId: "updatePassword" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "remove-password",
|
path: "remove-password",
|
||||||
component: RemovePasswordComponent,
|
component: RemovePasswordComponent,
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuard],
|
||||||
data: { titleId: "removeMasterPassword" },
|
data: { titleId: "removeMasterPassword" },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -162,9 +137,9 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
component: UserLayoutComponent,
|
component: UserLayoutComponent,
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuard],
|
||||||
children: [
|
children: [
|
||||||
{ path: "vault", component: VaultComponent, data: { titleId: "myVault" } },
|
{ path: "vault", component: VaultComponent, data: { titleId: "vaults" } },
|
||||||
{ path: "sends", component: SendComponent, data: { title: "Send" } },
|
{ path: "sends", component: SendComponent, data: { title: "Send" } },
|
||||||
{
|
{
|
||||||
path: "settings",
|
path: "settings",
|
||||||
@@ -172,17 +147,20 @@ const routes: Routes = [
|
|||||||
children: [
|
children: [
|
||||||
{ path: "", pathMatch: "full", redirectTo: "account" },
|
{ path: "", pathMatch: "full", redirectTo: "account" },
|
||||||
{ path: "account", component: AccountComponent, data: { titleId: "myAccount" } },
|
{ path: "account", component: AccountComponent, data: { titleId: "myAccount" } },
|
||||||
{ path: "options", component: OptionsComponent, data: { titleId: "options" } },
|
{
|
||||||
|
path: "preferences",
|
||||||
|
component: PreferencesComponent,
|
||||||
|
data: { titleId: "preferences" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "security",
|
||||||
|
loadChildren: async () => (await import("./settings/security.module")).SecurityModule,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "domain-rules",
|
path: "domain-rules",
|
||||||
component: DomainRulesComponent,
|
component: DomainRulesComponent,
|
||||||
data: { titleId: "domainRules" },
|
data: { titleId: "domainRules" },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "two-factor",
|
|
||||||
component: TwoFactorSetupComponent,
|
|
||||||
data: { titleId: "twoStepLogin" },
|
|
||||||
},
|
|
||||||
{ path: "premium", component: PremiumComponent, data: { titleId: "goPremium" } },
|
{ path: "premium", component: PremiumComponent, data: { titleId: "goPremium" } },
|
||||||
{ path: "billing", component: UserBillingComponent, data: { titleId: "billing" } },
|
{ path: "billing", component: UserBillingComponent, data: { titleId: "billing" } },
|
||||||
{
|
{
|
||||||
@@ -225,7 +203,7 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "tools",
|
path: "tools",
|
||||||
component: ToolsComponent,
|
component: ToolsComponent,
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuard],
|
||||||
children: [
|
children: [
|
||||||
{ path: "", pathMatch: "full", redirectTo: "generator" },
|
{ path: "", pathMatch: "full", redirectTo: "generator" },
|
||||||
{ path: "import", component: ImportComponent, data: { titleId: "importData" } },
|
{ path: "import", component: ImportComponent, data: { titleId: "importData" } },
|
||||||
@@ -242,191 +220,12 @@ const routes: Routes = [
|
|||||||
loadChildren: async () => (await import("./reports/reports.module")).ReportsModule,
|
loadChildren: async () => (await import("./reports/reports.module")).ReportsModule,
|
||||||
},
|
},
|
||||||
{ path: "setup/families-for-enterprise", component: FamiliesForEnterpriseSetupComponent },
|
{ path: "setup/families-for-enterprise", component: FamiliesForEnterpriseSetupComponent },
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "organizations/:organizationId",
|
path: "organizations",
|
||||||
component: OrganizationLayoutComponent,
|
loadChildren: () =>
|
||||||
canActivate: [AuthGuardService, OrganizationGuardService],
|
import("./organizations/organization-routing.module").then(
|
||||||
children: [
|
(m) => m.OrganizationsRoutingModule
|
||||||
{ path: "", pathMatch: "full", redirectTo: "vault" },
|
),
|
||||||
{ path: "vault", component: OrgVaultComponent, data: { titleId: "vault" } },
|
|
||||||
{
|
|
||||||
path: "tools",
|
|
||||||
component: OrgToolsComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: { permissions: [Permissions.AccessImportExport, Permissions.AccessReports] },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: "",
|
|
||||||
pathMatch: "full",
|
|
||||||
redirectTo: "import",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "import",
|
|
||||||
component: OrgImportComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: "importData",
|
|
||||||
permissions: [Permissions.AccessImportExport],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "export",
|
|
||||||
component: OrgExportComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: "exportVault",
|
|
||||||
permissions: [Permissions.AccessImportExport],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "exposed-passwords-report",
|
|
||||||
component: OrgExposedPasswordsReportComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: "exposedPasswordsReport",
|
|
||||||
permissions: [Permissions.AccessReports],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "inactive-two-factor-report",
|
|
||||||
component: OrgInactiveTwoFactorReportComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: "inactive2faReport",
|
|
||||||
permissions: [Permissions.AccessReports],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "reused-passwords-report",
|
|
||||||
component: OrgReusedPasswordsReportComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: "reusedPasswordsReport",
|
|
||||||
permissions: [Permissions.AccessReports],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "unsecured-websites-report",
|
|
||||||
component: OrgUnsecuredWebsitesReportComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: "unsecuredWebsitesReport",
|
|
||||||
permissions: [Permissions.AccessReports],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "weak-passwords-report",
|
|
||||||
component: OrgWeakPasswordsReportComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: "weakPasswordsReport",
|
|
||||||
permissions: [Permissions.AccessReports],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "manage",
|
|
||||||
component: OrgManageComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
permissions: [
|
|
||||||
Permissions.CreateNewCollections,
|
|
||||||
Permissions.EditAnyCollection,
|
|
||||||
Permissions.DeleteAnyCollection,
|
|
||||||
Permissions.EditAssignedCollections,
|
|
||||||
Permissions.DeleteAssignedCollections,
|
|
||||||
Permissions.AccessEventLogs,
|
|
||||||
Permissions.ManageGroups,
|
|
||||||
Permissions.ManageUsers,
|
|
||||||
Permissions.ManagePolicies,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: "",
|
|
||||||
pathMatch: "full",
|
|
||||||
redirectTo: "people",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "collections",
|
|
||||||
component: OrgManageCollectionsComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: "collections",
|
|
||||||
permissions: [
|
|
||||||
Permissions.CreateNewCollections,
|
|
||||||
Permissions.EditAnyCollection,
|
|
||||||
Permissions.DeleteAnyCollection,
|
|
||||||
Permissions.EditAssignedCollections,
|
|
||||||
Permissions.DeleteAssignedCollections,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "events",
|
|
||||||
component: OrgEventsComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: "eventLogs",
|
|
||||||
permissions: [Permissions.AccessEventLogs],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "groups",
|
|
||||||
component: OrgGroupsComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: "groups",
|
|
||||||
permissions: [Permissions.ManageGroups],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "people",
|
|
||||||
component: OrgPeopleComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: "people",
|
|
||||||
permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "policies",
|
|
||||||
component: OrgPoliciesComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: "policies",
|
|
||||||
permissions: [Permissions.ManagePolicies],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "settings",
|
|
||||||
component: OrgSettingsComponent,
|
|
||||||
canActivate: [OrganizationTypeGuardService],
|
|
||||||
data: { permissions: [Permissions.ManageOrganization] },
|
|
||||||
children: [
|
|
||||||
{ path: "", pathMatch: "full", redirectTo: "account" },
|
|
||||||
{ path: "account", component: OrgAccountComponent, data: { titleId: "myOrganization" } },
|
|
||||||
{
|
|
||||||
path: "two-factor",
|
|
||||||
component: OrgTwoFactorSetupComponent,
|
|
||||||
data: { titleId: "twoStepLogin" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "billing",
|
|
||||||
component: OrganizationBillingComponent,
|
|
||||||
data: { titleId: "billing" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "subscription",
|
|
||||||
component: OrganizationSubscriptionComponent,
|
|
||||||
data: { titleId: "subscription" },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -437,7 +236,7 @@ const routes: Routes = [
|
|||||||
RouterModule.forRoot(routes, {
|
RouterModule.forRoot(routes, {
|
||||||
useHash: true,
|
useHash: true,
|
||||||
paramsInheritanceStrategy: "always",
|
paramsInheritanceStrategy: "always",
|
||||||
/*enableTracing: true,*/
|
// enableTracing: true,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ import localeZhTw from "@angular/common/locales/zh-Hant";
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||||
import { RouterModule } from "@angular/router";
|
import { RouterModule } from "@angular/router";
|
||||||
import { BadgeModule, ButtonModule } from "@bitwarden/components";
|
import { BadgeModule, ButtonModule, CalloutModule } from "@bitwarden/components";
|
||||||
import { InfiniteScrollModule } from "ngx-infinite-scroll";
|
import { InfiniteScrollModule } from "ngx-infinite-scroll";
|
||||||
import { ToastrModule } from "ngx-toastr";
|
import { ToastrModule } from "ngx-toastr";
|
||||||
|
|
||||||
@@ -84,8 +84,9 @@ import { PremiumBadgeComponent } from "./components/premium-badge.component";
|
|||||||
import { FooterComponent } from "./layouts/footer.component";
|
import { FooterComponent } from "./layouts/footer.component";
|
||||||
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
|
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
|
||||||
import { NavbarComponent } from "./layouts/navbar.component";
|
import { NavbarComponent } from "./layouts/navbar.component";
|
||||||
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
|
|
||||||
import { UserLayoutComponent } from "./layouts/user-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 { BulkConfirmComponent as OrgBulkConfirmComponent } from "./organizations/manage/bulk/bulk-confirm.component";
|
||||||
import { BulkRemoveComponent as OrgBulkRemoveComponent } from "./organizations/manage/bulk/bulk-remove.component";
|
import { BulkRemoveComponent as OrgBulkRemoveComponent } from "./organizations/manage/bulk/bulk-remove.component";
|
||||||
import { BulkStatusComponent as OrgBulkStatusComponent } from "./organizations/manage/bulk/bulk-status.component";
|
import { BulkStatusComponent as OrgBulkStatusComponent } from "./organizations/manage/bulk/bulk-status.component";
|
||||||
@@ -135,7 +136,7 @@ import { AddEditComponent as OrgAddEditComponent } from "./organizations/vault/a
|
|||||||
import { AttachmentsComponent as OrgAttachmentsComponent } from "./organizations/vault/attachments.component";
|
import { AttachmentsComponent as OrgAttachmentsComponent } from "./organizations/vault/attachments.component";
|
||||||
import { CiphersComponent as OrgCiphersComponent } from "./organizations/vault/ciphers.component";
|
import { CiphersComponent as OrgCiphersComponent } from "./organizations/vault/ciphers.component";
|
||||||
import { CollectionsComponent as OrgCollectionsComponent } from "./organizations/vault/collections.component";
|
import { CollectionsComponent as OrgCollectionsComponent } from "./organizations/vault/collections.component";
|
||||||
import { GroupingsComponent as OrgGroupingsComponent } from "./organizations/vault/groupings.component";
|
import { VaultFilterComponent as OrgGroupingsComponent } from "./organizations/vault/vault-filter/vault-filter.component";
|
||||||
import { VaultComponent as OrgVaultComponent } from "./organizations/vault/vault.component";
|
import { VaultComponent as OrgVaultComponent } from "./organizations/vault/vault.component";
|
||||||
import { ProvidersComponent } from "./providers/providers.component";
|
import { ProvidersComponent } from "./providers/providers.component";
|
||||||
import { BreachReportComponent } from "./reports/breach-report.component";
|
import { BreachReportComponent } from "./reports/breach-report.component";
|
||||||
@@ -171,13 +172,15 @@ import { EmergencyAccessViewComponent } from "./settings/emergency-access-view.c
|
|||||||
import { EmergencyAccessComponent } from "./settings/emergency-access.component";
|
import { EmergencyAccessComponent } from "./settings/emergency-access.component";
|
||||||
import { EmergencyAddEditComponent } from "./settings/emergency-add-edit.component";
|
import { EmergencyAddEditComponent } from "./settings/emergency-add-edit.component";
|
||||||
import { LinkSsoComponent } from "./settings/link-sso.component";
|
import { LinkSsoComponent } from "./settings/link-sso.component";
|
||||||
import { OptionsComponent } from "./settings/options.component";
|
|
||||||
import { OrganizationPlansComponent } from "./settings/organization-plans.component";
|
import { OrganizationPlansComponent } from "./settings/organization-plans.component";
|
||||||
import { OrganizationsComponent } from "./settings/organizations.component";
|
import { OrganizationsComponent } from "./settings/organizations.component";
|
||||||
import { PaymentComponent } from "./settings/payment.component";
|
import { PaymentComponent } from "./settings/payment.component";
|
||||||
|
import { PreferencesComponent } from "./settings/preferences.component";
|
||||||
import { PremiumComponent } from "./settings/premium.component";
|
import { PremiumComponent } from "./settings/premium.component";
|
||||||
import { ProfileComponent } from "./settings/profile.component";
|
import { ProfileComponent } from "./settings/profile.component";
|
||||||
import { PurgeVaultComponent } from "./settings/purge-vault.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 { SettingsComponent } from "./settings/settings.component";
|
||||||
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
|
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
|
||||||
import { SponsoringOrgRowComponent } from "./settings/sponsoring-org-row.component";
|
import { SponsoringOrgRowComponent } from "./settings/sponsoring-org-row.component";
|
||||||
@@ -212,7 +215,6 @@ import { BulkShareComponent } from "./vault/bulk-share.component";
|
|||||||
import { CiphersComponent } from "./vault/ciphers.component";
|
import { CiphersComponent } from "./vault/ciphers.component";
|
||||||
import { CollectionsComponent } from "./vault/collections.component";
|
import { CollectionsComponent } from "./vault/collections.component";
|
||||||
import { FolderAddEditComponent } from "./vault/folder-add-edit.component";
|
import { FolderAddEditComponent } from "./vault/folder-add-edit.component";
|
||||||
import { GroupingsComponent } from "./vault/groupings.component";
|
|
||||||
import { ShareComponent } from "./vault/share.component";
|
import { ShareComponent } from "./vault/share.component";
|
||||||
import { VaultComponent } from "./vault/vault.component";
|
import { VaultComponent } from "./vault/vault.component";
|
||||||
|
|
||||||
@@ -273,9 +275,13 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
DragDropModule,
|
DragDropModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
InfiniteScrollModule,
|
InfiniteScrollModule,
|
||||||
|
VaultFilterModule,
|
||||||
JslibModule,
|
JslibModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
|
BadgeModule,
|
||||||
|
ButtonModule,
|
||||||
|
CalloutModule,
|
||||||
ToastrModule,
|
ToastrModule,
|
||||||
BadgeModule,
|
BadgeModule,
|
||||||
ButtonModule,
|
ButtonModule,
|
||||||
@@ -327,7 +333,6 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
FolderAddEditComponent,
|
FolderAddEditComponent,
|
||||||
FooterComponent,
|
FooterComponent,
|
||||||
FrontendLayoutComponent,
|
FrontendLayoutComponent,
|
||||||
GroupingsComponent,
|
|
||||||
HintComponent,
|
HintComponent,
|
||||||
ImportComponent,
|
ImportComponent,
|
||||||
InactiveTwoFactorReportComponent,
|
InactiveTwoFactorReportComponent,
|
||||||
@@ -337,7 +342,6 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
MasterPasswordPolicyComponent,
|
MasterPasswordPolicyComponent,
|
||||||
NavbarComponent,
|
NavbarComponent,
|
||||||
NestedCheckboxComponent,
|
NestedCheckboxComponent,
|
||||||
OptionsComponent,
|
|
||||||
OrgAccountComponent,
|
OrgAccountComponent,
|
||||||
OrgAddEditComponent,
|
OrgAddEditComponent,
|
||||||
OrganizationBillingComponent,
|
OrganizationBillingComponent,
|
||||||
@@ -385,6 +389,8 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
PasswordStrengthComponent,
|
PasswordStrengthComponent,
|
||||||
PaymentComponent,
|
PaymentComponent,
|
||||||
PersonalOwnershipPolicyComponent,
|
PersonalOwnershipPolicyComponent,
|
||||||
|
PreferencesComponent,
|
||||||
|
PremiumBadgeComponent,
|
||||||
PremiumComponent,
|
PremiumComponent,
|
||||||
ProfileComponent,
|
ProfileComponent,
|
||||||
ProvidersComponent,
|
ProvidersComponent,
|
||||||
@@ -399,6 +405,8 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
RequireSsoPolicyComponent,
|
RequireSsoPolicyComponent,
|
||||||
ResetPasswordPolicyComponent,
|
ResetPasswordPolicyComponent,
|
||||||
ReusedPasswordsReportComponent,
|
ReusedPasswordsReportComponent,
|
||||||
|
SecurityComponent,
|
||||||
|
SecurityKeysComponent,
|
||||||
SendAddEditComponent,
|
SendAddEditComponent,
|
||||||
SendComponent,
|
SendComponent,
|
||||||
SendEffluxDatesComponent,
|
SendEffluxDatesComponent,
|
||||||
|
|||||||
@@ -1,29 +1,5 @@
|
|||||||
<ng-container *ngIf="vault">
|
<app-navbar></app-navbar>
|
||||||
<p *ngIf="!loaded" class="text-muted">
|
<div class="container page-content">
|
||||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
|
||||||
</p>
|
|
||||||
<ng-container *ngIf="loaded">
|
|
||||||
<ul class="bwi-ul card-ul carets" *ngIf="providers && providers.length">
|
|
||||||
<li *ngFor="let p of providers">
|
|
||||||
<a [routerLink]="['/providers', p.id]" class="text-body">
|
|
||||||
<i class="bwi bwi-li bwi-caret-right" aria-hidden="true"></i> {{ p.name }}
|
|
||||||
<ng-container *ngIf="!p.enabled">
|
|
||||||
<i
|
|
||||||
class="bwi bwi-exclamation-triangle text-danger"
|
|
||||||
title="{{ 'providerIsDisabled' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span class="sr-only">{{ "providerIsDisabled" | i18n }}</span>
|
|
||||||
</ng-container>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!vault">
|
|
||||||
<app-navbar></app-navbar>
|
|
||||||
<div class="container page-content">
|
|
||||||
<div class="page-header d-flex">
|
<div class="page-header d-flex">
|
||||||
<h1>{{ "providers" | i18n }}</h1>
|
<h1>{{ "providers" | i18n }}</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,6 +29,5 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<app-footer></app-footer>
|
<app-footer></app-footer>
|
||||||
</ng-container>
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, Input, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||||
@@ -10,8 +10,6 @@ import { Provider } from "jslib-common/models/domain/provider";
|
|||||||
templateUrl: "providers.component.html",
|
templateUrl: "providers.component.html",
|
||||||
})
|
})
|
||||||
export class ProvidersComponent implements OnInit {
|
export class ProvidersComponent implements OnInit {
|
||||||
@Input() vault = false;
|
|
||||||
|
|
||||||
providers: Provider[];
|
providers: Provider[];
|
||||||
loaded = false;
|
loaded = false;
|
||||||
actionPromise: Promise<any>;
|
actionPromise: Promise<any>;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
import { RouterModule, Routes } from "@angular/router";
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
|
|
||||||
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
|
import { AuthGuard } from "jslib-angular/guards/auth.guard";
|
||||||
|
|
||||||
import { BreachReportComponent } from "./breach-report.component";
|
import { BreachReportComponent } from "./breach-report.component";
|
||||||
import { ExposedPasswordsReportComponent } from "./exposed-passwords-report.component";
|
import { ExposedPasswordsReportComponent } from "./exposed-passwords-report.component";
|
||||||
@@ -16,7 +16,7 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
component: ReportsComponent,
|
component: ReportsComponent,
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuard],
|
||||||
children: [
|
children: [
|
||||||
{ path: "", pathMatch: "full", component: ReportListComponent, data: { homepage: true } },
|
{ path: "", pathMatch: "full", component: ReportListComponent, data: { homepage: true } },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import { Injectable } from "@angular/core";
|
|
||||||
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
|
|
||||||
|
|
||||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
|
||||||
import { Permissions } from "jslib-common/enums/permissions";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class OrganizationTypeGuardService implements CanActivate {
|
|
||||||
constructor(private organizationService: OrganizationService, private router: Router) {}
|
|
||||||
|
|
||||||
async canActivate(route: ActivatedRouteSnapshot) {
|
|
||||||
const org = await this.organizationService.get(route.params.organizationId);
|
|
||||||
const permissions = route.data == null ? null : (route.data.permissions as Permissions[]);
|
|
||||||
|
|
||||||
if (
|
|
||||||
(permissions.indexOf(Permissions.AccessEventLogs) !== -1 && org.canAccessEventLogs) ||
|
|
||||||
(permissions.indexOf(Permissions.AccessImportExport) !== -1 && org.canAccessImportExport) ||
|
|
||||||
(permissions.indexOf(Permissions.AccessReports) !== -1 && org.canAccessReports) ||
|
|
||||||
(permissions.indexOf(Permissions.CreateNewCollections) !== -1 &&
|
|
||||||
org.canCreateNewCollections) ||
|
|
||||||
(permissions.indexOf(Permissions.EditAnyCollection) !== -1 && org.canEditAnyCollection) ||
|
|
||||||
(permissions.indexOf(Permissions.DeleteAnyCollection) !== -1 && org.canDeleteAnyCollection) ||
|
|
||||||
(permissions.indexOf(Permissions.EditAssignedCollections) !== -1 &&
|
|
||||||
org.canEditAssignedCollections) ||
|
|
||||||
(permissions.indexOf(Permissions.DeleteAssignedCollections) !== -1 &&
|
|
||||||
org.canDeleteAssignedCollections) ||
|
|
||||||
(permissions.indexOf(Permissions.ManageGroups) !== -1 && org.canManageGroups) ||
|
|
||||||
(permissions.indexOf(Permissions.ManageOrganization) !== -1 && org.isOwner) ||
|
|
||||||
(permissions.indexOf(Permissions.ManagePolicies) !== -1 && org.canManagePolicies) ||
|
|
||||||
(permissions.indexOf(Permissions.ManageUsers) !== -1 && org.canManageUsers) ||
|
|
||||||
(permissions.indexOf(Permissions.ManageUsersPassword) !== -1 && org.canManageUsersPassword) ||
|
|
||||||
(permissions.indexOf(Permissions.ManageSso) !== -1 && org.canManageSso)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.router.navigate(["/organizations", org.id]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -45,11 +45,11 @@ import { PasswordRepromptService } from "../../services/passwordReprompt.service
|
|||||||
import { StateService } from "../../services/state.service";
|
import { StateService } from "../../services/state.service";
|
||||||
import { StateMigrationService } from "../../services/stateMigration.service";
|
import { StateMigrationService } from "../../services/stateMigration.service";
|
||||||
import { WebPlatformUtilsService } from "../../services/webPlatformUtils.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 { EventService } from "./event.service";
|
||||||
import { ModalService } from "./modal.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 { PolicyListService } from "./policy-list.service";
|
||||||
import { RouterService } from "./router.service";
|
import { RouterService } from "./router.service";
|
||||||
|
|
||||||
@@ -100,6 +100,7 @@ export function initFactory(
|
|||||||
imports: [ToastrModule, JslibServicesModule],
|
imports: [ToastrModule, JslibServicesModule],
|
||||||
declarations: [],
|
declarations: [],
|
||||||
providers: [
|
providers: [
|
||||||
|
OrgPermissionsService,
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
useFactory: initFactory,
|
useFactory: initFactory,
|
||||||
@@ -117,8 +118,7 @@ export function initFactory(
|
|||||||
],
|
],
|
||||||
multi: true,
|
multi: true,
|
||||||
},
|
},
|
||||||
OrganizationGuardService,
|
OrgPermissionsGuard,
|
||||||
OrganizationTypeGuardService,
|
|
||||||
RouterService,
|
RouterService,
|
||||||
EventService,
|
EventService,
|
||||||
PolicyListService,
|
PolicyListService,
|
||||||
|
|||||||
@@ -8,43 +8,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<app-change-email></app-change-email>
|
<app-change-email></app-change-email>
|
||||||
</ng-container>
|
</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">
|
<div class="secondary-header text-danger border-0 mb-0">
|
||||||
<h1>{{ "dangerZone" | i18n }}</h1>
|
<h1>{{ "dangerZone" | i18n }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="card border-danger">
|
<div class="card border-danger">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>{{ "dangerZoneDesc" | i18n }}</p>
|
<p>{{ "dangerZoneDesc" | i18n }}</p>
|
||||||
<button type="button" class="btn btn-outline-danger" (click)="deauthorizeSessions()">
|
<button bit-button buttonType="danger" (click)="deauthorizeSessions()">
|
||||||
{{ "deauthorizeSessions" | i18n }}
|
{{ "deauthorizeSessions" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-danger" (click)="purgeVault()">
|
<button bit-button buttonType="danger" (click)="purgeVault()">
|
||||||
{{ "purgeVault" | i18n }}
|
{{ "purgeVault" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-danger" (click)="deleteAccount()">
|
<button bit-button buttonType="danger" (click)="deleteAccount()">
|
||||||
{{ "deleteAccount" | i18n }}
|
{{ "deleteAccount" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { ApiService } from "jslib-common/abstractions/api.service";
|
|||||||
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { StateService } from "jslib-common/abstractions/state.service";
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
|
|
||||||
import { ApiKeyComponent } from "./api-key.component";
|
|
||||||
import { DeauthorizeSessionsComponent } from "./deauthorize-sessions.component";
|
import { DeauthorizeSessionsComponent } from "./deauthorize-sessions.component";
|
||||||
import { DeleteAccountComponent } from "./delete-account.component";
|
import { DeleteAccountComponent } from "./delete-account.component";
|
||||||
import { PurgeVaultComponent } from "./purge-vault.component";
|
import { PurgeVaultComponent } from "./purge-vault.component";
|
||||||
@@ -21,13 +20,7 @@ export class AccountComponent {
|
|||||||
purgeModalRef: ViewContainerRef;
|
purgeModalRef: ViewContainerRef;
|
||||||
@ViewChild("deleteAccountTemplate", { read: ViewContainerRef, static: true })
|
@ViewChild("deleteAccountTemplate", { read: ViewContainerRef, static: true })
|
||||||
deleteModalRef: ViewContainerRef;
|
deleteModalRef: ViewContainerRef;
|
||||||
@ViewChild("viewUserApiKeyTemplate", { read: ViewContainerRef, static: true })
|
|
||||||
viewUserApiKeyModalRef: ViewContainerRef;
|
|
||||||
@ViewChild("rotateUserApiKeyTemplate", { read: ViewContainerRef, static: true })
|
|
||||||
rotateUserApiKeyModalRef: ViewContainerRef;
|
|
||||||
|
|
||||||
showChangePassword = true;
|
|
||||||
showChangeKdf = true;
|
|
||||||
showChangeEmail = true;
|
showChangeEmail = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -38,10 +31,7 @@ export class AccountComponent {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.showChangeEmail =
|
this.showChangeEmail = !(await this.keyConnectorService.getUsesKeyConnector());
|
||||||
this.showChangeKdf =
|
|
||||||
this.showChangePassword =
|
|
||||||
!(await this.keyConnectorService.getUsesKeyConnector());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deauthorizeSessions() {
|
async deauthorizeSessions() {
|
||||||
@@ -55,33 +45,4 @@ export class AccountComponent {
|
|||||||
async deleteAccount() {
|
async deleteAccount() {
|
||||||
await this.modalService.openViewRef(DeleteAccountComponent, this.deleteModalRef);
|
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";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
|
<div class="tabbed-header">
|
||||||
|
<h1>{{ "encKeySettings" | i18n }}</h1>
|
||||||
|
</div>
|
||||||
|
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
|
||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
@@ -68,7 +71,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
<button bit-button buttonType="primary" class="btn-submit" [disabled]="form.loading">
|
||||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||||
<span>{{ "changeKdf" | i18n }}</span>
|
<span>{{ "changeKdf" | i18n }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
|
<div class="tabbed-header">
|
||||||
|
<h1>{{ "changeMasterPassword" | i18n }}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
|
||||||
<app-callout
|
<app-callout
|
||||||
type="info"
|
type="info"
|
||||||
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||||
@@ -83,7 +87,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
<button bit-button buttonType="primary" class="btn-submit" [disabled]="form.loading">
|
||||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||||
<span>{{ "changeMasterPassword" | i18n }}</span>
|
<span>{{ "changeMasterPassword" | i18n }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { ChangePasswordComponent as BaseChangePasswordComponent } from "jslib-angular/components/change-password.component";
|
import { ChangePasswordComponent as BaseChangePasswordComponent } from "jslib-angular/components/change-password.component";
|
||||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
@@ -6,6 +7,7 @@ import { CipherService } from "jslib-common/abstractions/cipher.service";
|
|||||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { FolderService } from "jslib-common/abstractions/folder.service";
|
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||||
import { I18nService } from "jslib-common/abstractions/i18n.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 { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
@@ -47,7 +49,9 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
|||||||
private syncService: SyncService,
|
private syncService: SyncService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private sendService: SendService,
|
private sendService: SendService,
|
||||||
private organizationService: OrganizationService
|
private organizationService: OrganizationService,
|
||||||
|
private keyConnectorService: KeyConnectorService,
|
||||||
|
private router: Router
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
i18nService,
|
i18nService,
|
||||||
@@ -60,6 +64,12 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
if (await this.keyConnectorService.getUsesKeyConnector()) {
|
||||||
|
this.router.navigate(["/settings/security/two-factor"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async rotateEncKeyClicked() {
|
async rotateEncKeyClicked() {
|
||||||
if (this.rotateEncKey) {
|
if (this.rotateEncKey) {
|
||||||
const ciphers = await this.cipherService.getAllDecrypted();
|
const ciphers = await this.cipherService.getAllDecrypted();
|
||||||
|
|||||||
@@ -1,33 +1,4 @@
|
|||||||
<ng-container *ngIf="vault">
|
<div class="page-header d-flex">
|
||||||
<p *ngIf="!loaded" class="text-muted">
|
|
||||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
|
||||||
</p>
|
|
||||||
<ng-container *ngIf="loaded">
|
|
||||||
<ul class="bwi-ul card-ul carets" *ngIf="organizations && organizations.length">
|
|
||||||
<li *ngFor="let o of organizations">
|
|
||||||
<a [routerLink]="['/organizations', o.id]" class="text-body">
|
|
||||||
<i class="bwi bwi-li bwi-caret-right" aria-hidden="true"></i> {{ o.name }}
|
|
||||||
<ng-container *ngIf="!o.enabled">
|
|
||||||
<i
|
|
||||||
class="bwi bwi-exclamation-triangle text-danger"
|
|
||||||
title="{{ 'organizationIsDisabled' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span class="sr-only">{{ "organizationIsDisabled" | i18n }}</span>
|
|
||||||
</ng-container>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p *ngIf="!organizations || !organizations.length">{{ "noOrganizationsList" | i18n }}</p>
|
|
||||||
</ng-container>
|
|
||||||
<a href="#" routerLink="/settings/create-organization" class="btn btn-block btn-outline-primary">
|
|
||||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
|
||||||
{{ "newOrganization" | i18n }}
|
|
||||||
</a>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!vault">
|
|
||||||
<div class="page-header d-flex">
|
|
||||||
<h1>
|
<h1>
|
||||||
{{ "organizations" | i18n }}
|
{{ "organizations" | i18n }}
|
||||||
<small [appApiAction]="actionPromise" #action>
|
<small [appApiAction]="actionPromise" #action>
|
||||||
@@ -50,16 +21,16 @@
|
|||||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||||
{{ "newOrganization" | i18n }}
|
{{ "newOrganization" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="!loaded">
|
<ng-container *ngIf="!loaded">
|
||||||
<i
|
<i
|
||||||
class="bwi bwi-spinner bwi-spin text-muted"
|
class="bwi bwi-spinner bwi-spin text-muted"
|
||||||
title="{{ 'loading' | i18n }}"
|
title="{{ 'loading' | i18n }}"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></i>
|
></i>
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="loaded">
|
<ng-container *ngIf="loaded">
|
||||||
<ng-container *ngIf="!organizations || !organizations.length">
|
<ng-container *ngIf="!organizations || !organizations.length">
|
||||||
<p>{{ "noOrganizationsList" | i18n }}</p>
|
<p>{{ "noOrganizationsList" | i18n }}</p>
|
||||||
<a href="#" routerLink="/settings/create-organization" class="btn btn-outline-primary">
|
<a href="#" routerLink="/settings/create-organization" class="btn btn-outline-primary">
|
||||||
@@ -151,5 +122,4 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, Input, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
@@ -19,8 +19,6 @@ import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/mod
|
|||||||
templateUrl: "organizations.component.html",
|
templateUrl: "organizations.component.html",
|
||||||
})
|
})
|
||||||
export class OrganizationsComponent implements OnInit {
|
export class OrganizationsComponent implements OnInit {
|
||||||
@Input() vault = false;
|
|
||||||
|
|
||||||
organizations: Organization[];
|
organizations: Organization[];
|
||||||
policies: Policy[];
|
policies: Policy[];
|
||||||
loaded = false;
|
loaded = false;
|
||||||
@@ -38,11 +36,9 @@ export class OrganizationsComponent implements OnInit {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
if (!this.vault) {
|
|
||||||
await this.syncService.fullSync(true);
|
await this.syncService.fullSync(true);
|
||||||
await this.load();
|
await this.load();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
const orgs = await this.organizationService.getAll();
|
const orgs = await this.organizationService.getAll();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1>{{ "options" | i18n }}</h1>
|
<h1>{{ "preferences" | i18n }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<p>{{ "optionsDesc" | i18n }}</p>
|
<p>{{ "preferencesDesc" | i18n }}</p>
|
||||||
<form (ngSubmit)="submit()" ngNativeValidate>
|
<form (ngSubmit)="submit()" ngNativeValidate>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
@@ -10,10 +10,10 @@ import { ThemeType } from "jslib-common/enums/themeType";
|
|||||||
import { Utils } from "jslib-common/misc/utils";
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-options",
|
selector: "app-preferences",
|
||||||
templateUrl: "options.component.html",
|
templateUrl: "preferences.component.html",
|
||||||
})
|
})
|
||||||
export class OptionsComponent implements OnInit {
|
export class PreferencesComponent implements OnInit {
|
||||||
vaultTimeoutAction = "lock";
|
vaultTimeoutAction = "lock";
|
||||||
disableIcons: boolean;
|
disableIcons: boolean;
|
||||||
enableGravatars: boolean;
|
enableGravatars: boolean;
|
||||||
@@ -107,7 +107,11 @@ export class OptionsComponent implements OnInit {
|
|||||||
if (this.locale !== this.startingLocale) {
|
if (this.locale !== this.startingLocale) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("optionsUpdated"));
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("preferencesUpdated")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
18
src/app/settings/security-keys.component.html
Normal file
18
src/app/settings/security-keys.component.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<app-change-kdf *ngIf="showChangeKdf"></app-change-kdf>
|
||||||
|
<div
|
||||||
|
[ngClass]="{ 'tabbed-header': !showChangeKdf, 'secondary-header': showChangeKdf }"
|
||||||
|
class="border-0 mb-0"
|
||||||
|
>
|
||||||
|
<h1>{{ "apiKey" | i18n }}</h1>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{{ "userApiKeyDesc" | i18n }}
|
||||||
|
</p>
|
||||||
|
<button bit-button buttonType="secondary" (click)="viewUserApiKey()">
|
||||||
|
{{ "viewApiKey" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button bit-button buttonType="secondary" (click)="rotateUserApiKey()">
|
||||||
|
{{ "rotateApiKey" | i18n }}
|
||||||
|
</button>
|
||||||
|
<ng-template #viewUserApiKeyTemplate></ng-template>
|
||||||
|
<ng-template #rotateUserApiKeyTemplate></ng-template>
|
||||||
61
src/app/settings/security-keys.component.ts
Normal file
61
src/app/settings/security-keys.component.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||||
|
|
||||||
|
import { ModalService } from "jslib-angular/services/modal.service";
|
||||||
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
|
|
||||||
|
import { ApiKeyComponent } from "./api-key.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-security-keys",
|
||||||
|
templateUrl: "security-keys.component.html",
|
||||||
|
})
|
||||||
|
export class SecurityKeysComponent implements OnInit {
|
||||||
|
@ViewChild("viewUserApiKeyTemplate", { read: ViewContainerRef, static: true })
|
||||||
|
viewUserApiKeyModalRef: ViewContainerRef;
|
||||||
|
@ViewChild("rotateUserApiKeyTemplate", { read: ViewContainerRef, static: true })
|
||||||
|
rotateUserApiKeyModalRef: ViewContainerRef;
|
||||||
|
|
||||||
|
showChangeKdf = true;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private keyConnectorService: KeyConnectorService,
|
||||||
|
private stateService: StateService,
|
||||||
|
private modalService: ModalService,
|
||||||
|
private apiService: ApiService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.showChangeKdf = !(await this.keyConnectorService.getUsesKeyConnector());
|
||||||
|
}
|
||||||
|
|
||||||
|
async viewUserApiKey() {
|
||||||
|
const entityId = await this.stateService.getUserId();
|
||||||
|
await this.modalService.openViewRef(ApiKeyComponent, this.viewUserApiKeyModalRef, (comp) => {
|
||||||
|
comp.keyType = "user";
|
||||||
|
comp.entityId = entityId;
|
||||||
|
comp.postKey = this.apiService.postUserApiKey.bind(this.apiService);
|
||||||
|
comp.scope = "api";
|
||||||
|
comp.grantType = "client_credentials";
|
||||||
|
comp.apiKeyTitle = "apiKey";
|
||||||
|
comp.apiKeyWarning = "userApiKeyWarning";
|
||||||
|
comp.apiKeyDescription = "userApiKeyDesc";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async rotateUserApiKey() {
|
||||||
|
const entityId = await this.stateService.getUserId();
|
||||||
|
await this.modalService.openViewRef(ApiKeyComponent, this.rotateUserApiKeyModalRef, (comp) => {
|
||||||
|
comp.keyType = "user";
|
||||||
|
comp.isRotation = true;
|
||||||
|
comp.entityId = entityId;
|
||||||
|
comp.postKey = this.apiService.postUserRotateApiKey.bind(this.apiService);
|
||||||
|
comp.scope = "api";
|
||||||
|
comp.grantType = "client_credentials";
|
||||||
|
comp.apiKeyTitle = "apiKey";
|
||||||
|
comp.apiKeyWarning = "userApiKeyWarning";
|
||||||
|
comp.apiKeyDescription = "apiKeyRotateDesc";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/app/settings/security.component.html
Normal file
22
src/app/settings/security.component.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<div class="tabbed-nav d-flex flex-column">
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<ng-container *ngIf="showChangePassword">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" routerLink="change-password" routerLinkActive="active">
|
||||||
|
{{ "masterPassword" | i18n }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ng-container>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" routerLink="two-factor" routerLinkActive="active">
|
||||||
|
{{ "twoStepLogin" | i18n }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" routerLink="security-keys" routerLinkActive="active">
|
||||||
|
{{ "keys" | i18n }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
17
src/app/settings/security.component.ts
Normal file
17
src/app/settings/security.component.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-security",
|
||||||
|
templateUrl: "security.component.html",
|
||||||
|
})
|
||||||
|
export class SecurityComponent {
|
||||||
|
showChangePassword = true;
|
||||||
|
|
||||||
|
constructor(private keyConnectorService: KeyConnectorService) {}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.showChangePassword = !(await this.keyConnectorService.getUsesKeyConnector());
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/app/settings/security.module.ts
Normal file
39
src/app/settings/security.module.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { NgModule } from "@angular/core";
|
||||||
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
|
|
||||||
|
import { ChangePasswordComponent } from "./change-password.component";
|
||||||
|
import { SecurityKeysComponent } from "./security-keys.component";
|
||||||
|
import { SecurityComponent } from "./security.component";
|
||||||
|
import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: SecurityComponent,
|
||||||
|
data: { titleId: "security" },
|
||||||
|
children: [
|
||||||
|
{ path: "", pathMatch: "full", redirectTo: "change-password" },
|
||||||
|
{
|
||||||
|
path: "change-password",
|
||||||
|
component: ChangePasswordComponent,
|
||||||
|
data: { titleId: "masterPassword" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "two-factor",
|
||||||
|
component: TwoFactorSetupComponent,
|
||||||
|
data: { titleId: "twoStepLogin" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "security-keys",
|
||||||
|
component: SecurityKeysComponent,
|
||||||
|
data: { titleId: "keys" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class SecurityModule {}
|
||||||
@@ -7,8 +7,11 @@
|
|||||||
<a routerLink="account" class="list-group-item" routerLinkActive="active">
|
<a routerLink="account" class="list-group-item" routerLinkActive="active">
|
||||||
{{ "myAccount" | i18n }}
|
{{ "myAccount" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
<a routerLink="options" class="list-group-item" routerLinkActive="active">
|
<a routerLink="security" class="list-group-item" routerLinkActive="active">
|
||||||
{{ "options" | i18n }}
|
{{ "security" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a routerLink="preferences" class="list-group-item" routerLinkActive="active">
|
||||||
|
{{ "preferences" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
<a routerLink="organizations" class="list-group-item" routerLinkActive="active">
|
<a routerLink="organizations" class="list-group-item" routerLinkActive="active">
|
||||||
{{ "organizations" | i18n }}
|
{{ "organizations" | i18n }}
|
||||||
@@ -37,9 +40,6 @@
|
|||||||
>
|
>
|
||||||
{{ "billing" | i18n }}
|
{{ "billing" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
<a routerLink="two-factor" class="list-group-item" routerLinkActive="active">
|
|
||||||
{{ "twoStepLogin" | i18n }}
|
|
||||||
</a>
|
|
||||||
<a routerLink="domain-rules" class="list-group-item" routerLinkActive="active">
|
<a routerLink="domain-rules" class="list-group-item" routerLinkActive="active">
|
||||||
{{ "domainRules" | i18n }}
|
{{ "domainRules" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<div class="page-header">
|
<div class="tabbed-header">
|
||||||
<h1>{{ "twoStepLogin" | i18n }}</h1>
|
<h1>{{ "twoStepLogin" | i18n }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<p *ngIf="!organizationId">{{ "twoStepLoginDesc" | i18n }}</p>
|
<p *ngIf="!organizationId">{{ "twoStepLoginDesc" | i18n }}</p>
|
||||||
<p *ngIf="organizationId">{{ "twoStepLoginOrganizationDesc" | i18n }}</p>
|
<p *ngIf="organizationId">{{ "twoStepLoginOrganizationDesc" | i18n }}</p>
|
||||||
<app-callout type="warning" *ngIf="!organizationId">
|
<bit-callout type="warning" *ngIf="!organizationId">
|
||||||
<p>{{ "twoStepLoginRecoveryWarning" | i18n }}</p>
|
<p>{{ "twoStepLoginRecoveryWarning" | i18n }}</p>
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="recoveryCode()">
|
<button bit-button buttonType="secondary" (click)="recoveryCode()">
|
||||||
{{ "viewRecoveryCode" | i18n }}
|
{{ "viewRecoveryCode" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</app-callout>
|
</bit-callout>
|
||||||
<h2 [ngClass]="{ 'mt-5': !organizationId }">
|
<h2 [ngClass]="{ 'mt-5': !organizationId }">
|
||||||
{{ "providers" | i18n }}
|
{{ "providers" | i18n }}
|
||||||
<small *ngIf="loading">
|
<small *ngIf="loading">
|
||||||
@@ -20,9 +20,9 @@
|
|||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
</small>
|
</small>
|
||||||
</h2>
|
</h2>
|
||||||
<app-callout type="warning" *ngIf="showPolicyWarning">
|
<bit-callout type="warning" *ngIf="showPolicyWarning">
|
||||||
{{ "twoStepLoginPolicyUserWarning" | i18n }}
|
{{ "twoStepLoginPolicyUserWarning" | i18n }}
|
||||||
</app-callout>
|
</bit-callout>
|
||||||
<ul class="list-group list-group-2fa">
|
<ul class="list-group list-group-2fa">
|
||||||
<li *ngFor="let p of providers" class="list-group-item d-flex align-items-center">
|
<li *ngFor="let p of providers" class="list-group-item d-flex align-items-center">
|
||||||
<div class="logo-2fa d-flex justify-content-center">
|
<div class="logo-2fa d-flex justify-content-center">
|
||||||
@@ -45,8 +45,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="ml-auto">
|
<div class="ml-auto">
|
||||||
<button
|
<button
|
||||||
type="button"
|
bit-button
|
||||||
class="btn btn-outline-secondary btn-sm"
|
buttonType="secondary"
|
||||||
[disabled]="!canAccessPremium && p.premium"
|
[disabled]="!canAccessPremium && p.premium"
|
||||||
(click)="manage(p.type)"
|
(click)="manage(p.type)"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -47,7 +47,9 @@ export class UserBillingComponent implements OnInit {
|
|||||||
if (this.organizationId != null) {
|
if (this.organizationId != null) {
|
||||||
this.billing = await this.apiService.getOrganizationBilling(this.organizationId);
|
this.billing = await this.apiService.getOrganizationBilling(this.organizationId);
|
||||||
} else {
|
} else {
|
||||||
this.billing = await this.apiService.getUserBilling();
|
// let history = await this.apiService.getUserBillingHistory();
|
||||||
|
// let payment = await this.apiService.getUserBillingPayment();
|
||||||
|
this.billing = new BillingResponse(null);
|
||||||
}
|
}
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,173 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,25 @@
|
|||||||
<div class="container page-content">
|
<div class="container page-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
<app-vault-groupings
|
<div class="groupings">
|
||||||
(onAllClicked)="clearGroupingFilters()"
|
<div class="content">
|
||||||
(onFavoritesClicked)="filterFavorites()"
|
<div class="inner-content">
|
||||||
(onCipherTypeClicked)="filterCipherType($event)"
|
<app-vault-filter
|
||||||
(onFolderClicked)="filterFolder($event.id)"
|
#vaultFilter
|
||||||
|
[activeFilter]="activeFilter"
|
||||||
|
(onFilterChange)="applyVaultFilter($event)"
|
||||||
(onAddFolder)="addFolder()"
|
(onAddFolder)="addFolder()"
|
||||||
(onEditFolder)="editFolder($event.id)"
|
(onEditFolder)="editFolder($event.id)"
|
||||||
(onCollectionClicked)="filterCollection($event.id)"
|
|
||||||
(onSearchTextChanged)="filterSearchText($event)"
|
(onSearchTextChanged)="filterSearchText($event)"
|
||||||
(onTrashClicked)="filterDeleted()"
|
></app-vault-filter>
|
||||||
>
|
|
||||||
</app-vault-groupings>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div [ngClass]="{ 'col-6': isShowingCards, 'col-9': !isShowingCards }">
|
||||||
<div class="page-header d-flex">
|
<div class="page-header d-flex">
|
||||||
<h1>
|
<h1>
|
||||||
{{ "myVault" | i18n }}
|
{{ "vaultItems" | i18n }}
|
||||||
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
|
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
|
||||||
<ng-container *ngIf="actionSpinner.loading">
|
<ng-container *ngIf="actionSpinner.loading">
|
||||||
<i
|
<i
|
||||||
@@ -97,40 +99,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { first } from "rxjs/operators";
|
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 { ModalService } from "jslib-angular/services/modal.service";
|
||||||
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
@@ -17,14 +18,13 @@ import { I18nService } from "jslib-common/abstractions/i18n.service";
|
|||||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.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 { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { SyncService } from "jslib-common/abstractions/sync.service";
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
import { TokenService } from "jslib-common/abstractions/token.service";
|
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||||
import { CipherType } from "jslib-common/enums/cipherType";
|
import { CipherType } from "jslib-common/enums/cipherType";
|
||||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
|
|
||||||
import { OrganizationsComponent } from "../settings/organizations.component";
|
import { VaultFilterComponent } from "../modules/vault-filter/vault-filter.component";
|
||||||
import { UpdateKeyComponent } from "../settings/update-key.component";
|
import { UpdateKeyComponent } from "../settings/update-key.component";
|
||||||
|
|
||||||
import { AddEditComponent } from "./add-edit.component";
|
import { AddEditComponent } from "./add-edit.component";
|
||||||
@@ -32,7 +32,6 @@ import { AttachmentsComponent } from "./attachments.component";
|
|||||||
import { CiphersComponent } from "./ciphers.component";
|
import { CiphersComponent } from "./ciphers.component";
|
||||||
import { CollectionsComponent } from "./collections.component";
|
import { CollectionsComponent } from "./collections.component";
|
||||||
import { FolderAddEditComponent } from "./folder-add-edit.component";
|
import { FolderAddEditComponent } from "./folder-add-edit.component";
|
||||||
import { GroupingsComponent } from "./groupings.component";
|
|
||||||
import { ShareComponent } from "./share.component";
|
import { ShareComponent } from "./share.component";
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = "VaultComponent";
|
const BroadcasterSubscriptionId = "VaultComponent";
|
||||||
@@ -42,10 +41,8 @@ const BroadcasterSubscriptionId = "VaultComponent";
|
|||||||
templateUrl: "vault.component.html",
|
templateUrl: "vault.component.html",
|
||||||
})
|
})
|
||||||
export class VaultComponent implements OnInit, OnDestroy {
|
export class VaultComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
|
@ViewChild("vaultFilter", { static: true }) filterComponent: VaultFilterComponent;
|
||||||
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
|
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
|
||||||
@ViewChild(OrganizationsComponent, { static: true })
|
|
||||||
organizationsComponent: OrganizationsComponent;
|
|
||||||
@ViewChild("attachments", { read: ViewContainerRef, static: true })
|
@ViewChild("attachments", { read: ViewContainerRef, static: true })
|
||||||
attachmentsModalRef: ViewContainerRef;
|
attachmentsModalRef: ViewContainerRef;
|
||||||
@ViewChild("folderAddEdit", { read: ViewContainerRef, static: true })
|
@ViewChild("folderAddEdit", { read: ViewContainerRef, static: true })
|
||||||
@@ -62,13 +59,15 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
type: CipherType = null;
|
type: CipherType = null;
|
||||||
folderId: string = null;
|
folderId: string = null;
|
||||||
collectionId: string = null;
|
collectionId: string = null;
|
||||||
|
organizationId: string = null;
|
||||||
|
myVaultOnly = false;
|
||||||
showVerifyEmail = false;
|
showVerifyEmail = false;
|
||||||
showBrowserOutdated = false;
|
showBrowserOutdated = false;
|
||||||
showUpdateKey = false;
|
showUpdateKey = false;
|
||||||
showPremiumCallout = false;
|
showPremiumCallout = false;
|
||||||
showProviders = false;
|
|
||||||
deleted = false;
|
deleted = false;
|
||||||
trashCleanupWarning: string = null;
|
trashCleanupWarning: string = null;
|
||||||
|
activeFilter: VaultFilter = new VaultFilter();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private syncService: SyncService,
|
private syncService: SyncService,
|
||||||
@@ -84,8 +83,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
private broadcasterService: BroadcasterService,
|
private broadcasterService: BroadcasterService,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService
|
||||||
private providerService: ProviderService
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@@ -99,42 +97,23 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.route.queryParams.pipe(first()).subscribe(async (params) => {
|
this.route.queryParams.pipe(first()).subscribe(async (params) => {
|
||||||
await this.syncService.fullSync(false);
|
await this.syncService.fullSync(false);
|
||||||
|
|
||||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||||
this.showPremiumCallout =
|
this.showPremiumCallout =
|
||||||
!this.showVerifyEmail && !canAccessPremium && !this.platformUtilsService.isSelfHost();
|
!this.showVerifyEmail && !canAccessPremium && !this.platformUtilsService.isSelfHost();
|
||||||
|
|
||||||
this.showProviders = (await this.providerService.getAll()).length > 0;
|
this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
|
||||||
|
|
||||||
await Promise.all([this.groupingsComponent.load(), this.organizationsComponent.load()]);
|
|
||||||
this.showUpdateKey = !(await this.cryptoService.hasEncKey());
|
this.showUpdateKey = !(await this.cryptoService.hasEncKey());
|
||||||
|
|
||||||
if (params == null) {
|
if (params.cipherId) {
|
||||||
this.groupingsComponent.selectedAll = true;
|
const cipherView = new CipherView();
|
||||||
await this.ciphersComponent.reload();
|
cipherView.id = params.cipherId;
|
||||||
} else {
|
if (params.action === "clone") {
|
||||||
if (params.deleted) {
|
await this.cloneCipher(cipherView);
|
||||||
this.groupingsComponent.selectedTrash = true;
|
} else if (params.action === "edit") {
|
||||||
await this.filterDeleted();
|
await this.editCipher(cipherView);
|
||||||
} 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.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||||
this.ngZone.run(async () => {
|
this.ngZone.run(async () => {
|
||||||
@@ -142,8 +121,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
case "syncCompleted":
|
case "syncCompleted":
|
||||||
if (message.successfully) {
|
if (message.successfully) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.groupingsComponent.load(),
|
this.filterComponent.reloadCollectionsAndFolders(this.activeFilter),
|
||||||
this.organizationsComponent.load(),
|
|
||||||
this.ciphersComponent.load(this.ciphersComponent.filter),
|
this.ciphersComponent.load(this.ciphersComponent.filter),
|
||||||
]);
|
]);
|
||||||
this.changeDetectorRef.detectChanges();
|
this.changeDetectorRef.detectChanges();
|
||||||
@@ -155,64 +133,26 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isShowingCards() {
|
||||||
|
return (
|
||||||
|
this.showBrowserOutdated ||
|
||||||
|
this.showPremiumCallout ||
|
||||||
|
this.showUpdateKey ||
|
||||||
|
this.showVerifyEmail
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearGroupingFilters() {
|
async applyVaultFilter(vaultFilter: VaultFilter) {
|
||||||
this.ciphersComponent.showAddNew = true;
|
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
|
||||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchVault");
|
this.activeFilter = vaultFilter;
|
||||||
await this.ciphersComponent.reload();
|
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
|
||||||
this.clearFilters();
|
this.filterComponent.searchPlaceholder = this.calculateSearchBarLocalizationString(
|
||||||
this.go();
|
this.activeFilter
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
this.go();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +161,40 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
this.ciphersComponent.search(200);
|
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) {
|
async editCipherAttachments(cipher: CipherView) {
|
||||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||||
if (cipher.organizationId == null && !canAccessPremium) {
|
if (cipher.organizationId == null && !canAccessPremium) {
|
||||||
@@ -292,7 +266,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
comp.folderId = null;
|
comp.folderId = null;
|
||||||
comp.onSavedFolder.subscribe(async () => {
|
comp.onSavedFolder.subscribe(async () => {
|
||||||
modal.close();
|
modal.close();
|
||||||
await this.groupingsComponent.loadFolders();
|
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -306,13 +280,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
comp.folderId = folderId;
|
comp.folderId = folderId;
|
||||||
comp.onSavedFolder.subscribe(async () => {
|
comp.onSavedFolder.subscribe(async () => {
|
||||||
modal.close();
|
modal.close();
|
||||||
await this.groupingsComponent.loadFolders();
|
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
|
||||||
});
|
});
|
||||||
comp.onDeletedFolder.subscribe(async () => {
|
comp.onDeletedFolder.subscribe(async () => {
|
||||||
modal.close();
|
modal.close();
|
||||||
await this.groupingsComponent.loadFolders();
|
await this.filterComponent.reloadCollectionsAndFolders(this.activeFilter);
|
||||||
await this.filterFolder("none");
|
|
||||||
this.groupingsComponent.selectedFolderId = null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -322,15 +294,21 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
const component = await this.editCipher(null);
|
const component = await this.editCipher(null);
|
||||||
component.type = this.type;
|
component.type = this.type;
|
||||||
component.folderId = this.folderId === "none" ? null : this.folderId;
|
component.folderId = this.folderId === "none" ? null : this.folderId;
|
||||||
if (this.collectionId != null) {
|
if (this.activeFilter.selectedCollectionId != null) {
|
||||||
const collection = this.groupingsComponent.collections.filter(
|
const collection = this.filterComponent.collections.fullList.filter(
|
||||||
(c) => c.id === this.collectionId
|
(c) => c.id === this.activeFilter.selectedCollectionId
|
||||||
);
|
);
|
||||||
if (collection.length > 0) {
|
if (collection.length > 0) {
|
||||||
component.organizationId = collection[0].organizationId;
|
component.organizationId = collection[0].organizationId;
|
||||||
component.collectionIds = [this.collectionId];
|
component.collectionIds = [this.activeFilter.selectedCollectionId];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.activeFilter.selectedFolderId && this.activeFilter.selectedFolder) {
|
||||||
|
component.folderId = this.activeFilter.selectedFolderId;
|
||||||
|
}
|
||||||
|
if (this.activeFilter.selectedOrganizationId) {
|
||||||
|
component.organizationId = this.activeFilter.selectedOrganizationId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async editCipher(cipher: CipherView) {
|
async editCipher(cipher: CipherView) {
|
||||||
@@ -366,12 +344,30 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
await this.modalService.openViewRef(UpdateKeyComponent, this.updateKeyModalRef);
|
await this.modalService.openViewRef(UpdateKeyComponent, this.updateKeyModalRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearFilters() {
|
private calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string {
|
||||||
this.folderId = null;
|
if (vaultFilter.status === "favorites") {
|
||||||
this.collectionId = null;
|
return "searchFavorites";
|
||||||
this.favorites = false;
|
}
|
||||||
this.type = null;
|
if (vaultFilter.status === "trash") {
|
||||||
this.deleted = false;
|
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 go(queryParams: any = null) {
|
private go(queryParams: any = null) {
|
||||||
|
|||||||
@@ -424,9 +424,18 @@
|
|||||||
"myVault": {
|
"myVault": {
|
||||||
"message": "My Vault"
|
"message": "My Vault"
|
||||||
},
|
},
|
||||||
|
"allVaults": {
|
||||||
|
"message": "All Vaults"
|
||||||
|
},
|
||||||
"vault": {
|
"vault": {
|
||||||
"message": "Vault"
|
"message": "Vault"
|
||||||
},
|
},
|
||||||
|
"vaults": {
|
||||||
|
"message": "Vaults"
|
||||||
|
},
|
||||||
|
"vaultItems": {
|
||||||
|
"message": "Vault Items"
|
||||||
|
},
|
||||||
"moveSelectedToOrg": {
|
"moveSelectedToOrg": {
|
||||||
"message": "Move Selected to Organization"
|
"message": "Move Selected to Organization"
|
||||||
},
|
},
|
||||||
@@ -1110,11 +1119,14 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"message": "Options"
|
"message": "Options"
|
||||||
},
|
},
|
||||||
"optionsDesc": {
|
"preferences": {
|
||||||
|
"message": "Preferences"
|
||||||
|
},
|
||||||
|
"preferencesDesc": {
|
||||||
"message": "Customize your web vault experience."
|
"message": "Customize your web vault experience."
|
||||||
},
|
},
|
||||||
"optionsUpdated": {
|
"preferencesUpdated": {
|
||||||
"message": "Options updated"
|
"message": "Preferences updated"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"message": "Language"
|
"message": "Language"
|
||||||
@@ -4836,6 +4848,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"backToReports": {
|
||||||
"message": "Back to Reports"
|
"message": "Back to Reports"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page-header,
|
.page-header,
|
||||||
.secondary-header {
|
.secondary-header,
|
||||||
|
.tabbed-header {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
padding-bottom: 0.6rem;
|
padding-bottom: 0.6rem;
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
@@ -64,6 +65,11 @@ body {
|
|||||||
margin-top: 4rem;
|
margin-top: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabbed-header {
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
img.logo {
|
img.logo {
|
||||||
display: block;
|
display: block;
|
||||||
height: 43px;
|
height: 43px;
|
||||||
|
|||||||
@@ -71,6 +71,10 @@
|
|||||||
margin-left: 0.85em;
|
margin-left: 0.85em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.no-margin {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-org-plans {
|
.card-org-plans {
|
||||||
|
|||||||
@@ -74,6 +74,27 @@
|
|||||||
height: 100%;
|
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 {
|
.nav-tabs {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
|
|
||||||
@@ -90,6 +111,7 @@
|
|||||||
padding-top: calc(#{$nav-link-padding-y} - 2px);
|
padding-top: calc(#{$nav-link-padding-y} - 2px);
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
border-top: 3px solid themed("primary");
|
border-top: 3px solid themed("primary");
|
||||||
|
border-bottom: 1px solid themed("backgroundColor");
|
||||||
color: themed("linkColor");
|
color: themed("linkColor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,15 +123,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.org-name {
|
|
||||||
line-height: 1;
|
|
||||||
span {
|
|
||||||
display: block;
|
|
||||||
font-size: $font-size-lg;
|
|
||||||
@include themify($themes) {
|
|
||||||
color: themed("textHeadingColor");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,3 +260,45 @@ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user