1
0
mirror of https://github.com/bitwarden/web synced 2025-12-16 16:23:31 +00:00

Compare commits

..

5 Commits

Author SHA1 Message Date
github-actions[bot]
05a17629a5 Bump version to 2.27.0 (#1545)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit e266a740ba)
2022-03-15 15:44:02 -06:00
Matt Gibson
a1bd0b7eec Update jslib 2022-03-15 08:47:49 -04:00
Joseph Flinn
f263156484 Update hotfix release branch name to hotfix-rc (#1532)
Fix confilct with newer commit in master

(cherry picked from commit 153870693b)
2022-03-09 12:59:13 -08:00
Addison Beck
c174751b9c [bug] Partially revert #1516 (#1531) 2022-03-09 11:17:52 -05:00
Daniel James Smith
970d3162ac Pull jslib for https://github.com/bitwarden/jslib/pull/714 (#1526)
(cherry picked from commit 80ffa965e1)
2022-03-08 13:54:38 +01:00
171 changed files with 4086 additions and 9706 deletions

View File

@@ -14,7 +14,6 @@ on:
paths-ignore: paths-ignore:
- '.github/workflows/**' - '.github/workflows/**'
jobs: jobs:
cloc: cloc:
name: CLOC name: CLOC
@@ -31,7 +30,6 @@ jobs:
- name: Print lines of code - name: Print lines of code
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
lint: lint:
name: Lint name: Lint
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -49,19 +47,9 @@ 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
setup: setup:
name: Setup name: Setup
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -75,7 +63,6 @@ jobs:
id: version id: version
run: echo "::set-output name=value::${GITHUB_SHA:0:7}" run: echo "::set-output name=value::${GITHUB_SHA:0:7}"
build-oss-selfhost: build-oss-selfhost:
name: Build OSS zip name: Build OSS zip
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -85,16 +72,18 @@ jobs:
env: env:
_VERSION: ${{ needs.setup.outputs.version }} _VERSION: ${{ needs.setup.outputs.version }}
steps: steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up Node - name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0 uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with: with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: "16" node-version: "16"
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Print environment - name: Print environment
run: | run: |
whoami whoami
@@ -105,6 +94,9 @@ jobs:
echo "GitHub ref: $GITHUB_REF" echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
@@ -120,7 +112,6 @@ jobs:
path: ./web-${{ env._VERSION }}-selfhosted-open-source.zip path: ./web-${{ env._VERSION }}-selfhosted-open-source.zip
if-no-files-found: error if-no-files-found: error
build-cloud: build-cloud:
name: Build Cloud zip name: Build Cloud zip
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -130,16 +121,18 @@ jobs:
env: env:
_VERSION: ${{ needs.setup.outputs.version }} _VERSION: ${{ needs.setup.outputs.version }}
steps: steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up Node - name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0 uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with: with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: "16" node-version: "16"
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Print environment - name: Print environment
run: | run: |
whoami whoami
@@ -150,6 +143,9 @@ jobs:
echo "GitHub ref: $GITHUB_REF" echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
@@ -165,7 +161,6 @@ jobs:
path: ./web-${{ env._VERSION }}-cloud-COMMERCIAL.zip path: ./web-${{ env._VERSION }}-cloud-COMMERCIAL.zip
if-no-files-found: error if-no-files-found: error
build-commercial-selfhost: build-commercial-selfhost:
name: Build SelfHost Docker image name: Build SelfHost Docker image
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -175,16 +170,18 @@ jobs:
env: env:
_VERSION: ${{ needs.setup.outputs.version }} _VERSION: ${{ needs.setup.outputs.version }}
steps: steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up Node - name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0 uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with: with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: "16" node-version: "16"
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Print environment - name: Print environment
run: | run: |
whoami whoami
@@ -203,6 +200,9 @@ jobs:
azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
azure-keyvault-name: "bitwarden-prod-kv" azure-keyvault-name: "bitwarden-prod-kv"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
@@ -267,34 +267,8 @@ jobs:
- name: Log out of Docker - name: Log out of Docker
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc' if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
run: |
docker logout
echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV
- name: Login to Azure - QA Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
- name: Login to Azure ACR
run: az acr login -n bitwardenqa
- name: Tag and Push RC to Azure ACR QA registry
env:
REGISTRY: bitwardenqa.azurecr.io
run: |
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name
if [[ "$IMAGE_TAG" == "master" ]]; then
IMAGE_TAG=dev
fi
docker tag bitwarden/web \
$REGISTRY/web-sh:$IMAGE_TAG
docker push $REGISTRY/web-sh:$IMAGE_TAG
- name: Log out of Docker
run: docker logout run: docker logout
build-qa: build-qa:
name: Build Docker images for QA environment name: Build Docker images for QA environment
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -302,16 +276,18 @@ jobs:
- setup - setup
- lint - lint
steps: steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up Node - name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0 uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with: with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: "16" node-version: "16"
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Print environment - name: Print environment
run: | run: |
whoami whoami
@@ -330,6 +306,9 @@ jobs:
- name: Log into container registry - name: Log into container registry
run: az acr login -n bitwardenqa run: az acr login -n bitwardenqa
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
@@ -385,29 +364,34 @@ jobs:
- name: Log out of Docker - name: Log out of Docker
run: docker logout run: docker logout
windows: windows:
name: Test code on Windows name: Test code on Windows
runs-on: windows-2019 runs-on: windows-2019
steps: steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up NuGet - name: Set up NuGet
uses: nuget/setup-nuget@04b0c2b8d1b97922f67eca497d7cf0bf17b8ffe1 uses: nuget/setup-nuget@04b0c2b8d1b97922f67eca497d7cf0bf17b8ffe1
with: with:
nuget-version: "latest" nuget-version: "latest"
- name: Set up Node - name: Set up MSBuild
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0 uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d
- name: Cache npm
id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with:
path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with: with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: "16" node-version: "16"
- name: Print environment - name: Print environment
run: | run: |
nuget help | grep Version nuget help | grep Version
msbuild -version
node --version node --version
npm --version npm --version
echo "GitHub ref: $GITHUB_REF" echo "GitHub ref: $GITHUB_REF"
@@ -416,13 +400,15 @@ jobs:
GITHUB_REF: ${{ github.ref }} GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }} GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: NPM build - name: NPM build
run: npm run build:bit:cloud run: npm run build:bit:cloud
crowdin-push: crowdin-push:
name: Crowdin Push name: Crowdin Push
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
@@ -461,7 +447,6 @@ jobs:
upload_sources: true upload_sources: true
upload_translations: false upload_translations: false
check-failures: check-failures:
name: Check for failures name: Check for failures
if: always() if: always()

View File

@@ -137,9 +137,6 @@ jobs:
else else
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web:$_RELEASE_VERSION docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web:$_RELEASE_VERSION
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web:latest docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web:latest
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web-sh:$_RELEASE_VERSION
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web-sh:latest
fi fi
- name: Push version and latest image - name: Push version and latest image
@@ -150,9 +147,6 @@ jobs:
docker push $REGISTRY/web:$_RELEASE_VERSION docker push $REGISTRY/web:$_RELEASE_VERSION
docker push $REGISTRY/web:latest docker push $REGISTRY/web:latest
docker push $REGISTRY/web-sh:$_RELEASE_VERSION
docker push $REGISTRY/web-sh:latest
- name: Log out of Docker - name: Log out of Docker
run: docker logout run: docker logout
@@ -188,7 +182,7 @@ jobs:
git config --global url."https://".insteadOf ssh:// git config --global url."https://".insteadOf ssh://
- name: Download latest cloud asset - name: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success

View File

@@ -1,11 +1,39 @@
Bitwarden believes that working with security researchers across the globe is crucial to keeping our users safe. If you believe you've found a security issue in our product or service, we encourage you to please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). We welcome working with you to resolve the issue promptly. Thanks in advance! Bitwarden believes that working with security researchers across the globe is crucial to keeping our
users safe. If you believe you've found a security issue in our product or service, we encourage you to
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
# Disclosure Policy # Disclosure Policy
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every effort to quickly resolve the issue. - Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a third-party. We may publicly disclose the issue before resolving it, if appropriate. effort to quickly resolve the issue.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our service. Only interact with accounts you own or with explicit permission of the account holder. - Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
- If you would like to encrypt your report, please use the PGP key with long ID `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool). third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder.
- If you would like to encrypt your report, please use the PGP key with long ID
`0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
# In-scope
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
code is available at https://github.com/bitwarden.
# Exclusions
The following bug classes are out-of scope:
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
or that we already know of. Note that some of our issue tracking is private.
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
upstream maintainer.
- Attacks requiring physical access to a user's device.
- Self-XSS
- Issues related to software or protocols not under Bitwarden's control
- Vulnerabilities in outdated versions of Bitwarden
- Missing security best practices that do not directly lead to a vulnerability
- Issues that do not have any impact on the general public
While researching, we'd like to ask you to refrain from: While researching, we'd like to ask you to refrain from:
@@ -14,8 +42,4 @@ While researching, we'd like to ask you to refrain from:
- Social engineering (including phishing) of Bitwarden staff or contractors - Social engineering (including phishing) of Bitwarden staff or contractors
- Any physical attempts against Bitwarden property or data centers - Any physical attempts against Bitwarden property or data centers
# We want to help you!
If you have something that you feel is close to exploitation, or if you'd like some information regarding the internal API, or generally have any questions regarding the app that would help in your efforts, please email us at https://bitwarden.com/contact and ask for that information. As stated above, Bitwarden wants to help you find issues, and is more than willing to help.
Thank you for helping keep Bitwarden and our users safe! Thank you for helping keep Bitwarden and our users safe!

View File

@@ -5,10 +5,10 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { RouterModule } from "@angular/router"; import { RouterModule } from "@angular/router";
import { InfiniteScrollModule } from "ngx-infinite-scroll"; import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { JslibModule } from "jslib-angular/jslib.module"; import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
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 { OssModule } from "src/app/oss.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,24 +20,28 @@ import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-tim
@NgModule({ @NgModule({
imports: [ imports: [
JslibModule, OssModule,
VaultFilterModule,
BrowserAnimationsModule, BrowserAnimationsModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
ServicesModule, ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
InfiniteScrollModule, InfiniteScrollModule,
DragDropModule, DragDropModule,
AppRoutingModule, AppRoutingModule,
OssRoutingModule, OssRoutingModule,
OrganizationsModule, // Must be after OssRoutingModule for competing routes to resolve properly OrganizationsModule,
RouterModule, RouterModule,
WildcardRoutingModule, // Needs to be last to catch all non-existing routes WildcardRoutingModule, // Needs to be last to catch all non-existing routes
], ],
declarations: [ declarations: [
AppComponent, AppComponent,
DisablePersonalVaultExportPolicyComponent,
MaximumVaultTimeoutPolicyComponent, MaximumVaultTimeoutPolicyComponent,
DisablePersonalVaultExportPolicyComponent,
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })

View File

@@ -6,7 +6,6 @@ import "jquery";
import "popper.js"; import "popper.js";
require("src/scss/styles.scss"); require("src/scss/styles.scss");
require("src/scss/tailwind.css");
import { AppModule } from "./app.module"; import { AppModule } from "./app.module";

View File

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

View File

@@ -2,7 +2,7 @@ import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core"; import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { JslibModule } from "jslib-angular/jslib.module"; import { OssModule } from "src/app/oss.module";
import { InputCheckboxComponent } from "./components/input-checkbox.component"; import { InputCheckboxComponent } from "./components/input-checkbox.component";
import { InputTextReadOnlyComponent } from "./components/input-text-readonly.component"; import { InputTextReadOnlyComponent } from "./components/input-text-readonly.component";
@@ -14,13 +14,7 @@ import { OrganizationsRoutingModule } from "./organizations-routing.module";
// Form components are for use in the SSO Configuration Form only and should not be exported for use elsewhere. // Form components are for use in the SSO Configuration Form only and should not be exported for use elsewhere.
// They will be deprecated by the Component Library. // They will be deprecated by the Component Library.
@NgModule({ @NgModule({
imports: [ imports: [CommonModule, FormsModule, ReactiveFormsModule, OssModule, OrganizationsRoutingModule],
CommonModule,
FormsModule,
ReactiveFormsModule,
JslibModule,
OrganizationsRoutingModule,
],
declarations: [ declarations: [
InputCheckboxComponent, InputCheckboxComponent,
InputTextComponent, InputTextComponent,

View File

@@ -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 { AuthGuard } from "jslib-angular/guards/auth.guard"; import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
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: [AuthGuard], canActivate: [AuthGuardService],
component: ProvidersComponent, component: ProvidersComponent,
}, },
{ {
@@ -45,7 +45,7 @@ const routes: Routes = [
}, },
{ {
path: "", path: "",
canActivate: [AuthGuard], canActivate: [AuthGuardService],
children: [ children: [
{ {
path: "setup", path: "setup",
@@ -54,7 +54,7 @@ const routes: Routes = [
{ {
path: ":providerId", path: ":providerId",
component: ProvidersLayoutComponent, component: ProvidersLayoutComponent,
canActivate: [ProviderGuard], canActivate: [ProviderGuardService],
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: [PermissionsGuard], canActivate: [ProviderTypeGuardService],
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: [PermissionsGuard], canActivate: [ProviderTypeGuardService],
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: [PermissionsGuard], canActivate: [ProviderTypeGuardService],
data: { data: {
titleId: "myProvider", titleId: "myProvider",
permissions: [Permissions.ManageProvider], permissions: [Permissions.ManageProvider],

View File

@@ -2,7 +2,6 @@ import { CommonModule } from "@angular/common";
import { ComponentFactoryResolver, NgModule } from "@angular/core"; import { ComponentFactoryResolver, NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms"; import { FormsModule } from "@angular/forms";
import { JslibModule } from "jslib-angular/jslib.module";
import { ModalService } from "jslib-angular/services/modal.service"; import { ModalService } from "jslib-angular/services/modal.service";
import { OssModule } from "src/app/oss.module"; import { OssModule } from "src/app/oss.module";
@@ -10,8 +9,6 @@ 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";
@@ -21,6 +18,8 @@ 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";
@@ -28,7 +27,7 @@ import { SetupProviderComponent } from "./setup/setup-provider.component";
import { SetupComponent } from "./setup/setup.component"; import { SetupComponent } from "./setup/setup.component";
@NgModule({ @NgModule({
imports: [CommonModule, FormsModule, OssModule, JslibModule, ProvidersRoutingModule], imports: [CommonModule, FormsModule, OssModule, ProvidersRoutingModule],
declarations: [ declarations: [
AcceptProviderComponent, AcceptProviderComponent,
AccountComponent, AccountComponent,
@@ -46,7 +45,7 @@ import { SetupComponent } from "./setup/setup.component";
SetupProviderComponent, SetupProviderComponent,
UserAddEditComponent, UserAddEditComponent,
], ],
providers: [WebProviderService, ProviderGuard, PermissionsGuard], providers: [WebProviderService, ProviderGuardService, ProviderTypeGuardService],
}) })
export class ProvidersModule { export class ProvidersModule {
constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) { constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) {

View File

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

View File

@@ -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 PermissionsGuard implements CanActivate { export class ProviderTypeGuardService implements CanActivate {
constructor(private providerService: ProviderService, private router: Router) {} constructor(private providerService: ProviderService, private router: Router) {}
async canActivate(route: ActivatedRouteSnapshot) { async canActivate(route: ActivatedRouteSnapshot) {

View File

@@ -7,7 +7,6 @@
"buttonAction": "https://www.sandbox.paypal.com/cgi-bin/webscr" "buttonAction": "https://www.sandbox.paypal.com/cgi-bin/webscr"
}, },
"dev": { "dev": {
"port": 8080,
"allowedHosts": "auto" "allowedHosts": "auto"
} }
} }

View File

@@ -1,9 +1 @@
{ {}
"dev": {
"proxyApi": "http://localhost:4001",
"proxyIdentity": "http://localhost:33657",
"proxyEvents": "http://localhost:46274",
"proxyNotifications": "http://localhost:61841",
"port": 8081
}
}

2
jslib

Submodule jslib updated: ad37de9373...212bd70986

2011
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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": "rm -rf jslib; git submodule sync --recursive; git -c protocol.version=2 submodule update --init --force --depth=1 --recursive", "sub:init": "git submodule update --init --recursive",
"sub:update": "git submodule update --remote", "sub: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",
@@ -43,7 +43,6 @@
"@types/webpack": "^5.28.0", "@types/webpack": "^5.28.0",
"@typescript-eslint/eslint-plugin": "^5.10.1", "@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/parser": "^5.10.1", "@typescript-eslint/parser": "^5.10.1",
"autoprefixer": "^10.4.2",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.0.0", "copy-webpack-plugin": "^10.0.0",
@@ -60,15 +59,12 @@
"husky": "^7.0.4", "husky": "^7.0.4",
"lint-staged": "^12.1.2", "lint-staged": "^12.1.2",
"mini-css-extract-plugin": "^2.4.5", "mini-css-extract-plugin": "^2.4.5",
"postcss": "^8.4.6",
"postcss-loader": "^6.2.1",
"prettier": "2.5.1", "prettier": "2.5.1",
"process": "^0.11.10", "process": "^0.11.10",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"sass": "^1.32.10", "sass": "^1.32.10",
"sass-loader": "^12.4.0", "sass-loader": "^12.4.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"tailwindcss": "^3.0.18",
"terser-webpack-plugin": "^5.2.5", "terser-webpack-plugin": "^5.2.5",
"ts-loader": "^9.2.5", "ts-loader": "^9.2.5",
"typescript": "4.3.5", "typescript": "4.3.5",
@@ -98,13 +94,13 @@
"jszip": "^3.7.1", "jszip": "^3.7.1",
"ngx-infinite-scroll": "^10.0.1", "ngx-infinite-scroll": "^10.0.1",
"ngx-toastr": "14.1.4", "ngx-toastr": "14.1.4",
"node-forge": "^0.10.0",
"popper.js": "1.16.1", "popper.js": "1.16.1",
"qrious": "4.0.2", "qrious": "4.0.2",
"rxjs": "^7.4.0", "rxjs": "^7.4.0",
"sweetalert2": "^10.16.6", "sweetalert2": "^10.16.6",
"webcrypto-shim": "0.1.7", "webcrypto-shim": "0.1.7",
"whatwg-fetch": "3.6.2", "whatwg-fetch": "3.6.2"
"zone.js": "0.11.4"
}, },
"engines": { "engines": {
"node": "~16", "node": "~16",

View File

@@ -1,4 +0,0 @@
/* eslint-disable no-undef */
module.exports = {
plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")],
};

View File

@@ -4,6 +4,8 @@ import { FormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { InfiniteScrollModule } from "ngx-infinite-scroll"; import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
import { AppComponent } from "./app.component"; import { AppComponent } from "./app.component";
import { OssRoutingModule } from "./oss-routing.module"; import { OssRoutingModule } from "./oss-routing.module";
import { OssModule } from "./oss.module"; import { OssModule } from "./oss.module";
@@ -16,6 +18,11 @@ import { WildcardRoutingModule } from "./wildcard-routing.module";
BrowserAnimationsModule, BrowserAnimationsModule,
FormsModule, FormsModule,
ServicesModule, ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
InfiniteScrollModule, InfiniteScrollModule,
DragDropModule, DragDropModule,
OssRoutingModule, OssRoutingModule,

View File

@@ -1,19 +0,0 @@
import { Component } from "@angular/core";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
@Component({
selector: "app-premium-badge",
template: `
<button *appNotPremium bit-badge badgeType="success" (click)="premiumRequired()">
{{ "premium" | i18n }}
</button>
`,
})
export class PremiumBadgeComponent {
constructor(private messagingService: MessagingService) {}
premiumRequired() {
this.messagingService.send("premiumRequired");
}
}

View File

@@ -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">{{ "vaults" | i18n }}</a> <a class="nav-link" routerLink="/vault">{{ "myVault" | 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>
@@ -25,12 +25,7 @@
<a class="nav-link" routerLink="/tools">{{ "tools" | i18n }}</a> <a class="nav-link" routerLink="/tools">{{ "tools" | i18n }}</a>
</li> </li>
<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="/settings">{{ "settings" | i18n }}</a>
</li>
<li *ngIf="organizations.length >= 1" class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/organizations', organizations[0].id]">{{
"organizations" | i18n
}}</a>
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -1,18 +1,12 @@
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",
@@ -22,16 +16,13 @@ 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();
} }
@@ -43,16 +34,11 @@ export class NavbarComponent implements OnInit {
this.name = this.email; this.name = this.email;
} }
// Ensure providers and organizations are loaded // Ensure provides 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() {

View File

@@ -1,3 +1,4 @@
<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">
@@ -26,7 +27,7 @@
</div> </div>
</div> </div>
</div> </div>
<ul class="nav nav-tabs"> <ul class="nav nav-tabs" *ngIf="showMenuBar">
<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>
@@ -45,7 +46,7 @@
{{ "tools" | i18n }} {{ "tools" | i18n }}
</a> </a>
</li> </li>
<li class="nav-item" *ngIf="showSettingsTab"> <li class="nav-item" *ngIf="organization.isOwner">
<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 }}
@@ -56,3 +57,4 @@
</div> </div>
</div> </div>
<router-outlet></router-outlet> <router-outlet></router-outlet>
<app-footer></app-footer>

View File

@@ -5,8 +5,6 @@ import { BroadcasterService } from "jslib-common/abstractions/broadcaster.servic
import { OrganizationService } from "jslib-common/abstractions/organization.service"; import { 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({
@@ -27,7 +25,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: any) => { this.route.params.subscribe(async (params) => {
this.organizationId = params.organizationId; this.organizationId = params.organizationId;
await this.load(); await this.load();
}); });
@@ -50,16 +48,23 @@ 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 NavigationPermissionsService.canAccessManage(this.organization); return (
this.organization.canManageUsers ||
this.organization.canViewAllCollections ||
this.organization.canViewAssignedCollections ||
this.organization.canManageGroups ||
this.organization.canManagePolicies ||
this.organization.canAccessEventLogs
);
} }
get showToolsTab(): boolean { get showToolsTab(): boolean {
return NavigationPermissionsService.canAccessTools(this.organization); return this.organization.canAccessImportExport || this.organization.canAccessReports;
}
get showSettingsTab(): boolean {
return NavigationPermissionsService.canAccessSettings(this.organization);
} }
get toolsRoute(): string { get toolsRoute(): string {

View File

@@ -6,7 +6,6 @@ import "jquery";
import "popper.js"; import "popper.js";
require("../scss/styles.scss"); require("../scss/styles.scss");
require("../scss/tailwind.css");
import { AppModule } from "./app.module"; import { AppModule } from "./app.module";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,27 +0,0 @@
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { VaultFilterComponent as BaseVaultFilterComponent } from "jslib-angular/modules/vault-filter/vault-filter.component";
import { VaultFilterService } from "jslib-angular/modules/vault-filter/vault-filter.service";
@Component({
selector: "app-vault-filter",
templateUrl: "vault-filter.component.html",
})
export class VaultFilterComponent extends BaseVaultFilterComponent {
@Input() showOrgFilter = true;
@Input() showFolders = true;
@Input() showFavorites = true;
@Output() onSearchTextChanged = new EventEmitter<string>();
searchPlaceholder: string;
searchText = "";
constructor(vaultFilterService: VaultFilterService) {
super(vaultFilterService);
}
searchTextChanged() {
this.onSearchTextChanged.emit(this.searchText);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,11 +11,11 @@ import { StateService } from "jslib-common/abstractions/state.service";
import { Cipher } from "jslib-common/models/domain/cipher"; import { Cipher } from "jslib-common/models/domain/cipher";
import { CipherView } from "jslib-common/models/view/cipherView"; import { CipherView } from "jslib-common/models/view/cipherView";
import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent } from "../../reports/exposed-passwords-report.component"; import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent } from "../../tools/exposed-passwords-report.component";
@Component({ @Component({
selector: "app-exposed-passwords-report", selector: "app-exposed-passwords-report",
templateUrl: "../../reports/exposed-passwords-report.component.html", templateUrl: "../../tools/exposed-passwords-report.component.html",
}) })
export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent { export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent {
manageableCiphers: Cipher[]; manageableCiphers: Cipher[];

View File

@@ -10,11 +10,11 @@ import { PasswordRepromptService } from "jslib-common/abstractions/passwordRepro
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { CipherView } from "jslib-common/models/view/cipherView"; import { CipherView } from "jslib-common/models/view/cipherView";
import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../../reports/inactive-two-factor-report.component"; import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../../tools/inactive-two-factor-report.component";
@Component({ @Component({
selector: "app-inactive-two-factor-report", selector: "app-inactive-two-factor-report",
templateUrl: "../../reports/inactive-two-factor-report.component.html", templateUrl: "../../tools/inactive-two-factor-report.component.html",
}) })
export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorReportComponent { export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorReportComponent {
constructor( constructor(

View File

@@ -10,11 +10,11 @@ import { StateService } from "jslib-common/abstractions/state.service";
import { Cipher } from "jslib-common/models/domain/cipher"; import { Cipher } from "jslib-common/models/domain/cipher";
import { CipherView } from "jslib-common/models/view/cipherView"; import { CipherView } from "jslib-common/models/view/cipherView";
import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } from "../../reports/reused-passwords-report.component"; import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } from "../../tools/reused-passwords-report.component";
@Component({ @Component({
selector: "app-reused-passwords-report", selector: "app-reused-passwords-report",
templateUrl: "../../reports/reused-passwords-report.component.html", templateUrl: "../../tools/reused-passwords-report.component.html",
}) })
export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportComponent { export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportComponent {
manageableCiphers: Cipher[]; manageableCiphers: Cipher[];

View File

@@ -9,11 +9,11 @@ import { PasswordRepromptService } from "jslib-common/abstractions/passwordRepro
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { CipherView } from "jslib-common/models/view/cipherView"; import { CipherView } from "jslib-common/models/view/cipherView";
import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponent } from "../../reports/unsecured-websites-report.component"; import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponent } from "../../tools/unsecured-websites-report.component";
@Component({ @Component({
selector: "app-unsecured-websites-report", selector: "app-unsecured-websites-report",
templateUrl: "../../reports/unsecured-websites-report.component.html", templateUrl: "../../tools/unsecured-websites-report.component.html",
}) })
export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesReportComponent { export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesReportComponent {
constructor( constructor(

View File

@@ -11,11 +11,11 @@ import { StateService } from "jslib-common/abstractions/state.service";
import { Cipher } from "jslib-common/models/domain/cipher"; import { Cipher } from "jslib-common/models/domain/cipher";
import { CipherView } from "jslib-common/models/view/cipherView"; import { CipherView } from "jslib-common/models/view/cipherView";
import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from "../../reports/weak-passwords-report.component"; import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from "../../tools/weak-passwords-report.component";
@Component({ @Component({
selector: "app-weak-passwords-report", selector: "app-weak-passwords-report",
templateUrl: "../../reports/weak-passwords-report.component.html", templateUrl: "../../tools/weak-passwords-report.component.html",
}) })
export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportComponent { export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportComponent {
manageableCiphers: Cipher[]; manageableCiphers: Cipher[];

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,6 @@ 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";
@@ -28,7 +27,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 { VaultFilterComponent } from "./vault-filter/vault-filter.component"; import { GroupingsComponent } from "./groupings.component";
const BroadcasterSubscriptionId = "OrgVaultComponent"; const BroadcasterSubscriptionId = "OrgVaultComponent";
@@ -37,7 +36,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("vaultFilter", { static: true }) vaultFilterComponent: VaultFilterComponent; @ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
@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;
@@ -53,7 +52,6 @@ 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,
@@ -77,11 +75,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.vaultFilterComponent.organization = this.organization; this.groupingsComponent.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.vaultFilterComponent.search = qParams.search; this.ciphersComponent.searchText = this.groupingsComponent.searchText = 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) => {
@@ -90,11 +88,7 @@ export class VaultComponent implements OnInit, OnDestroy {
case "syncCompleted": case "syncCompleted":
if (message.successfully) { if (message.successfully) {
await Promise.all([ await Promise.all([
this.vaultFilterComponent.reloadCollectionsAndFolders( this.groupingsComponent.load(),
new VaultFilter({
selectedOrganizationId: this.organization.id,
} as Partial<VaultFilter>)
),
this.ciphersComponent.refresh(), this.ciphersComponent.refresh(),
]); ]);
this.changeDetectorRef.detectChanges(); this.changeDetectorRef.detectChanges();
@@ -104,10 +98,27 @@ export class VaultComponent implements OnInit, OnDestroy {
}); });
}); });
} }
await this.vaultFilterComponent.reloadCollectionsAndFolders( await this.groupingsComponent.load();
new VaultFilter({ selectedOrganizationId: this.organization.id } as Partial<VaultFilter>)
); if (qParams == null) {
await this.ciphersComponent.reload(); this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.reload();
} else {
if (qParams.deleted) {
this.groupingsComponent.selectedTrash = true;
await this.filterDeleted(true);
} else if (qParams.type) {
const t = parseInt(qParams.type, null);
this.groupingsComponent.selectedType = t;
await this.filterCipherType(t, true);
} else if (qParams.collectionId) {
this.groupingsComponent.selectedCollectionId = qParams.collectionId;
await this.filterCollection(qParams.collectionId, true);
} else {
this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.reload();
}
}
if (qParams.viewEvents != null) { 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);
@@ -123,46 +134,63 @@ export class VaultComponent implements OnInit, OnDestroy {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
} }
async applyVaultFilter(vaultFilter: VaultFilter) { async clearGroupingFilters() {
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash"; this.ciphersComponent.showAddNew = true;
this.activeFilter = vaultFilter; this.ciphersComponent.deleted = false;
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash"); this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchVault");
this.go(); await this.ciphersComponent.applyFilter();
this.clearFilters();
this.go(); this.go();
} }
private buildFilter(): (cipher: CipherView) => boolean { async filterCipherType(type: CipherType, load = false) {
return (cipher) => { this.ciphersComponent.showAddNew = true;
let cipherPassesFilter = true; this.ciphersComponent.deleted = false;
if (this.activeFilter.status === "favorites" && cipherPassesFilter) { this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchType");
cipherPassesFilter = cipher.favorite; const filter = (c: CipherView) => c.type === type;
if (load) {
await this.ciphersComponent.reload(filter);
} else {
await this.ciphersComponent.applyFilter(filter);
}
this.clearFilters();
this.type = type;
this.go();
}
async filterCollection(collectionId: string, load = false) {
this.ciphersComponent.showAddNew = true;
this.ciphersComponent.deleted = false;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchCollection");
const filter = (c: CipherView) => {
if (collectionId === "unassigned") {
return c.collectionIds == null || c.collectionIds.length === 0;
} else {
return c.collectionIds != null && c.collectionIds.indexOf(collectionId) > -1;
} }
if (this.activeFilter.status === "trash" && cipherPassesFilter) {
cipherPassesFilter = cipher.isDeleted;
}
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
}
if (
this.activeFilter.selectedFolderId != null &&
this.activeFilter.selectedFolderId != "none" &&
cipherPassesFilter
) {
cipherPassesFilter = cipher.folderId === this.activeFilter.selectedFolderId;
}
if (this.activeFilter.selectedCollectionId != null && cipherPassesFilter) {
cipherPassesFilter =
cipher.collectionIds != null &&
cipher.collectionIds.indexOf(this.activeFilter.selectedCollectionId) > -1;
}
if (this.activeFilter.selectedOrganizationId != null && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === this.activeFilter.selectedOrganizationId;
}
if (this.activeFilter.myVaultOnly && cipherPassesFilter) {
cipherPassesFilter = cipher.organizationId === null;
}
return cipherPassesFilter;
}; };
if (load) {
await this.ciphersComponent.reload(filter);
} else {
await this.ciphersComponent.applyFilter(filter);
}
this.clearFilters();
this.collectionId = collectionId;
this.go();
}
async filterDeleted(load = false) {
this.ciphersComponent.showAddNew = false;
this.ciphersComponent.deleted = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchTrash");
if (load) {
await this.ciphersComponent.reload(null, true);
} else {
await this.ciphersComponent.applyFilter(null);
}
this.clearFilters();
this.deleted = true;
this.go();
} }
filterSearchText(searchText: string) { filterSearchText(searchText: string) {
@@ -204,9 +232,7 @@ 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.vaultFilterComponent.collections.fullList.filter( comp.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
(c) => !c.readOnly
);
} }
comp.organization = this.organization; comp.organization = this.organization;
comp.cipherId = cipher.id; comp.cipherId = cipher.id;
@@ -223,9 +249,7 @@ 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.vaultFilterComponent.collections.fullList.filter( component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
(c) => !c.readOnly
);
} }
if (this.collectionId != null) { if (this.collectionId != null) {
component.collectionIds = [this.collectionId]; component.collectionIds = [this.collectionId];
@@ -262,9 +286,7 @@ 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.vaultFilterComponent.collections.fullList.filter( component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
(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

View File

@@ -1,9 +1,10 @@
import { NgModule } from "@angular/core"; import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "jslib-angular/guards/auth.guard"; import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { LockGuard } from "jslib-angular/guards/lock.guard"; import { LockGuardService } from "jslib-angular/services/lock-guard.service";
import { UnauthGuard } from "jslib-angular/guards/unauth.guard"; import { UnauthGuardService } from "jslib-angular/services/unauth-guard.service";
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";
@@ -22,26 +23,56 @@ 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 { BreachReportComponent } from "./tools/breach-report.component";
import { ExportComponent } from "./tools/export.component"; import { ExportComponent } from "./tools/export.component";
import { GeneratorComponent } from "./tools/generator.component"; import { ExposedPasswordsReportComponent } from "./tools/exposed-passwords-report.component";
import { ImportComponent } from "./tools/import.component"; import { ImportComponent } from "./tools/import.component";
import { InactiveTwoFactorReportComponent } from "./tools/inactive-two-factor-report.component";
import { PasswordGeneratorComponent } from "./tools/password-generator.component";
import { ReusedPasswordsReportComponent } from "./tools/reused-passwords-report.component";
import { ToolsComponent } from "./tools/tools.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"; import { VaultComponent } from "./vault/vault.component";
const routes: Routes = [ const routes: Routes = [
@@ -49,18 +80,18 @@ const routes: Routes = [
path: "", path: "",
component: FrontendLayoutComponent, component: FrontendLayoutComponent,
children: [ children: [
{ path: "", pathMatch: "full", component: LoginComponent, canActivate: [UnauthGuard] }, { path: "", pathMatch: "full", component: LoginComponent, canActivate: [UnauthGuardService] },
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard] }, { path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuardService] },
{ {
path: "register", path: "register",
component: RegisterComponent, component: RegisterComponent,
canActivate: [UnauthGuard], canActivate: [UnauthGuardService],
data: { titleId: "createAccount" }, data: { titleId: "createAccount" },
}, },
{ {
path: "sso", path: "sso",
component: SsoComponent, component: SsoComponent,
canActivate: [UnauthGuard], canActivate: [UnauthGuardService],
data: { titleId: "enterpriseSingleSignOn" }, data: { titleId: "enterpriseSingleSignOn" },
}, },
{ {
@@ -71,13 +102,13 @@ const routes: Routes = [
{ {
path: "hint", path: "hint",
component: HintComponent, component: HintComponent,
canActivate: [UnauthGuard], canActivate: [UnauthGuardService],
data: { titleId: "passwordHint" }, data: { titleId: "passwordHint" },
}, },
{ {
path: "lock", path: "lock",
component: LockComponent, component: LockComponent,
canActivate: [LockGuard], canActivate: [LockGuardService],
}, },
{ path: "verify-email", component: VerifyEmailTokenComponent }, { path: "verify-email", component: VerifyEmailTokenComponent },
{ {
@@ -94,19 +125,19 @@ const routes: Routes = [
{ {
path: "recover-2fa", path: "recover-2fa",
component: RecoverTwoFactorComponent, component: RecoverTwoFactorComponent,
canActivate: [UnauthGuard], canActivate: [UnauthGuardService],
data: { titleId: "recoverAccountTwoStep" }, data: { titleId: "recoverAccountTwoStep" },
}, },
{ {
path: "recover-delete", path: "recover-delete",
component: RecoverDeleteComponent, component: RecoverDeleteComponent,
canActivate: [UnauthGuard], canActivate: [UnauthGuardService],
data: { titleId: "deleteAccount" }, data: { titleId: "deleteAccount" },
}, },
{ {
path: "verify-recover-delete", path: "verify-recover-delete",
component: VerifyRecoverDeleteComponent, component: VerifyRecoverDeleteComponent,
canActivate: [UnauthGuard], canActivate: [UnauthGuardService],
data: { titleId: "deleteAccount" }, data: { titleId: "deleteAccount" },
}, },
{ {
@@ -117,19 +148,19 @@ const routes: Routes = [
{ {
path: "update-temp-password", path: "update-temp-password",
component: UpdateTempPasswordComponent, component: UpdateTempPasswordComponent,
canActivate: [AuthGuard], canActivate: [AuthGuardService],
data: { titleId: "updateTempPassword" }, data: { titleId: "updateTempPassword" },
}, },
{ {
path: "update-password", path: "update-password",
component: UpdatePasswordComponent, component: UpdatePasswordComponent,
canActivate: [AuthGuard], canActivate: [AuthGuardService],
data: { titleId: "updatePassword" }, data: { titleId: "updatePassword" },
}, },
{ {
path: "remove-password", path: "remove-password",
component: RemovePasswordComponent, component: RemovePasswordComponent,
canActivate: [AuthGuard], canActivate: [AuthGuardService],
data: { titleId: "removeMasterPassword" }, data: { titleId: "removeMasterPassword" },
}, },
], ],
@@ -137,9 +168,9 @@ const routes: Routes = [
{ {
path: "", path: "",
component: UserLayoutComponent, component: UserLayoutComponent,
canActivate: [AuthGuard], canActivate: [AuthGuardService],
children: [ children: [
{ path: "vault", component: VaultComponent, data: { titleId: "vaults" } }, { path: "vault", component: VaultComponent, data: { titleId: "myVault" } },
{ path: "sends", component: SendComponent, data: { title: "Send" } }, { path: "sends", component: SendComponent, data: { title: "Send" } },
{ {
path: "settings", path: "settings",
@@ -147,20 +178,17 @@ 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" } },
{ {
@@ -203,29 +231,234 @@ const routes: Routes = [
{ {
path: "tools", path: "tools",
component: ToolsComponent, component: ToolsComponent,
canActivate: [AuthGuard], canActivate: [AuthGuardService],
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" } },
{ path: "export", component: ExportComponent, data: { titleId: "exportVault" } }, { path: "export", component: ExportComponent, data: { titleId: "exportVault" } },
{ {
path: "generator", path: "generator",
component: GeneratorComponent, component: PasswordGeneratorComponent,
data: { titleId: "generator" }, data: { titleId: "passwordGenerator" },
},
{
path: "breach-report",
component: BreachReportComponent,
data: { titleId: "dataBreachReport" },
},
{
path: "reused-passwords-report",
component: ReusedPasswordsReportComponent,
data: { titleId: "reusedPasswordsReport" },
},
{
path: "unsecured-websites-report",
component: UnsecuredWebsitesReportComponent,
data: { titleId: "unsecuredWebsitesReport" },
},
{
path: "weak-passwords-report",
component: WeakPasswordsReportComponent,
data: { titleId: "weakPasswordsReport" },
},
{
path: "exposed-passwords-report",
component: ExposedPasswordsReportComponent,
data: { titleId: "exposedPasswordsReport" },
},
{
path: "inactive-two-factor-report",
component: InactiveTwoFactorReportComponent,
data: { titleId: "inactive2faReport" },
},
],
},
{ path: "setup/families-for-enterprise", component: FamiliesForEnterpriseSetupComponent },
],
},
{
path: "organizations/:organizationId",
component: OrganizationLayoutComponent,
canActivate: [AuthGuardService, OrganizationGuardService],
children: [
{ path: "", pathMatch: "full", redirectTo: "vault" },
{ path: "vault", component: OrgVaultComponent, data: { titleId: "vault" } },
{
path: "tools",
component: OrgToolsComponent,
canActivate: [OrganizationTypeGuardService],
data: { permissions: [Permissions.AccessImportExport, Permissions.AccessReports] },
children: [
{
path: "",
pathMatch: "full",
redirectTo: "import",
},
{
path: "import",
component: OrgImportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "importData",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "export",
component: OrgExportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "exportVault",
permissions: [Permissions.AccessImportExport],
},
},
{
path: "exposed-passwords-report",
component: OrgExposedPasswordsReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "exposedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "inactive-two-factor-report",
component: OrgInactiveTwoFactorReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "inactive2faReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "reused-passwords-report",
component: OrgReusedPasswordsReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "reusedPasswordsReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "unsecured-websites-report",
component: OrgUnsecuredWebsitesReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "unsecuredWebsitesReport",
permissions: [Permissions.AccessReports],
},
},
{
path: "weak-passwords-report",
component: OrgWeakPasswordsReportComponent,
canActivate: [OrganizationTypeGuardService],
data: {
titleId: "weakPasswordsReport",
permissions: [Permissions.AccessReports],
},
}, },
], ],
}, },
{ {
path: "reports", path: "manage",
loadChildren: async () => (await import("./reports/reports.module")).ReportsModule, 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: "setup/families-for-enterprise", component: FamiliesForEnterpriseSetupComponent },
{ {
path: "organizations", path: "settings",
loadChildren: () => component: OrgSettingsComponent,
import("./organizations/organization-routing.module").then( canActivate: [OrganizationTypeGuardService],
(m) => m.OrganizationsRoutingModule data: { permissions: [Permissions.ManageOrganization] },
), children: [
{ path: "", pathMatch: "full", redirectTo: "account" },
{ path: "account", component: OrgAccountComponent, data: { titleId: "myOrganization" } },
{
path: "two-factor",
component: OrgTwoFactorSetupComponent,
data: { titleId: "twoStepLogin" },
},
{
path: "billing",
component: OrganizationBillingComponent,
data: { titleId: "billing" },
},
{
path: "subscription",
component: OrganizationSubscriptionComponent,
data: { titleId: "subscription" },
},
],
}, },
], ],
}, },
@@ -236,7 +469,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],

View File

@@ -53,12 +53,32 @@ 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, 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";
import { AvatarComponent } from "jslib-angular/components/avatar.component";
import { CalloutComponent } from "jslib-angular/components/callout.component";
import { ExportScopeCalloutComponent } from "jslib-angular/components/export-scope-callout.component";
import { IconComponent } from "jslib-angular/components/icon.component";
import { VerifyMasterPasswordComponent } from "jslib-angular/components/verify-master-password.component"; import { VerifyMasterPasswordComponent } from "jslib-angular/components/verify-master-password.component";
import { JslibModule } from "jslib-angular/jslib.module"; import { A11yInvalidDirective } from "jslib-angular/directives/a11y-invalid.directive";
import { A11yTitleDirective } from "jslib-angular/directives/a11y-title.directive";
import { ApiActionDirective } from "jslib-angular/directives/api-action.directive";
import { AutofocusDirective } from "jslib-angular/directives/autofocus.directive";
import { BlurClickDirective } from "jslib-angular/directives/blur-click.directive";
import { BoxRowDirective } from "jslib-angular/directives/box-row.directive";
import { FallbackSrcDirective } from "jslib-angular/directives/fallback-src.directive";
import { InputStripSpacesDirective } from "jslib-angular/directives/input-strip-spaces.directive";
import { InputVerbatimDirective } from "jslib-angular/directives/input-verbatim.directive";
import { SelectCopyDirective } from "jslib-angular/directives/select-copy.directive";
import { StopClickDirective } from "jslib-angular/directives/stop-click.directive";
import { StopPropDirective } from "jslib-angular/directives/stop-prop.directive";
import { TrueFalseValueDirective } from "jslib-angular/directives/true-false-value.directive";
import { ColorPasswordPipe } from "jslib-angular/pipes/color-password.pipe";
import { I18nPipe } from "jslib-angular/pipes/i18n.pipe";
import { SearchCiphersPipe } from "jslib-angular/pipes/search-ciphers.pipe";
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
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";
@@ -80,13 +100,11 @@ import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.c
import { NestedCheckboxComponent } from "./components/nested-checkbox.component"; import { NestedCheckboxComponent } from "./components/nested-checkbox.component";
import { PasswordRepromptComponent } from "./components/password-reprompt.component"; import { PasswordRepromptComponent } from "./components/password-reprompt.component";
import { PasswordStrengthComponent } from "./components/password-strength.component"; import { PasswordStrengthComponent } from "./components/password-strength.component";
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";
@@ -136,18 +154,9 @@ 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 { VaultFilterComponent as OrgGroupingsComponent } from "./organizations/vault/vault-filter/vault-filter.component"; import { GroupingsComponent as OrgGroupingsComponent } from "./organizations/vault/groupings.component";
import { VaultComponent as OrgVaultComponent } from "./organizations/vault/vault.component"; import { 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 { ExposedPasswordsReportComponent } from "./reports/exposed-passwords-report.component";
import { InactiveTwoFactorReportComponent } from "./reports/inactive-two-factor-report.component";
import { ReportCardComponent } from "./reports/report-card.component";
import { ReportListComponent } from "./reports/report-list.component";
import { ReportsComponent } from "./reports/reports.component";
import { ReusedPasswordsReportComponent } from "./reports/reused-passwords-report.component";
import { UnsecuredWebsitesReportComponent } from "./reports/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./reports/weak-passwords-report.component";
import { AccessComponent } from "./send/access.component"; import { AccessComponent } from "./send/access.component";
import { AddEditComponent as SendAddEditComponent } from "./send/add-edit.component"; import { AddEditComponent as SendAddEditComponent } from "./send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component"; import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
@@ -172,15 +181,13 @@ 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";
@@ -199,11 +206,17 @@ import { UserBillingComponent } from "./settings/user-billing.component";
import { UserSubscriptionComponent } from "./settings/user-subscription.component"; import { UserSubscriptionComponent } from "./settings/user-subscription.component";
import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component"; import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
import { VerifyEmailComponent } from "./settings/verify-email.component"; import { VerifyEmailComponent } from "./settings/verify-email.component";
import { BreachReportComponent } from "./tools/breach-report.component";
import { ExportComponent } from "./tools/export.component"; import { ExportComponent } from "./tools/export.component";
import { GeneratorComponent } from "./tools/generator.component"; import { ExposedPasswordsReportComponent } from "./tools/exposed-passwords-report.component";
import { ImportComponent } from "./tools/import.component"; import { ImportComponent } from "./tools/import.component";
import { InactiveTwoFactorReportComponent } from "./tools/inactive-two-factor-report.component";
import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component"; import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component";
import { PasswordGeneratorComponent } from "./tools/password-generator.component";
import { ReusedPasswordsReportComponent } from "./tools/reused-passwords-report.component";
import { ToolsComponent } from "./tools/tools.component"; import { ToolsComponent } from "./tools/tools.component";
import { UnsecuredWebsitesReportComponent } from "./tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./tools/weak-passwords-report.component";
import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component"; import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
import { AddEditComponent } from "./vault/add-edit.component"; import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from "./vault/attachments.component"; import { AttachmentsComponent } from "./vault/attachments.component";
@@ -215,6 +228,7 @@ 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";
@@ -272,22 +286,16 @@ registerLocaleData(localeZhTw, "zh-TW");
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, CommonModule,
DragDropModule,
FormsModule, FormsModule,
InfiniteScrollModule, InfiniteScrollModule,
VaultFilterModule, DragDropModule,
JslibModule, ToastrModule,
ReactiveFormsModule, ReactiveFormsModule,
RouterModule, RouterModule,
BadgeModule,
ButtonModule,
CalloutModule,
ToastrModule,
BadgeModule,
ButtonModule,
], ],
declarations: [ declarations: [
PremiumBadgeComponent, A11yTitleDirective,
A11yInvalidDirective,
AcceptEmergencyComponent, AcceptEmergencyComponent,
AcceptOrganizationComponent, AcceptOrganizationComponent,
AccessComponent, AccessComponent,
@@ -299,20 +307,27 @@ registerLocaleData(localeZhTw, "zh-TW");
AdjustPaymentComponent, AdjustPaymentComponent,
AdjustStorageComponent, AdjustStorageComponent,
AdjustSubscription, AdjustSubscription,
ApiActionDirective,
ApiKeyComponent, ApiKeyComponent,
AttachmentsComponent, AttachmentsComponent,
AutofocusDirective,
AvatarComponent,
BlurClickDirective,
BoxRowDirective,
BreachReportComponent, BreachReportComponent,
BulkActionsComponent, BulkActionsComponent,
BulkDeleteComponent, BulkDeleteComponent,
BulkMoveComponent, BulkMoveComponent,
BulkRestoreComponent, BulkRestoreComponent,
BulkShareComponent, BulkShareComponent,
CalloutComponent,
ChangeEmailComponent, ChangeEmailComponent,
ChangeKdfComponent, ChangeKdfComponent,
ChangePasswordComponent, ChangePasswordComponent,
ChangePlanComponent, ChangePlanComponent,
CiphersComponent, CiphersComponent,
CollectionsComponent, CollectionsComponent,
ColorPasswordPipe,
CreateOrganizationComponent, CreateOrganizationComponent,
DeauthorizeSessionsComponent, DeauthorizeSessionsComponent,
DeleteAccountComponent, DeleteAccountComponent,
@@ -328,20 +343,28 @@ registerLocaleData(localeZhTw, "zh-TW");
EmergencyAccessViewComponent, EmergencyAccessViewComponent,
EmergencyAddEditComponent, EmergencyAddEditComponent,
ExportComponent, ExportComponent,
ExportScopeCalloutComponent,
ExposedPasswordsReportComponent, ExposedPasswordsReportComponent,
FallbackSrcDirective,
FamiliesForEnterpriseSetupComponent, FamiliesForEnterpriseSetupComponent,
FolderAddEditComponent, FolderAddEditComponent,
FooterComponent, FooterComponent,
FrontendLayoutComponent, FrontendLayoutComponent,
GroupingsComponent,
HintComponent, HintComponent,
I18nPipe,
IconComponent,
ImportComponent, ImportComponent,
InactiveTwoFactorReportComponent, InactiveTwoFactorReportComponent,
InputStripSpacesDirective,
InputVerbatimDirective,
LinkSsoComponent, LinkSsoComponent,
LockComponent, LockComponent,
LoginComponent, LoginComponent,
MasterPasswordPolicyComponent, MasterPasswordPolicyComponent,
NavbarComponent, NavbarComponent,
NestedCheckboxComponent, NestedCheckboxComponent,
OptionsComponent,
OrgAccountComponent, OrgAccountComponent,
OrgAddEditComponent, OrgAddEditComponent,
OrganizationBillingComponent, OrganizationBillingComponent,
@@ -382,15 +405,13 @@ registerLocaleData(localeZhTw, "zh-TW");
OrgUserGroupsComponent, OrgUserGroupsComponent,
OrgVaultComponent, OrgVaultComponent,
OrgWeakPasswordsReportComponent, OrgWeakPasswordsReportComponent,
GeneratorComponent, PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent, PasswordGeneratorHistoryComponent,
PasswordGeneratorPolicyComponent, PasswordGeneratorPolicyComponent,
PasswordRepromptComponent, PasswordRepromptComponent,
PasswordStrengthComponent, PasswordStrengthComponent,
PaymentComponent, PaymentComponent,
PersonalOwnershipPolicyComponent, PersonalOwnershipPolicyComponent,
PreferencesComponent,
PremiumBadgeComponent,
PremiumComponent, PremiumComponent,
ProfileComponent, ProfileComponent,
ProvidersComponent, ProvidersComponent,
@@ -399,14 +420,12 @@ registerLocaleData(localeZhTw, "zh-TW");
RecoverTwoFactorComponent, RecoverTwoFactorComponent,
RegisterComponent, RegisterComponent,
RemovePasswordComponent, RemovePasswordComponent,
ReportCardComponent,
ReportListComponent,
ReportsComponent,
RequireSsoPolicyComponent, RequireSsoPolicyComponent,
ResetPasswordPolicyComponent, ResetPasswordPolicyComponent,
ReusedPasswordsReportComponent, ReusedPasswordsReportComponent,
SecurityComponent, SearchCiphersPipe,
SecurityKeysComponent, SearchPipe,
SelectCopyDirective,
SendAddEditComponent, SendAddEditComponent,
SendComponent, SendComponent,
SendEffluxDatesComponent, SendEffluxDatesComponent,
@@ -418,8 +437,11 @@ registerLocaleData(localeZhTw, "zh-TW");
SponsoredFamiliesComponent, SponsoredFamiliesComponent,
SponsoringOrgRowComponent, SponsoringOrgRowComponent,
SsoComponent, SsoComponent,
StopClickDirective,
StopPropDirective,
TaxInfoComponent, TaxInfoComponent,
ToolsComponent, ToolsComponent,
TrueFalseValueDirective,
TwoFactorAuthenticationPolicyComponent, TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent, TwoFactorAuthenticatorComponent,
TwoFactorComponent, TwoFactorComponent,
@@ -434,10 +456,11 @@ registerLocaleData(localeZhTw, "zh-TW");
UnsecuredWebsitesReportComponent, UnsecuredWebsitesReportComponent,
UpdateKeyComponent, UpdateKeyComponent,
UpdateLicenseComponent, UpdateLicenseComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent, UpdateTempPasswordComponent,
UpdatePasswordComponent,
UserBillingComponent, UserBillingComponent,
UserLayoutComponent, UserLayoutComponent,
UserNamePipe,
UserSubscriptionComponent, UserSubscriptionComponent,
VaultComponent, VaultComponent,
VaultTimeoutInputComponent, VaultTimeoutInputComponent,
@@ -447,8 +470,23 @@ registerLocaleData(localeZhTw, "zh-TW");
VerifyRecoverDeleteComponent, VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent, WeakPasswordsReportComponent,
], ],
exports: [FooterComponent, NavbarComponent, OrganizationPlansComponent], exports: [
providers: [DatePipe], A11yTitleDirective,
A11yInvalidDirective,
ApiActionDirective,
AvatarComponent,
CalloutComponent,
FooterComponent,
I18nPipe,
InputStripSpacesDirective,
NavbarComponent,
OrganizationPlansComponent,
SearchPipe,
StopClickDirective,
StopPropDirective,
UserNamePipe,
],
providers: [DatePipe, SearchPipe, UserNamePipe],
bootstrap: [], bootstrap: [],
}) })
export class OssModule {} export class OssModule {}

View File

@@ -1,6 +1,11 @@
import "core-js/stable"; import "core-js/stable";
require("zone.js/dist/zone"); require("zone.js/dist/zone");
// IE11 fix, ref: https://github.com/angular/angular/issues/24769
if (!Element.prototype.matches && (Element.prototype as any).msMatchesSelector) {
Element.prototype.matches = (Element.prototype as any).msMatchesSelector;
}
if (process.env.NODE_ENV === "production") { if (process.env.NODE_ENV === "production") {
// Production // Production
} else { } else {

View File

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

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from "@angular/core"; import { Component, Input, OnInit } from "@angular/core";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { ProviderService } from "jslib-common/abstractions/provider.service"; import { ProviderService } from "jslib-common/abstractions/provider.service";
@@ -10,6 +10,8 @@ import { Provider } from "jslib-common/models/domain/provider";
templateUrl: "providers.component.html", 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>;

View File

@@ -1,25 +0,0 @@
<a
class="tw-border tw-border-solid tw-border-secondary-300 tw-rounded tw-overflow-hidden tw-h-full tw-w-72 tw-block !tw-text-main hover:tw-no-underline hover:tw-scale-105 tw-transition-all focus:tw-outline-none focus:tw-ring focus:tw-ring-offset-2 focus:tw-ring-primary-700"
[routerLink]="route"
(click)="click()"
>
<div class="tw-relative">
<div
class="tw-text-center tw-h-28 tw-flex tw-bg-background-alt2 tw-text-primary-300"
[ngClass]="{ 'tw-grayscale': premium }"
>
<div class="tw-m-auto" [innerHtml]="icon"></div>
</div>
<div class="tw-p-5" [ngClass]="{ 'tw-grayscale': report.requiresPremium }">
<h3 class="tw-text-xl tw-font-bold tw-mb-4">{{ report.title | i18n }}</h3>
<p class="tw-mb-0">{{ report.description | i18n }}</p>
</div>
<span
bit-badge
badgeType="success"
class="tw-absolute tw-left-2 tw-top-2 tw-leading-none"
*ngIf="premium"
>{{ "premium" | i18n }}</span
>
</div>
</a>

View File

@@ -1,166 +0,0 @@
import { Component, Input, OnInit } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { StateService } from "jslib-common/abstractions/state.service";
export enum ReportTypes {
"exposedPasswords" = "exposedPasswords",
"reusedPasswords" = "reusedPasswords",
"weakPasswords" = "weakPasswords",
"unsecuredWebsites" = "unsecuredWebsites",
"inactive2fa" = "inactive2fa",
"dataBreach" = "dataBreach",
}
type ReportEntry = {
title: string;
description: string;
route: string;
icon: string;
requiresPremium: boolean;
};
const reports: Record<ReportTypes, ReportEntry> = {
exposedPasswords: {
title: "exposedPasswordsReport",
description: "exposedPasswordsReportDesc",
route: "exposed-passwords-report",
icon: `
<svg width="101" height="77" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.374 50.192a26.42 26.42 0 0 0 9.111 1.608c14.34 0 25.965-11.372 25.965-25.4 0-.337-.007-.673-.02-1.008h25.299v34.85H32.374v-10.05Z" fill="currentColor" />
<path d="M15.805 26.4c0 14.028 11.625 25.4 25.965 25.4s25.964-11.372 25.964-25.4C67.734 12.372 56.11 1 41.77 1 27.43 1 15.805 12.372 15.805 26.4Z" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M27.914 47.849a1 1 0 0 0-2 0h2Zm68.288-26.792a2.12 2.12 0 0 1 2.14 2.11h2c0-2.253-1.83-4.11-4.14-4.11v2Zm2.14 2.11v40.552h2V23.167h-2Zm0 40.552c0 1.172-.958 2.11-2.14 2.11v2c2.25 0 4.14-1.798 4.14-4.11h-2Zm-2.14 2.11H30.054v2h66.148v-2Zm-66.148 0a2.12 2.12 0 0 1-2.14-2.11h-2a4.12 4.12 0 0 0 4.14 4.11v-2Zm-2.14-2.11V47.85h-2v15.87h2Zm39.254-42.662h29.034v-2H67.168v2Z" fill="#fff" />
<path d="M67.203 25.56h25.64v34.85H32.487V50.011" stroke="#fff" stroke-width="2" stroke-linejoin="round" />
<path d="M47.343 76h31.571" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M57.557 66.83V76M67.771 66.83V76" stroke="#fff" stroke-width="2" stroke-linejoin="round" />
<path d="m20.995 42.873-3.972 3.972-14.61 14.61a3.413 3.413 0 0 0 0 4.826v0a3.413 3.413 0 0 0 4.827 0l14.61-14.61 3.972-3.972" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M86.037 32.488H71.845M86.037 37.81H76.28M71.845 37.81h-6.652M86.037 43.132h-6.209M74.95 43.132H61.2M86.037 48.454H71.845M66.967 48.454h-7.54M86.037 53.776H66.08M61.201 53.776h-11.53M44.793 53.776h-7.096" stroke="#fff" stroke-width="2" stroke-linecap="round" />
<rect width="40.801" height="9.757" rx="4" transform="matrix(-1 0 0 1 61.201 14.748)" stroke="#fff" stroke-width="2" />
<path d="M16.852 33.375h28.375a4 4 0 0 1 4 4v1.757a4 4 0 0 1-4 4H22.174M66.523 33.375h-3.539a4 4 0 0 0-4 4v3.761c0 1.102.894 1.996 1.996 1.996v0" stroke="#fff" stroke-width="2" stroke-linecap="round" />
</svg>
`,
requiresPremium: true,
},
reusedPasswords: {
title: "reusedPasswordsReport",
description: "reusedPasswordsReportDesc",
route: "reused-passwords-report",
icon: `
<svg width="102" height="102" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M57.983 15.06a35.664 35.664 0 0 1 14.531 6.27c16.164 11.78 19.585 34.613 7.643 51a37.227 37.227 0 0 1-6.81 7.138m-32.842 6.697a35.708 35.708 0 0 1-11.239-5.495c-16.163-11.78-19.585-34.613-7.642-51a37.55 37.55 0 0 1 3.295-3.929" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M93.909 64.598H7.72c-.708 0-1.275-.662-1.275-1.49V40.273c0-.828.567-1.49 1.275-1.49H93.91c.708 0 1.275.663 1.275 1.49v22.837c.047.827-.567 1.49-1.275 1.49Z" fill="currentColor" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M21.532 52.186v-5.965M21.532 52.187l5.748-1.844M21.532 52.186l3.524 4.881M21.531 52.186l-3.47 4.881M21.532 52.187l-5.694-1.844M40.944 52.186v-5.965M40.944 52.187l5.694-1.844M40.944 52.187l3.525 4.88M40.944 52.187l-3.525 4.88M40.944 52.187l-5.694-1.844M54.849 57.337h11.294M74.21 57.337h11.295M41.75 83l.71 4.75-4.75.71M58.664 18.66 56 14.665 59.996 12" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
`,
requiresPremium: true,
},
weakPasswords: {
title: "weakPasswordsReport",
description: "weakPasswordsReportDesc",
route: "weak-passwords-report",
icon: `
<svg width="78" height="78" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M66.493 64.415V77H9.979V64.324M9.979 44.065V32.106h56.514v12.148" stroke="#fff" stroke-width="2" stroke-linejoin="round" />
<path d="M75.44 64.852H2.085c-.603 0-1.085-.555-1.085-1.25V44.448c0-.694.482-1.25 1.085-1.25H75.44c.603 0 1.085.556 1.085 1.25v19.156c.04.694-.482 1.25-1.085 1.25Z" fill="currentColor" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M13.84 54.56v-5.077M13.84 54.56l4.893-1.57M13.84 54.56l3 4.153M13.84 54.56l-2.954 4.153M13.84 54.56l-4.846-1.57M30.363 54.56v-5.077M30.363 54.56l4.846-1.57M30.363 54.56l3 4.153M30.363 54.56l-3 4.153M30.363 54.56l-4.846-1.57M42.197 59.042h9.506M58.57 59.042h9.507" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M20.863 31.364c-.274-5.285 0-15.817 1.093-18.863 1.276-3.554 6.233-10.826 15.856-11.482 4.83-.273 15.2 2.296 18.043 14.763" stroke="#fff" stroke-width="2" />
</svg>
`,
requiresPremium: true,
},
unsecuredWebsites: {
title: "unsecuredWebsitesReport",
description: "unsecuredWebsitesReportDesc",
route: "unsecured-websites-report",
icon: `
<svg width="113" height="76" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.71 12.983h110.362v55.11a6 6 0 0 1-6 6H7.711a6 6 0 0 1-6-6v-55.11Z" fill="currentColor" />
<rect x="1" y="1.073" width="110.5" height="73.454" rx="9" stroke="#fff" stroke-width="2" />
<path d="M89.48 8.048V7.47M96.363 8.048V7.47M103.246 8.048V7.47" stroke="#fff" stroke-width="4" stroke-linecap="round" />
<path d="M0 12.983h111.217" stroke="#fff" stroke-width="2" />
<path d="m93.236 44.384-18.42-11.026 2.93 21.266 5.582-5.237 4.27 6.46 2.98-1.971-4.26-6.446 6.918-3.046Z" fill="#175DDC" stroke="#fff" stroke-width="2" stroke-linejoin="round" />
<rect width="96.673" height="6.886" rx="3.443" transform="matrix(-1 0 0 1 104.373 18.721)" stroke="#fff" />
</svg>
`,
requiresPremium: true,
},
inactive2fa: {
title: "inactive2faReport",
description: "inactive2faReportDesc",
route: "inactive-two-factor-report",
icon: `
<svg width="42" height="75" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" stroke="#fff" stroke-width="2" d="M1 13.121h39.595v48.758H1z" />
<rect x="1" y="1" width="39.595" height="73" rx="8" stroke="#fff" stroke-width="2" />
<path stroke="#fff" stroke-width="2" stroke-linecap="round" d="M12.344 8.091h16.907M18.907 67.424h3.025M31.503 32.515c-2.047-4.337-6.717-7.061-11.73-6.414a11.356 11.356 0 0 0-9.125 7.126M10.816 42.016c2.047 4.337 6.718 7.062 11.73 6.414 4.346-.562 7.8-3.51 9.213-7.358" />
<path d="m33.584 29.293-1.295 4.625-4.625-1.295M8.523 44.725l1.441-4.581 4.582 1.441" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
`,
requiresPremium: true,
},
dataBreach: {
title: "dataBreachReport",
description: "breachDesc",
route: "breach-report",
icon: `
<svg width="58" height="75" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M39.569 74H13.007a7 7 0 0 1-7-7V31.077a7 7 0 0 1 7-7h19.101a7 7 0 0 1 4.988 2.088l7.46 7.576a7 7 0 0 1 2.013 4.912V67a7 7 0 0 1-7 7Z" fill="#175DDC" stroke="#fff" stroke-width="2" />
<path d="M44.576 69.055H18.015a7 7 0 0 1-7-7V26.132a7 7 0 0 1 7-7h19.1a7 7 0 0 1 4.988 2.088l7.46 7.576a7 7 0 0 1 2.013 4.911v28.348a7 7 0 0 1-7 7Z" fill="#175DDC" stroke="#fff" stroke-width="2" />
<path d="M50 63.698H23.439a7 7 0 0 1-7-7V20.775a7 7 0 0 1 7-7h19.1a7 7 0 0 1 4.988 2.088l7.46 7.575A7 7 0 0 1 57 28.35v28.348a7 7 0 0 1-7 7Z" fill="#175DDC" stroke="#fff" stroke-width="2" />
<path d="M44.648 13.599v3.95a8 8 0 0 0 8 8h4.518" stroke="#fff" stroke-width="2" />
<path stroke="#fff" stroke-width="2" stroke-linecap="round" d="M23.533 37.736H49.49M23.533 46.802H49.49M23.533 42.269H49.49M23.533 55.456H49.49M23.533 50.923H49.49" />
<path d="M1 16.483C1 7.944 8.013 1 16.69 1c8.678 0 15.691 6.944 15.691 15.483 0 8.54-7.013 15.484-15.69 15.484C8.012 31.967 1 25.023 1 16.484Z" fill="#518FFF" stroke="#fff" stroke-width="2" />
<path d="m16.562 7.979.1 11.538" stroke="#fff" stroke-width="2" stroke-linecap="round" />
<ellipse rx="1.252" ry="1.236" transform="rotate(-.479 2802.219 -1964.476) skewX(.012)" fill="#fff" />
</svg>
`,
requiresPremium: false,
},
};
@Component({
selector: "app-report-card",
templateUrl: "report-card.component.html",
})
export class ReportCardComponent implements OnInit {
@Input() type: ReportTypes;
report: ReportEntry;
hasPremium: boolean;
constructor(
private stateService: StateService,
private messagingService: MessagingService,
private sanitizer: DomSanitizer
) {}
async ngOnInit() {
this.report = reports[this.type];
this.hasPremium = await this.stateService.getCanAccessPremium();
}
get premium() {
return this.report.requiresPremium && !this.hasPremium;
}
get route() {
if (this.premium) {
return null;
}
return this.report.route;
}
get icon() {
return this.sanitizer.bypassSecurityTrustHtml(this.report.icon);
}
click() {
if (this.premium) {
this.messagingService.send("premiumRequired");
}
}
}

View File

@@ -1,11 +0,0 @@
<div class="page-header">
<h1>{{ "reports" | i18n }}</h1>
</div>
<p>{{ "reportsDesc" | i18n }}</p>
<div class="tw-inline-grid tw-grid-cols-3 tw-gap-4">
<div *ngFor="let report of reports">
<app-report-card [type]="report"></app-report-card>
</div>
</div>

View File

@@ -1,18 +0,0 @@
import { Component } from "@angular/core";
import { ReportTypes } from "./report-card.component";
@Component({
selector: "app-report-list",
templateUrl: "report-list.component.html",
})
export class ReportListComponent {
reports = [
ReportTypes.exposedPasswords,
ReportTypes.reusedPasswords,
ReportTypes.weakPasswords,
ReportTypes.unsecuredWebsites,
ReportTypes.inactive2fa,
ReportTypes.dataBreach,
];
}

View File

@@ -1,12 +0,0 @@
<div class="container page-content">
<router-outlet></router-outlet>
<div class="row mt-4">
<div class="col">
<a bit-button routerLink="./" *ngIf="!homepage">
<i class="bwi bwi-angle-left" aria-hidden="true"></i>
{{ "backToReports" | i18n }}
</a>
</div>
</div>
</div>

View File

@@ -1,25 +0,0 @@
import { Component, OnDestroy } from "@angular/core";
import { NavigationEnd, Router } from "@angular/router";
import { Subscription } from "rxjs";
import { filter } from "rxjs/operators";
@Component({
selector: "app-reports",
templateUrl: "reports.component.html",
})
export class ReportsComponent implements OnDestroy {
homepage = true;
subscription: Subscription;
constructor(router: Router) {
this.subscription = router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe((event) => {
this.homepage = (event as NavigationEnd).url == "/reports";
});
}
ngOnDestroy(): void {
this.subscription?.unsubscribe();
}
}

View File

@@ -1,60 +0,0 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "jslib-angular/guards/auth.guard";
import { BreachReportComponent } from "./breach-report.component";
import { ExposedPasswordsReportComponent } from "./exposed-passwords-report.component";
import { InactiveTwoFactorReportComponent } from "./inactive-two-factor-report.component";
import { ReportListComponent } from "./report-list.component";
import { ReportsComponent } from "./reports.component";
import { ReusedPasswordsReportComponent } from "./reused-passwords-report.component";
import { UnsecuredWebsitesReportComponent } from "./unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./weak-passwords-report.component";
const routes: Routes = [
{
path: "",
component: ReportsComponent,
canActivate: [AuthGuard],
children: [
{ path: "", pathMatch: "full", component: ReportListComponent, data: { homepage: true } },
{
path: "breach-report",
component: BreachReportComponent,
data: { titleId: "dataBreachReport" },
},
{
path: "reused-passwords-report",
component: ReusedPasswordsReportComponent,
data: { titleId: "reusedPasswordsReport" },
},
{
path: "unsecured-websites-report",
component: UnsecuredWebsitesReportComponent,
data: { titleId: "unsecuredWebsitesReport" },
},
{
path: "weak-passwords-report",
component: WeakPasswordsReportComponent,
data: { titleId: "weakPasswordsReport" },
},
{
path: "exposed-passwords-report",
component: ExposedPasswordsReportComponent,
data: { titleId: "exposedPasswordsReport" },
},
{
path: "inactive-two-factor-report",
component: InactiveTwoFactorReportComponent,
data: { titleId: "inactive2faReport" },
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class ReportsModule {}

View File

@@ -6,7 +6,6 @@ import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SEND_KDF_ITERATIONS } from "jslib-common/enums/kdfType";
import { SendType } from "jslib-common/enums/sendType"; import { SendType } from "jslib-common/enums/sendType";
import { Utils } from "jslib-common/misc/utils"; import { Utils } from "jslib-common/misc/utils";
import { SendAccess } from "jslib-common/models/domain/sendAccess"; import { SendAccess } from "jslib-common/models/domain/sendAccess";
@@ -141,7 +140,7 @@ export class AccessComponent implements OnInit {
this.password, this.password,
keyArray, keyArray,
"sha256", "sha256",
SEND_KDF_ITERATIONS 100000
); );
this.accessRequest.password = Utils.fromBufferToB64(passwordHash); this.accessRequest.password = Utils.fromBufferToB64(passwordHash);
} }

View File

@@ -1,42 +1,33 @@
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 PermissionsGuard extends BaseGuard implements CanActivate { export class OrganizationGuardService implements CanActivate {
constructor( constructor(
router: Router, private 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) {
return this.redirect(); this.router.navigate(["/"]);
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")
); );
return this.redirect(); this.router.navigate(["/"]);
} 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;

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ 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";
@@ -20,7 +21,13 @@ 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(
@@ -31,7 +38,10 @@ export class AccountComponent {
) {} ) {}
async ngOnInit() { async ngOnInit() {
this.showChangeEmail = !(await this.keyConnectorService.getUsesKeyConnector()); this.showChangeEmail =
this.showChangeKdf =
this.showChangePassword =
!(await this.keyConnectorService.getUsesKeyConnector());
} }
async deauthorizeSessions() { async deauthorizeSessions() {
@@ -45,4 +55,33 @@ 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";
});
}
} }

View File

@@ -1,7 +1,4 @@
<div class="tabbed-header"> <app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
<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">
@@ -64,14 +61,14 @@
<div class="col-12"> <div class="col-12">
<div class="form-group"> <div class="form-group">
<div class="small form-text text-muted"> <div class="small form-text text-muted">
<p>{{ "kdfIterationsDesc" | i18n: (recommendedKdfIterations | number) }}</p> <p>{{ "kdfIterationsDesc" | i18n: (100000 | number) }}</p>
<strong>{{ "warning" | i18n }}</strong <strong>{{ "warning" | i18n }}</strong
>: {{ "kdfIterationsWarning" | i18n: (50000 | number) }} >: {{ "kdfIterationsWarning" | i18n: (50000 | number) }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<button bit-button buttonType="primary" class="btn-submit" [disabled]="form.loading"> <button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "changeKdf" | i18n }}</span> <span>{{ "changeKdf" | i18n }}</span>
</button> </button>

View File

@@ -7,7 +7,7 @@ import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service"; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { DEFAULT_KDF_ITERATIONS, KdfType } from "jslib-common/enums/kdfType"; import { KdfType } from "jslib-common/enums/kdfType";
import { KdfRequest } from "jslib-common/models/request/kdfRequest"; import { KdfRequest } from "jslib-common/models/request/kdfRequest";
@Component({ @Component({
@@ -20,7 +20,6 @@ export class ChangeKdfComponent implements OnInit {
kdf = KdfType.PBKDF2_SHA256; kdf = KdfType.PBKDF2_SHA256;
kdfOptions: any[] = []; kdfOptions: any[] = [];
formPromise: Promise<any>; formPromise: Promise<any>;
recommendedKdfIterations = DEFAULT_KDF_ITERATIONS;
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,

View File

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

View File

@@ -1,5 +1,4 @@
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";
@@ -7,7 +6,6 @@ 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";
@@ -49,9 +47,7 @@ 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,
@@ -64,12 +60,6 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
); );
} }
async ngOnInit() {
if (await this.keyConnectorService.getUsesKeyConnector()) {
this.router.navigate(["/settings/security/two-factor"]);
}
}
async rotateEncKeyClicked() { async rotateEncKeyClicked() {
if (this.rotateEncKey) { if (this.rotateEncKey) {
const ciphers = await this.cipherService.getAllDecrypted(); const ciphers = await this.cipherService.getAllDecrypted();

View File

@@ -9,7 +9,7 @@
> >
<div class="modal-header"> <div class="modal-header">
<h2 class="modal-title" id="userAddEditTitle"> <h2 class="modal-title" id="userAddEditTitle">
<app-premium-badge *ngIf="readOnly"></app-premium-badge> <span class="badge badge-primary" *ngIf="readOnly">{{ "premium" | i18n }}</span>
{{ title }} {{ title }}
<small class="text-muted" *ngIf="name">{{ name }}</small> <small class="text-muted" *ngIf="name">{{ name }}</small>
</h2> </h2>

View File

@@ -13,7 +13,7 @@
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{ <a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
c.name c.name
}}</a> }}</a>
<ng-container *ngIf="c.organizationId"> <ng-container *ngIf="!organization && c.organizationId">
<i <i
class="bwi bwi-collection" class="bwi bwi-collection"
appStopProp appStopProp

View File

@@ -15,7 +15,15 @@
<div class="page-header d-flex"> <div class="page-header d-flex">
<h2> <h2>
{{ "trustedEmergencyContacts" | i18n }} {{ "trustedEmergencyContacts" | i18n }}
<app-premium-badge></app-premium-badge> <a
href="#"
appStopClick
class="badge badge-primary"
*ngIf="!canAccessPremium"
(click)="premiumRequired()"
>
{{ "premium" | i18n }}
</a>
</h2> </h2>
<div class="ml-auto d-flex"> <div class="ml-auto d-flex">
<button <button

View File

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

View File

@@ -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-preferences", selector: "app-options",
templateUrl: "preferences.component.html", templateUrl: "options.component.html",
}) })
export class PreferencesComponent implements OnInit { export class OptionsComponent implements OnInit {
vaultTimeoutAction = "lock"; vaultTimeoutAction = "lock";
disableIcons: boolean; disableIcons: boolean;
enableGravatars: boolean; enableGravatars: boolean;
@@ -107,11 +107,7 @@ export class PreferencesComponent implements OnInit {
if (this.locale !== this.startingLocale) { if (this.locale !== this.startingLocale) {
window.location.reload(); window.location.reload();
} else { } else {
this.platformUtilsService.showToast( this.platformUtilsService.showToast("success", null, this.i18nService.t("optionsUpdated"));
"success",
null,
this.i18nService.t("preferencesUpdated")
);
} }
} }

View File

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

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from "@angular/core"; import { Component, Input, OnInit } from "@angular/core";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { CryptoService } from "jslib-common/abstractions/crypto.service";
@@ -19,6 +19,8 @@ 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;
@@ -36,8 +38,10 @@ export class OrganizationsComponent implements OnInit {
) {} ) {}
async ngOnInit() { async ngOnInit() {
await this.syncService.fullSync(true); if (!this.vault) {
await this.load(); await this.syncService.fullSync(true);
await this.load();
}
} }
async load() { async load() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,11 +7,8 @@
<a routerLink="account" class="list-group-item" routerLinkActive="active"> <a routerLink="account" class="list-group-item" routerLinkActive="active">
{{ "myAccount" | i18n }} {{ "myAccount" | i18n }}
</a> </a>
<a routerLink="security" class="list-group-item" routerLinkActive="active"> <a routerLink="options" class="list-group-item" routerLinkActive="active">
{{ "security" | i18n }} {{ "options" | 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 }}
@@ -40,6 +37,9 @@
> >
{{ "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>

View File

@@ -1,14 +1,14 @@
<div class="tabbed-header"> <div class="page-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>
<bit-callout type="warning" *ngIf="!organizationId"> <app-callout type="warning" *ngIf="!organizationId">
<p>{{ "twoStepLoginRecoveryWarning" | i18n }}</p> <p>{{ "twoStepLoginRecoveryWarning" | i18n }}</p>
<button bit-button buttonType="secondary" (click)="recoveryCode()"> <button type="button" class="btn btn-outline-secondary" (click)="recoveryCode()">
{{ "viewRecoveryCode" | i18n }} {{ "viewRecoveryCode" | i18n }}
</button> </button>
</bit-callout> </app-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>
<bit-callout type="warning" *ngIf="showPolicyWarning"> <app-callout type="warning" *ngIf="showPolicyWarning">
{{ "twoStepLoginPolicyUserWarning" | i18n }} {{ "twoStepLoginPolicyUserWarning" | i18n }}
</bit-callout> </app-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">
@@ -39,14 +39,22 @@
></i> ></i>
<span class="sr-only">{{ "enabled" | i18n }}</span> <span class="sr-only">{{ "enabled" | i18n }}</span>
</ng-container> </ng-container>
<app-premium-badge *ngIf="p.premium"></app-premium-badge> <a
href="#"
appStopClick
class="badge badge-primary"
*ngIf="!canAccessPremium && p.premium"
(click)="premiumRequired()"
>
{{ "premium" | i18n }}
</a>
</h3> </h3>
{{ p.description }} {{ p.description }}
</div> </div>
<div class="ml-auto"> <div class="ml-auto">
<button <button
bit-button type="button"
buttonType="secondary" class="btn btn-outline-secondary btn-sm"
[disabled]="!canAccessPremium && p.premium" [disabled]="!canAccessPremium && p.premium"
(click)="manage(p.type)" (click)="manage(p.type)"
> >

View File

@@ -47,9 +47,7 @@ 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 {
// let history = await this.apiService.getUserBillingHistory(); this.billing = await this.apiService.getUserBilling();
// let payment = await this.apiService.getUserBillingPayment();
this.billing = new BillingResponse(null);
} }
this.loading = false; this.loading = false;
} }

View File

@@ -17,13 +17,7 @@
<small class="form-text text-muted">{{ "breachCheckUsernameEmail" | i18n }}</small> <small class="form-text text-muted">{{ "breachCheckUsernameEmail" | i18n }}</small>
</div> </div>
</div> </div>
<button <button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
bit-button
buttonType="primary"
class="btn-submit"
type="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>{{ "checkBreaches" | i18n }}</span> <span>{{ "checkBreaches" | i18n }}</span>
</button> </button>

View File

@@ -2,14 +2,7 @@
<h1>{{ "exposedPasswordsReport" | i18n }}</h1> <h1>{{ "exposedPasswordsReport" | i18n }}</h1>
</div> </div>
<p>{{ "exposedPasswordsReportDesc" | i18n }}</p> <p>{{ "exposedPasswordsReportDesc" | i18n }}</p>
<button <button type="button" class="btn btn-primary btn-submit" [disabled]="loading" (click)="load()">
bit-button
buttonType="primary"
type="button"
class="btn-submit"
[disabled]="loading"
(click)="load()"
>
<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>{{ "checkExposedPasswords" | i18n }}</span> <span>{{ "checkExposedPasswords" | i18n }}</span>
</button> </button>

View File

@@ -1,341 +0,0 @@
<div class="page-header">
<h1>{{ "generator" | i18n }}</h1>
</div>
<app-callout type="info" *ngIf="enforcedPasswordPolicyOptions?.inEffect() && type === 'password'">
{{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout>
<div class="card card-generated bg-light my-4">
<div class="card-body">
<div
*ngIf="type === 'password'"
class="generated-wrapper"
[innerHTML]="password | colorPassword"
appSelectCopy
></div>
<div
*ngIf="type === 'username'"
class="generated-wrapper"
[innerHTML]="username | colorPassword"
appSelectCopy
></div>
</div>
</div>
<div class="form-group">
<label class="d-block">{{ "whatWouldYouLikeToGenerate" | i18n }}</label>
<div class="form-check form-check-inline" *ngFor="let o of typeOptions">
<input
class="form-check-input"
type="radio"
[(ngModel)]="type"
name="Type_{{ o.value }}"
id="type_{{ o.value }}"
[value]="o.value"
(change)="typeChanged()"
[checked]="type === o.value"
/>
<label class="form-check-label" for="type_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<ng-container *ngIf="type === 'password'">
<div class="form-group">
<label class="d-block">{{ "passwordType" | i18n }}</label>
<div class="form-check form-check-inline" *ngFor="let o of passTypeOptions">
<input
class="form-check-input"
type="radio"
[(ngModel)]="passwordOptions.type"
name="PasswordType_{{ o.value }}"
id="passwordType_{{ o.value }}"
[value]="o.value"
(change)="savePasswordOptions()"
[checked]="passwordOptions.type === o.value"
/>
<label class="form-check-label" for="passwordType_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<ng-container *ngIf="passwordOptions.type === 'passphrase'">
<div class="row">
<div class="form-group col-4">
<label for="num-words">{{ "numWords" | i18n }}</label>
<input
id="num-words"
class="form-control"
type="number"
min="3"
max="20"
[(ngModel)]="passwordOptions.numWords"
(blur)="savePasswordOptions()"
/>
</div>
<div class="form-group col-4">
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input
id="word-separator"
class="form-control"
type="text"
maxlength="1"
[(ngModel)]="passwordOptions.wordSeparator"
(blur)="savePasswordOptions()"
/>
</div>
</div>
<label class="d-block">{{ "options" | i18n }}</label>
<div class="form-group">
<div class="form-check">
<input
id="capitalize"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.capitalize"
[disabled]="enforcedPasswordPolicyOptions?.capitalize"
/>
<label for="capitalize" class="form-check-label">{{ "capitalize" | i18n }}</label>
</div>
<div class="form-check">
<input
id="include-number"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.includeNumber"
[disabled]="enforcedPasswordPolicyOptions?.includeNumber"
/>
<label for="include-number" class="form-check-label">{{ "includeNumber" | i18n }}</label>
</div>
</div>
</ng-container>
<ng-container *ngIf="passwordOptions.type === 'password'">
<div class="row">
<div class="form-group col-4">
<label for="length">{{ "length" | i18n }}</label>
<input
id="length"
class="form-control"
type="number"
min="5"
max="128"
[(ngModel)]="passwordOptions.length"
(blur)="savePasswordOptions()"
(change)="lengthChanged()"
/>
</div>
<div class="form-group col-4">
<label for="min-number">{{ "minNumbers" | i18n }}</label>
<input
id="min-number"
class="form-control"
type="number"
min="0"
max="9"
(blur)="savePasswordOptions()"
[(ngModel)]="passwordOptions.minNumber"
(change)="minNumberChanged()"
/>
</div>
<div class="form-group col-4">
<label for="min-special">{{ "minSpecial" | i18n }}</label>
<input
id="min-special"
class="form-control"
type="number"
min="0"
max="9"
(blur)="savePasswordOptions()"
[(ngModel)]="passwordOptions.minSpecial"
(change)="minSpecialChanged()"
/>
</div>
</div>
<label class="d-block">{{ "options" | i18n }}</label>
<div class="form-group">
<div class="form-check">
<input
id="uppercase"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.uppercase"
[disabled]="enforcedPasswordPolicyOptions?.useUppercase"
/>
<label for="uppercase" class="form-check-label">A-Z</label>
</div>
<div class="form-check">
<input
id="lowercase"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.lowercase"
[disabled]="enforcedPasswordPolicyOptions?.useLowercase"
/>
<label for="lowercase" class="form-check-label">a-z</label>
</div>
<div class="form-check">
<input
id="numbers"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.number"
[disabled]="enforcedPasswordPolicyOptions?.useNumbers"
/>
<label for="numbers" class="form-check-label">0-9</label>
</div>
<div class="form-check">
<input
id="special"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.special"
[disabled]="enforcedPasswordPolicyOptions?.useSpecial"
/>
<label for="special" class="form-check-label">!@#$%^&amp;*</label>
</div>
<div class="form-check">
<input
id="ambiguous"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="avoidAmbiguous"
/>
<label for="ambiguous" class="form-check-label">{{ "ambiguous" | i18n }}</label>
</div>
</div>
</ng-container>
<div class="d-flex">
<div>
<button type="button" class="btn btn-primary" (click)="regenerate()">
{{ "regeneratePassword" | i18n }}
</button>
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
{{ "copyPassword" | i18n }}
</button>
</div>
<div class="ml-auto">
<button
type="button"
class="btn btn-outline-secondary"
(click)="history()"
appA11yTitle="{{ 'passwordHistory' | i18n }}"
>
<i class="bwi bwi-clock bwi-lg" aria-hidden="true"></i>
</button>
</div>
</div>
</ng-container>
<ng-container *ngIf="type === 'username'">
<div class="form-group">
<div class="d-block">
<label>{{ "usernameType" | i18n }}</label>
<a
class="ml-auto"
href="https://bitwarden.com/help/generator/#username-types"
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div class="form-check" *ngFor="let o of usernameTypeOptions">
<input
class="form-check-input"
type="radio"
[(ngModel)]="usernameOptions.type"
name="UsernameType_{{ o.value }}"
id="usernameType_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.type === o.value"
/>
<label class="form-check-label" for="usernameType_{{ o.value }}">
{{ o.name }}
<div class="small text-muted">{{ o.desc }}</div>
</label>
</div>
</div>
<div class="form-group" *ngIf="usernameOptions.type === 'forwarded'">
<div class="form-check form-check-inline" *ngFor="let o of forwardOptions">
<input
class="form-check-input"
type="radio"
[(ngModel)]="usernameOptions.forwardedService"
name="ForwardType_{{ o.value }}"
id="forwardtype_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.forwardedService === o.value"
/>
<label class="form-check-label" for="forwardtype_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<div class="row" *ngIf="usernameOptions.type === 'subaddress'">
<div class="form-group col-4">
<label for="subaddress-email">{{ "emailAddress" | i18n }}</label>
<input
id="subaddress-email"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.subaddressEmail"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.type === 'catchall'">
<div class="form-group col-4">
<label for="catchall-domain">{{ "domainName" | i18n }}</label>
<input
id="catchall-domain"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.catchallDomain"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<ng-container *ngIf="usernameOptions.type === 'word'">
<label class="d-block">{{ "options" | i18n }}</label>
<div class="row">
<div class="form-group">
<div class="form-check">
<input
id="capitalizeUsername"
type="checkbox"
(change)="saveUsernameOptions()"
[(ngModel)]="usernameOptions.wordCapitalize"
/>
<label for="capitalizeUsername" class="form-check-label">{{ "capitalize" | i18n }}</label>
</div>
<div class="form-check">
<input
id="includeNumberUsername"
type="checkbox"
(change)="saveUsernameOptions()"
[(ngModel)]="usernameOptions.wordIncludeNumber"
/>
<label for="includeNumberUsername" class="form-check-label">{{
"includeNumber" | i18n
}}</label>
</div>
</div>
</div>
</ng-container>
<div>
<button type="button" class="btn btn-primary" (click)="regenerate()">
{{ "regenerateUsername" | i18n }}
</button>
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
{{ "copyUsername" | i18n }}
</button>
</div>
</ng-container>
<ng-template #historyTemplate></ng-template>

View File

@@ -17,7 +17,7 @@
<li class="list-group-item d-flex" *ngFor="let h of history"> <li class="list-group-item d-flex" *ngFor="let h of history">
<div class="password-row"> <div class="password-row">
<div <div
class="text-monospace generated-wrapper" class="text-monospace password-wrapper"
[innerHTML]="h.password | colorPassword" [innerHTML]="h.password | colorPassword"
appSelectCopy appSelectCopy
></div> ></div>

View File

@@ -0,0 +1,199 @@
<div class="page-header">
<h1>{{ "passwordGenerator" | i18n }}</h1>
</div>
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
{{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout>
<div class="card card-password bg-light my-4">
<div class="card-body">
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
</div>
</div>
<div class="form-group">
<div class="form-check form-check-inline" *ngFor="let o of passTypeOptions">
<input
class="form-check-input"
type="radio"
[(ngModel)]="options.type"
name="Type_{{ o.value }}"
id="type_{{ o.value }}"
[value]="o.value"
(change)="saveOptions()"
[checked]="options.type === o.value"
/>
<label class="form-check-label" for="type_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<ng-container *ngIf="options.type === 'passphrase'">
<div class="row">
<div class="form-group col-4">
<label for="num-words">{{ "numWords" | i18n }}</label>
<input
id="num-words"
class="form-control"
type="number"
min="3"
max="20"
[(ngModel)]="options.numWords"
(blur)="saveOptions()"
/>
</div>
<div class="form-group col-4">
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input
id="word-separator"
class="form-control"
type="text"
maxlength="1"
[(ngModel)]="options.wordSeparator"
(blur)="saveOptions()"
/>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input
id="capitalize"
class="form-check-input"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.capitalize"
[disabled]="enforcedPolicyOptions?.capitalize"
/>
<label for="capitalize" class="form-check-label">{{ "capitalize" | i18n }}</label>
</div>
<div class="form-check">
<input
id="include-number"
class="form-check-input"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.includeNumber"
[disabled]="enforcedPolicyOptions?.includeNumber"
/>
<label for="include-number" class="form-check-label">{{ "includeNumber" | i18n }}</label>
</div>
</div>
</ng-container>
<ng-container *ngIf="options.type === 'password'">
<div class="row">
<div class="form-group col-4">
<label for="length">{{ "length" | i18n }}</label>
<input
id="length"
class="form-control"
type="number"
min="5"
max="128"
[(ngModel)]="options.length"
(blur)="saveOptions()"
(change)="lengthChanged()"
/>
</div>
<div class="form-group col-4">
<label for="min-number">{{ "minNumbers" | i18n }}</label>
<input
id="min-number"
class="form-control"
type="number"
min="0"
max="9"
(blur)="saveOptions()"
[(ngModel)]="options.minNumber"
(change)="minNumberChanged()"
/>
</div>
<div class="form-group col-4">
<label for="min-special">{{ "minSpecial" | i18n }}</label>
<input
id="min-special"
class="form-control"
type="number"
min="0"
max="9"
(blur)="saveOptions()"
[(ngModel)]="options.minSpecial"
(change)="minSpecialChanged()"
/>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input
id="uppercase"
class="form-check-input"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.uppercase"
[disabled]="enforcedPolicyOptions?.useUppercase"
/>
<label for="uppercase" class="form-check-label">A-Z</label>
</div>
<div class="form-check">
<input
id="lowercase"
class="form-check-input"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.lowercase"
[disabled]="enforcedPolicyOptions?.useLowercase"
/>
<label for="lowercase" class="form-check-label">a-z</label>
</div>
<div class="form-check">
<input
id="numbers"
class="form-check-input"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.number"
[disabled]="enforcedPolicyOptions?.useNumbers"
/>
<label for="numbers" class="form-check-label">0-9</label>
</div>
<div class="form-check">
<input
id="special"
class="form-check-input"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.special"
[disabled]="enforcedPolicyOptions?.useSpecial"
/>
<label for="special" class="form-check-label">!@#$%^&amp;*</label>
</div>
<div class="form-check">
<input
id="ambiguous"
class="form-check-input"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="avoidAmbiguous"
/>
<label for="ambiguous" class="form-check-label">{{ "ambiguous" | i18n }}</label>
</div>
</div>
</ng-container>
<div class="d-flex">
<div>
<button type="button" class="btn btn-primary" (click)="regenerate()">
{{ "regeneratePassword" | i18n }}
</button>
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
{{ "copyPassword" | i18n }}
</button>
</div>
<div class="ml-auto">
<button
type="button"
class="btn btn-outline-secondary"
(click)="history()"
appA11yTitle="{{ 'passwordHistory' | i18n }}"
>
<i class="bwi bwi-clock bwi-lg" aria-hidden="true"></i>
</button>
</div>
</div>
<ng-template #historyTemplate></ng-template>

View File

@@ -1,42 +1,28 @@
import { Component, ViewChild, ViewContainerRef } from "@angular/core"; import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { GeneratorComponent as BaseGeneratorComponent } from "jslib-angular/components/generator.component"; import { PasswordGeneratorComponent as BasePasswordGeneratorComponent } from "jslib-angular/components/password-generator.component";
import { ModalService } from "jslib-angular/services/modal.service"; import { ModalService } from "jslib-angular/services/modal.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { UsernameGenerationService } from "jslib-common/abstractions/usernameGeneration.service";
import { PasswordGeneratorHistoryComponent } from "./password-generator-history.component"; import { PasswordGeneratorHistoryComponent } from "./password-generator-history.component";
@Component({ @Component({
selector: "app-generator", selector: "app-password-generator",
templateUrl: "generator.component.html", templateUrl: "password-generator.component.html",
}) })
export class GeneratorComponent extends BaseGeneratorComponent { export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
@ViewChild("historyTemplate", { read: ViewContainerRef, static: true }) @ViewChild("historyTemplate", { read: ViewContainerRef, static: true })
historyModalRef: ViewContainerRef; historyModalRef: ViewContainerRef;
constructor( constructor(
passwordGenerationService: PasswordGenerationService, passwordGenerationService: PasswordGenerationService,
usernameGenerationService: UsernameGenerationService,
stateService: StateService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService, i18nService: I18nService,
route: ActivatedRoute,
private modalService: ModalService private modalService: ModalService
) { ) {
super( super(passwordGenerationService, platformUtilsService, i18nService, window);
passwordGenerationService,
usernameGenerationService,
platformUtilsService,
stateService,
i18nService,
route,
window
);
} }
async history() { async history() {

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