1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 14:23:32 +00:00

Apply Prettier (#1347)

This commit is contained in:
Oscar Hinton
2021-12-17 15:57:11 +01:00
committed by GitHub
parent 2b0a9d995e
commit 56477eb39c
414 changed files with 33390 additions and 26857 deletions

4
.gitattributes vendored
View File

@@ -1,3 +1 @@
*.sh eol=lf * text=auto eol=lf
.dockerignore eol=lf
dockerfile eol=lf

View File

@@ -1,4 +1,5 @@
## Type of change ## Type of change
- [ ] Bug fix - [ ] Bug fix
- [ ] New feature development - [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
@@ -6,27 +7,26 @@
- [ ] Other - [ ] Other
## Objective ## Objective
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding--> <!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
## Code changes ## Code changes
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes--> <!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
<!--Also refer to any related changes or PRs in other repositories--> <!--Also refer to any related changes or PRs in other repositories-->
* **file.ext:** Description of what was changed and why - **file.ext:** Description of what was changed and why
## Screenshots ## Screenshots
<!--Required for any UI changes. Delete if not applicable--> <!--Required for any UI changes. Delete if not applicable-->
## Testing requirements ## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing--> <!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit ## Before you submit
- [ ] I have checked for **linting** errors (`npm run lint`) (required) - [ ] I have checked for **linting** errors (`npm run lint`) (required)
- [ ] This change requires a **documentation update** (notify the documentation team) - [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team) - [ ] This change has particular **deployment requirements** (notify the DevOps team)

View File

@@ -9,8 +9,8 @@ on:
required: false required: false
push: push:
branches-ignore: branches-ignore:
- 'l10n_master' - "l10n_master"
- 'gh-pages' - "gh-pages"
jobs: jobs:
cloc: cloc:
@@ -28,7 +28,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
setup: setup:
name: Setup name: Setup
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -42,7 +41,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
@@ -53,13 +51,13 @@ jobs:
- name: Set up Node - name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with: with:
node-version: '16' node-version: "16"
- name: Cache npm - name: Cache npm
id: npm-cache id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6 uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with: with:
path: '~/.npm' path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Print environment - name: Print environment
@@ -90,7 +88,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
@@ -101,13 +98,13 @@ jobs:
- name: Set up Node - name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with: with:
node-version: '16' node-version: "16"
- name: Cache npm - name: Cache npm
id: npm-cache id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6 uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with: with:
path: '~/.npm' path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Print environment - name: Print environment
@@ -138,7 +135,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
@@ -149,13 +145,13 @@ jobs:
- name: Set up Node - name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with: with:
node-version: '16' node-version: "16"
- name: Cache npm - name: Cache npm
id: npm-cache id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6 uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with: with:
path: '~/.npm' path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Print environment - name: Print environment
@@ -248,7 +244,6 @@ jobs:
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix' if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix'
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
@@ -256,13 +251,13 @@ jobs:
- name: Set up Node - name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with: with:
node-version: '16' node-version: "16"
- name: Cache npm - name: Cache npm
id: npm-cache id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6 uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with: with:
path: '~/.npm' path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Print environment - name: Print environment
@@ -344,7 +339,6 @@ 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
@@ -352,7 +346,7 @@ jobs:
- 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 MSBuild - name: Set up MSBuild
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d
@@ -361,13 +355,13 @@ jobs:
id: npm-cache id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6 uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with: with:
path: '~/.npm' path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Set up Node - name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with: with:
node-version: '16' node-version: "16"
- name: Print environment - name: Print environment
run: | run: |
@@ -388,13 +382,12 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
# - name: Run linter - name: Run linter
# run: npm run lint run: npm run lint
- 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'
@@ -433,7 +426,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

@@ -5,7 +5,7 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: {} inputs: {}
schedule: schedule:
- cron: '0 0 * * 5' - cron: "0 0 * * 5"
jobs: jobs:
crowdin-pull: crowdin-pull:

View File

@@ -23,8 +23,7 @@ jobs:
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Setup - name: Setup
run: run: export PATH=$PATH:~/work/web/web
export PATH=$PATH:~/work/web/web
- name: Login to Azure - name: Login to Azure
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a

View File

@@ -48,7 +48,6 @@ jobs:
BRANCH_NAME=$(basename ${{ github.ref }}) BRANCH_NAME=$(basename ${{ github.ref }})
echo "::set-output name=branch-name::$BRANCH_NAME" echo "::set-output name=branch-name::$BRANCH_NAME"
self-host: self-host:
name: Release self-host docker name: Release self-host docker
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -96,7 +95,6 @@ jobs:
- name: Log out of Docker - name: Log out of Docker
run: docker logout run: docker logout
ghpages-deploy: ghpages-deploy:
name: Deploy Web Vault name: Deploy Web Vault
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -159,7 +157,6 @@ jobs:
--base gh-pages \ --base gh-pages \
--head "$PR_BRANCH" --head "$PR_BRANCH"
release: release:
name: Create GitHub Release name: Create GitHub Release
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04

View File

@@ -6,17 +6,12 @@ Please visit our [Community Forums](https://community.bitwarden.com/) for genera
Here is how you can get involved: Here is how you can get involved:
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one - **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
- **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code - **Report a bug or submit a bugfix:** Use Github issues and pull requests
- **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Report a bug or submit a bugfix:** Use Github issues and pull requests - **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
- **Translate:** See the localization (l10n) section below
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
* **Translate:** See the localization (l10n) section below
## Contributor Agreement ## Contributor Agreement
@@ -24,9 +19,9 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/web)
## Pull Request Guidelines ## Pull Request Guidelines
* use `npm run lint` and fix any linting suggestions before submitting a pull request - use `npm run lint` and fix any linting suggestions before submitting a pull request
* commit any pull requests against the `master` branch - commit any pull requests against the `master` branch
* include a link to your Community Forums post - include a link to your Community Forums post
# Localization (l10n) # Localization (l10n)

View File

@@ -53,11 +53,9 @@ You can also manually adjusting your API endpoint settings by adding `config/loc
"proxyIdentity": "http://your-identity-url", "proxyIdentity": "http://your-identity-url",
"proxyEvents": "http://your-events-url", "proxyEvents": "http://your-events-url",
"proxyNotifications": "http://your-notifications-url", "proxyNotifications": "http://your-notifications-url",
"allowedHosts": ["hostnames-to-allow-in-webpack"], "allowedHosts": ["hostnames-to-allow-in-webpack"]
}, },
"urls": { "urls": {}
}
} }
``` ```

View File

@@ -1,10 +1,10 @@
import { NgModule } from '@angular/core'; import { NgModule } from "@angular/core";
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from "@angular/router";
const routes: Routes = [ const routes: Routes = [
{ {
path: 'providers', path: "providers",
loadChildren: async () => (await import('./providers/providers.module')).ProvidersModule, loadChildren: async () => (await import("./providers/providers.module")).ProvidersModule,
}, },
]; ];
@@ -12,4 +12,4 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], exports: [RouterModule],
}) })
export class AppRoutingModule { } export class AppRoutingModule {}

View File

@@ -1,15 +1,14 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { AppComponent as BaseAppComponent } from 'src/app/app.component'; import { AppComponent as BaseAppComponent } from "src/app/app.component";
import { DisablePersonalVaultExportPolicy } from './policies/disable-personal-vault-export.component'; import { DisablePersonalVaultExportPolicy } from "./policies/disable-personal-vault-export.component";
import { MaximumVaultTimeoutPolicy } from './policies/maximum-vault-timeout.component'; import { MaximumVaultTimeoutPolicy } from "./policies/maximum-vault-timeout.component";
@Component({ @Component({
selector: 'app-root', selector: "app-root",
templateUrl: '../../../src/app/app.component.html', templateUrl: "../../../src/app/app.component.html",
}) })
export class AppComponent extends BaseAppComponent { export class AppComponent extends BaseAppComponent {
ngOnInit() { ngOnInit() {
super.ngOnInit(); super.ngOnInit();
@@ -18,5 +17,4 @@ export class AppComponent extends BaseAppComponent {
new DisablePersonalVaultExportPolicy(), new DisablePersonalVaultExportPolicy(),
]); ]);
} }
} }

View File

@@ -1,22 +1,22 @@
import { DragDropModule } from '@angular/cdk/drag-drop'; import { DragDropModule } from "@angular/cdk/drag-drop";
import { NgModule } from '@angular/core'; import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 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 { BitwardenToastModule } from 'jslib-angular/components/toastr.component'; import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from './app.component'; import { AppComponent } from "./app.component";
import { OrganizationsModule } from './organizations/organizations.module'; import { OrganizationsModule } from "./organizations/organizations.module";
import { DisablePersonalVaultExportPolicyComponent } from './policies/disable-personal-vault-export.component'; import { DisablePersonalVaultExportPolicyComponent } from "./policies/disable-personal-vault-export.component";
import { MaximumVaultTimeoutPolicyComponent } from './policies/maximum-vault-timeout.component'; import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-timeout.component";
import { OssRoutingModule } from 'src/app/oss-routing.module'; import { OssRoutingModule } from "src/app/oss-routing.module";
import { OssModule } from 'src/app/oss.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";
@NgModule({ @NgModule({
imports: [ imports: [
@@ -45,4 +45,4 @@ import { WildcardRoutingModule } from 'src/app/wildcard-routing.module';
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })
export class AppModule { } export class AppModule {}

View File

@@ -1,16 +1,16 @@
import { enableProdMode } from '@angular/core'; import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import 'bootstrap'; import "bootstrap";
import 'jquery'; import "jquery";
import 'popper.js'; import "popper.js";
// tslint:disable-next-line // tslint:disable-next-line
require('src/scss/styles.scss'); require("src/scss/styles.scss");
import { AppModule } from './app.module'; import { AppModule } from "./app.module";
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === "production") {
enableProdMode(); enableProdMode();
} }

View File

@@ -1,68 +1,110 @@
<div class="page-header d-flex"> <div class="page-header d-flex">
<h1>{{'singleSignOn' | i18n}}</h1> <h1>{{ "singleSignOn" | i18n }}</h1>
</div> </div>
<ng-container *ngIf="loading"> <ng-container *ngIf="loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span> <span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container> </ng-container>
<form #form (ngSubmit)="submit()" [formGroup]="data" [appApiAction]="formPromise" *ngIf="!loading" ngNativeValidate> <form
#form
(ngSubmit)="submit()"
[formGroup]="data"
[appApiAction]="formPromise"
*ngIf="!loading"
ngNativeValidate
>
<p> <p>
{{'ssoPolicyHelpStart' | i18n}} {{ "ssoPolicyHelpStart" | i18n }}
<a routerLink="../policies">{{'ssoPolicyHelpLink' | i18n}}</a> <a routerLink="../policies">{{ "ssoPolicyHelpLink" | i18n }}</a>
{{'ssoPolicyHelpEnd' | i18n}} {{ "ssoPolicyHelpEnd" | i18n }}
<br> <br />
{{'ssoPolicyHelpKeyConnector' | i18n}} {{ "ssoPolicyHelpKeyConnector" | i18n }}
</p> </p>
<div class="form-group"> <div class="form-group">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled"> <input
<label class="form-check-label" for="enabled">{{'allowSso' | i18n}}</label> class="form-check-input"
type="checkbox"
id="enabled"
[formControl]="enabled"
name="Enabled"
/>
<label class="form-check-label" for="enabled">{{ "allowSso" | i18n }}</label>
</div> </div>
<small class="form-text text-muted">{{'allowSsoDesc' | i18n}}</small> <small class="form-text text-muted">{{ "allowSsoDesc" | i18n }}</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>{{'memberDecryptionOption' | i18n}}</label> <label>{{ "memberDecryptionOption" | i18n }}</label>
<div class="form-check form-check-block"> <div class="form-check form-check-block">
<input class="form-check-input" type="radio" id="memberDecryptionPass" [value]="false" formControlName="keyConnectorEnabled"> <input
class="form-check-input"
type="radio"
id="memberDecryptionPass"
[value]="false"
formControlName="keyConnectorEnabled"
/>
<label class="form-check-label" for="memberDecryptionPass"> <label class="form-check-label" for="memberDecryptionPass">
{{'masterPass' | i18n}} {{ "masterPass" | i18n }}
<small>{{'memberDecryptionPassDesc' | i18n}}</small> <small>{{ "memberDecryptionPassDesc" | i18n }}</small>
</label> </label>
</div> </div>
<div class="form-check mt-2 form-check-block"> <div class="form-check mt-2 form-check-block">
<input class="form-check-input" type="radio" id="memberDecryptionKey" [value]="true" formControlName="keyConnectorEnabled" <input
[attr.disabled]="!organization.useKeyConnector || null"> class="form-check-input"
type="radio"
id="memberDecryptionKey"
[value]="true"
formControlName="keyConnectorEnabled"
[attr.disabled]="!organization.useKeyConnector || null"
/>
<label class="form-check-label" for="memberDecryptionKey"> <label class="form-check-label" for="memberDecryptionKey">
{{'keyConnector' | i18n}} {{ "keyConnector" | i18n }}
<a target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}" <a
href="https://bitwarden.com/help/article/about-key-connector/"> target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
href="https://bitwarden.com/help/article/about-key-connector/"
>
<i class="fa fa-question-circle-o" aria-hidden="true"></i> <i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a> </a>
<small>{{'memberDecryptionKeyConnectorDesc' | i18n}}</small> <small>{{ "memberDecryptionKeyConnectorDesc" | i18n }}</small>
</label> </label>
</div> </div>
</div> </div>
<ng-container *ngIf="data.value.keyConnectorEnabled"> <ng-container *ngIf="data.value.keyConnectorEnabled">
<app-callout type="warning" [useAlertRole]="true"> <app-callout type="warning" [useAlertRole]="true">
{{'keyConnectorWarning' | i18n}} {{ "keyConnectorWarning" | i18n }}
</app-callout> </app-callout>
<div class="form-group"> <div class="form-group">
<label for="keyConnectorUrl">{{'keyConnectorUrl' | i18n}}</label> <label for="keyConnectorUrl">{{ "keyConnectorUrl" | i18n }}</label>
<div class="input-group"> <div class="input-group">
<input class="form-control" formControlName="keyConnectorUrl" id="keyConnectorUrl" required> <input
class="form-control"
formControlName="keyConnectorUrl"
id="keyConnectorUrl"
required
/>
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="btn btn-outline-secondary" (click)="validateKeyConnectorUrl()" <button
[disabled]="!enableTestKeyConnector"> type="button"
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" class="btn btn-outline-secondary"
*ngIf="keyConnectorUrl.pending"></i> (click)="validateKeyConnectorUrl()"
[disabled]="!enableTestKeyConnector"
>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
*ngIf="keyConnectorUrl.pending"
></i>
<span *ngIf="!keyConnectorUrl.pending"> <span *ngIf="!keyConnectorUrl.pending">
{{'keyConnectorTest' | i18n}} {{ "keyConnectorTest" | i18n }}
</span> </span>
</button> </button>
</div> </div>
@@ -70,20 +112,20 @@
<ng-container *ngIf="keyConnectorUrl.pristine && !keyConnectorUrl.pending"> <ng-container *ngIf="keyConnectorUrl.pristine && !keyConnectorUrl.pending">
<div class="text-danger" *ngIf="keyConnectorUrl.hasError('invalidUrl')" role="alert"> <div class="text-danger" *ngIf="keyConnectorUrl.hasError('invalidUrl')" role="alert">
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> <i class="fa fa-exclamation-circle" aria-hidden="true"></i>
{{'keyConnectorTestFail' | i18n}} {{ "keyConnectorTestFail" | i18n }}
</div> </div>
<div class="text-success" *ngIf="!keyConnectorUrl.hasError('invalidUrl')" role="alert"> <div class="text-success" *ngIf="!keyConnectorUrl.hasError('invalidUrl')" role="alert">
<i class="fa fa-check-circle-o" aria-hidden="true"></i> <i class="fa fa-check-circle-o" aria-hidden="true"></i>
{{'keyConnectorTestSuccess' | i18n}} {{ "keyConnectorTestSuccess" | i18n }}
</div> </div>
</ng-container> </ng-container>
</div> </div>
</ng-container> </ng-container>
<div class="form-group"> <div class="form-group">
<label for="type">{{'type' | i18n}}</label> <label for="type">{{ "type" | i18n }}</label>
<select class="form-control" id="type" formControlName="configType"> <select class="form-control" id="type" formControlName="configType">
<option [ngValue]="0" disabled>{{'selectType' | i18n}}</option> <option [ngValue]="0" disabled>{{ "selectType" | i18n }}</option>
<option [ngValue]="1">OpenID Connect</option> <option [ngValue]="1">OpenID Connect</option>
<option [ngValue]="2">SAML 2.0</option> <option [ngValue]="2">SAML 2.0</option>
</select> </select>
@@ -92,51 +134,57 @@
<!-- OIDC --> <!-- OIDC -->
<div *ngIf="data.value.configType == 1"> <div *ngIf="data.value.configType == 1">
<div class="config-section"> <div class="config-section">
<h2>{{'openIdConnectConfig' | i18n}}</h2> <h2>{{ "openIdConnectConfig" | i18n }}</h2>
<div class="form-group"> <div class="form-group">
<label>{{'callbackPath' | i18n}}</label> <label>{{ "callbackPath" | i18n }}</label>
<div class="input-group"> <div class="input-group">
<input class="form-control" readonly [value]="callbackPath"> <input class="form-control" readonly [value]="callbackPath" />
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="btn btn-outline-secondary" <button
appA11yTitle="{{'copyValue' | i18n}}" type="button"
(click)="copy(callbackPath)"> class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(callbackPath)"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> <i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>{{'signedOutCallbackPath' | i18n}}</label> <label>{{ "signedOutCallbackPath" | i18n }}</label>
<div class="input-group"> <div class="input-group">
<input class="form-control" readonly [value]="signedOutCallbackPath"> <input class="form-control" readonly [value]="signedOutCallbackPath" />
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="btn btn-outline-secondary" <button
appA11yTitle="{{'copyValue' | i18n}}" type="button"
(click)="copy(signedOutCallbackPath)"> class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(signedOutCallbackPath)"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> <i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="authority">{{'authority' | i18n}}</label> <label for="authority">{{ "authority" | i18n }}</label>
<input class="form-control" formControlName="authority" id="authority"> <input class="form-control" formControlName="authority" id="authority" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="clientId">{{'clientId' | i18n}}</label> <label for="clientId">{{ "clientId" | i18n }}</label>
<input class="form-control" formControlName="clientId" id="clientId"> <input class="form-control" formControlName="clientId" id="clientId" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="clientSecret">{{'clientSecret' | i18n}}</label> <label for="clientSecret">{{ "clientSecret" | i18n }}</label>
<input class="form-control" formControlName="clientSecret" id="clientSecret"> <input class="form-control" formControlName="clientSecret" id="clientSecret" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="metadataAddress">{{'metadataAddress' | i18n}}</label> <label for="metadataAddress">{{ "metadataAddress" | i18n }}</label>
<input class="form-control" formControlName="metadataAddress" id="metadataAddress"> <input class="form-control" formControlName="metadataAddress" id="metadataAddress" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="redirectBehavior">{{'oidcRedirectBehavior' | i18n}}</label> <label for="redirectBehavior">{{ "oidcRedirectBehavior" | i18n }}</label>
<select class="form-control" formControlName="redirectBehavior" id="redirectBehavior"> <select class="form-control" formControlName="redirectBehavior" id="redirectBehavior">
<option [ngValue]="0">Redirect GET</option> <option [ngValue]="0">Redirect GET</option>
<option [ngValue]="1">Form POST</option> <option [ngValue]="1">Form POST</option>
@@ -144,39 +192,56 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="getClaimsFromUserInfoEndpoint" <input
formControlName="getClaimsFromUserInfoEndpoint"> class="form-check-input"
type="checkbox"
id="getClaimsFromUserInfoEndpoint"
formControlName="getClaimsFromUserInfoEndpoint"
/>
<label class="form-check-label" for="getClaimsFromUserInfoEndpoint"> <label class="form-check-label" for="getClaimsFromUserInfoEndpoint">
{{'getClaimsFromUserInfoEndpoint' | i18n}} {{ "getClaimsFromUserInfoEndpoint" | i18n }}
</label> </label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="additionalScopes">{{'additionalScopes' | i18n}}</label> <label for="additionalScopes">{{ "additionalScopes" | i18n }}</label>
<input class="form-control" formControlName="additionalScopes" id="additionalScopes"> <input class="form-control" formControlName="additionalScopes" id="additionalScopes" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="additionalUserIdClaimTypes">{{'additionalUserIdClaimTypes' | i18n}}</label> <label for="additionalUserIdClaimTypes">{{ "additionalUserIdClaimTypes" | i18n }}</label>
<input class="form-control" formControlName="additionalUserIdClaimTypes" <input
id="additionalUserIdClaimTypes"> class="form-control"
formControlName="additionalUserIdClaimTypes"
id="additionalUserIdClaimTypes"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="additionalEmailClaimTypes">{{'additionalEmailClaimTypes' | i18n}}</label> <label for="additionalEmailClaimTypes">{{ "additionalEmailClaimTypes" | i18n }}</label>
<input class="form-control" formControlName="additionalEmailClaimTypes" <input
id="additionalEmailClaimTypes"> class="form-control"
formControlName="additionalEmailClaimTypes"
id="additionalEmailClaimTypes"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="additionalNameClaimTypes">{{'additionalNameClaimTypes' | i18n}}</label> <label for="additionalNameClaimTypes">{{ "additionalNameClaimTypes" | i18n }}</label>
<input class="form-control" formControlName="additionalNameClaimTypes" <input
id="additionalNameClaimTypes"> class="form-control"
formControlName="additionalNameClaimTypes"
id="additionalNameClaimTypes"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="acrValues">{{'acrValues' | i18n}}</label> <label for="acrValues">{{ "acrValues" | i18n }}</label>
<input class="form-control" formControlName="acrValues" id="acrValues"> <input class="form-control" formControlName="acrValues" id="acrValues" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="expectedReturnAcrValue">{{'expectedReturnAcrValue' | i18n}}</label> <label for="expectedReturnAcrValue">{{ "expectedReturnAcrValue" | i18n }}</label>
<input class="form-control" formControlName="expectedReturnAcrValue" id="expectedReturnAcrValue"> <input
class="form-control"
formControlName="expectedReturnAcrValue"
id="expectedReturnAcrValue"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -184,53 +249,65 @@
<div *ngIf="data.value.configType == 2"> <div *ngIf="data.value.configType == 2">
<!-- SAML2 SP --> <!-- SAML2 SP -->
<div class="config-section"> <div class="config-section">
<h2>{{'samlSpConfig' | i18n}}</h2> <h2>{{ "samlSpConfig" | i18n }}</h2>
<div class="form-group"> <div class="form-group">
<label>{{'spEntityId' | i18n}}</label> <label>{{ "spEntityId" | i18n }}</label>
<div class="input-group"> <div class="input-group">
<input class="form-control" readonly [value]="spEntityId" > <input class="form-control" readonly [value]="spEntityId" />
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="btn btn-outline-secondary" <button
appA11yTitle="{{'copyValue' | i18n}}" type="button"
(click)="copy(spEntityId)"> class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(spEntityId)"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> <i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>{{'spMetadataUrl' | i18n}}</label> <label>{{ "spMetadataUrl" | i18n }}</label>
<div class="input-group"> <div class="input-group">
<input class="form-control" readonly [value]="spMetadataUrl"> <input class="form-control" readonly [value]="spMetadataUrl" />
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="btn btn-outline-secondary" <button
appA11yTitle="{{'launch' | i18n}}" type="button"
(click)="launchUri(spMetadataUrl)"> class="btn btn-outline-secondary"
appA11yTitle="{{ 'launch' | i18n }}"
(click)="launchUri(spMetadataUrl)"
>
<i class="fa fa-lg fa-external-link" aria-hidden="true"></i> <i class="fa fa-lg fa-external-link" aria-hidden="true"></i>
</button> </button>
<button type="button" class="btn btn-outline-secondary" <button
appA11yTitle="{{'copyValue' | i18n}}" type="button"
(click)="copy(spMetadataUrl)"> class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(spMetadataUrl)"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> <i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>{{'spAcsUrl' | i18n}}</label> <label>{{ "spAcsUrl" | i18n }}</label>
<div class="input-group"> <div class="input-group">
<input class="form-control" readonly [value]="spAcsUrl"> <input class="form-control" readonly [value]="spAcsUrl" />
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="btn btn-outline-secondary" <button
appA11yTitle="{{'copyValue' | i18n}}" type="button"
(click)="copy(spAcsUrl)"> class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(spAcsUrl)"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i> <i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="spNameIdFormat">{{'spNameIdFormat' | i18n}}</label> <label for="spNameIdFormat">{{ "spNameIdFormat" | i18n }}</label>
<select class="form-control" formControlName="spNameIdFormat" id="spNameIdFormat"> <select class="form-control" formControlName="spNameIdFormat" id="spNameIdFormat">
<option value="0">Not Configured</option> <option value="0">Not Configured</option>
<option value="1">Unspecified</option> <option value="1">Unspecified</option>
@@ -244,14 +321,17 @@
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="spOutboundSigningAlgorithm">{{'spOutboundSigningAlgorithm' | i18n}}</label> <label for="spOutboundSigningAlgorithm">{{ "spOutboundSigningAlgorithm" | i18n }}</label>
<select class="form-control" formControlName="spOutboundSigningAlgorithm" <select
id="spOutboundSigningAlgorithm"> class="form-control"
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{o}}</option> formControlName="spOutboundSigningAlgorithm"
id="spOutboundSigningAlgorithm"
>
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="spSigningBehavior">{{'spSigningBehavior' | i18n}}</label> <label for="spSigningBehavior">{{ "spSigningBehavior" | i18n }}</label>
<select class="form-control" formControlName="spSigningBehavior" id="spSigningBehavior"> <select class="form-control" formControlName="spSigningBehavior" id="spSigningBehavior">
<option value="0">If IdP Wants Authn Requests Signed</option> <option value="0">If IdP Wants Authn Requests Signed</option>
<option value="1">Always</option> <option value="1">Always</option>
@@ -259,27 +339,40 @@
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="spMinIncomingSigningAlgorithm">{{'spMinIncomingSigningAlgorithm' | i18n}}</label> <label for="spMinIncomingSigningAlgorithm">{{
<select class="form-control" formControlName="spMinIncomingSigningAlgorithm" "spMinIncomingSigningAlgorithm" | i18n
id="spMinIncomingSigningAlgorithm"> }}</label>
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{o}}</option> <select
class="form-control"
formControlName="spMinIncomingSigningAlgorithm"
id="spMinIncomingSigningAlgorithm"
>
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="spWantAssertionsSigned" <input
formControlName="spWantAssertionsSigned"> class="form-check-input"
type="checkbox"
id="spWantAssertionsSigned"
formControlName="spWantAssertionsSigned"
/>
<label class="form-check-label" for="spWantAssertionsSigned"> <label class="form-check-label" for="spWantAssertionsSigned">
{{'spWantAssertionsSigned' | i18n}} {{ "spWantAssertionsSigned" | i18n }}
</label> </label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="spValidateCertificates" <input
formControlName="spValidateCertificates"> class="form-check-input"
type="checkbox"
id="spValidateCertificates"
formControlName="spValidateCertificates"
/>
<label class="form-check-label" for="spValidateCertificates"> <label class="form-check-label" for="spValidateCertificates">
{{'spValidateCertificates' | i18n}} {{ "spValidateCertificates" | i18n }}
</label> </label>
</div> </div>
</div> </div>
@@ -287,14 +380,14 @@
<!-- SAML2 IDP --> <!-- SAML2 IDP -->
<div class="config-section"> <div class="config-section">
<h2>{{'samlIdpConfig' | i18n}}</h2> <h2>{{ "samlIdpConfig" | i18n }}</h2>
<div class="form-group"> <div class="form-group">
<label for="idpEntityId">{{'idpEntityId' | i18n}}</label> <label for="idpEntityId">{{ "idpEntityId" | i18n }}</label>
<input class="form-control" formControlName="idpEntityId" id="idpEntityId"> <input class="form-control" formControlName="idpEntityId" id="idpEntityId" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="idpBindingType">{{'idpBindingType' | i18n}}</label> <label for="idpBindingType">{{ "idpBindingType" | i18n }}</label>
<select class="form-control" formControlName="idpBindingType" id="idpBindingType"> <select class="form-control" formControlName="idpBindingType" id="idpBindingType">
<option value="1">Redirect</option> <option value="1">Redirect</option>
<option value="2">HTTP POST</option> <option value="2">HTTP POST</option>
@@ -302,54 +395,86 @@
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="idpSingleSignOnServiceUrl">{{'idpSingleSignOnServiceUrl' | i18n}}</label> <label for="idpSingleSignOnServiceUrl">{{ "idpSingleSignOnServiceUrl" | i18n }}</label>
<input class="form-control" formControlName="idpSingleSignOnServiceUrl" id="idpSingleSignOnServiceUrl"> <input
class="form-control"
formControlName="idpSingleSignOnServiceUrl"
id="idpSingleSignOnServiceUrl"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="idpSingleLogoutServiceUrl">{{'idpSingleLogoutServiceUrl' | i18n}}</label> <label for="idpSingleLogoutServiceUrl">{{ "idpSingleLogoutServiceUrl" | i18n }}</label>
<input class="form-control" formControlName="idpSingleLogoutServiceUrl" id="idpSingleLogoutServiceUrl"> <input
class="form-control"
formControlName="idpSingleLogoutServiceUrl"
id="idpSingleLogoutServiceUrl"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="idpArtifactResolutionServiceUrl">{{'idpArtifactResolutionServiceUrl' | i18n}}</label> <label for="idpArtifactResolutionServiceUrl">{{
<input class="form-control" formControlName="idpArtifactResolutionServiceUrl" "idpArtifactResolutionServiceUrl" | i18n
id="idpArtifactResolutionServiceUrl"> }}</label>
<input
class="form-control"
formControlName="idpArtifactResolutionServiceUrl"
id="idpArtifactResolutionServiceUrl"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="idpX509PublicCert">{{'idpX509PublicCert' | i18n}}</label> <label for="idpX509PublicCert">{{ "idpX509PublicCert" | i18n }}</label>
<textarea formControlName="idpX509PublicCert" class="form-control form-control-sm text-monospace" <textarea
rows="6" id="idpX509PublicCert"></textarea> formControlName="idpX509PublicCert"
class="form-control form-control-sm text-monospace"
rows="6"
id="idpX509PublicCert"
></textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="idpOutboundSigningAlgorithm">{{'idpOutboundSigningAlgorithm' | i18n}}</label> <label for="idpOutboundSigningAlgorithm">{{ "idpOutboundSigningAlgorithm" | i18n }}</label>
<select class="form-control" formControlName="idpOutboundSigningAlgorithm" <select
id="idpOutboundSigningAlgorithm"> class="form-control"
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{o}}</option> formControlName="idpOutboundSigningAlgorithm"
id="idpOutboundSigningAlgorithm"
>
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="idpAllowUnsolicitedAuthnResponse" <input
formControlName="idpAllowUnsolicitedAuthnResponse"> class="form-check-input"
type="checkbox"
id="idpAllowUnsolicitedAuthnResponse"
formControlName="idpAllowUnsolicitedAuthnResponse"
/>
<label class="form-check-label" for="idpAllowUnsolicitedAuthnResponse"> <label class="form-check-label" for="idpAllowUnsolicitedAuthnResponse">
{{'idpAllowUnsolicitedAuthnResponse' | i18n}} {{ "idpAllowUnsolicitedAuthnResponse" | i18n }}
</label> </label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="idpDisableOutboundLogoutRequests" <input
formControlName="idpDisableOutboundLogoutRequests"> class="form-check-input"
type="checkbox"
id="idpDisableOutboundLogoutRequests"
formControlName="idpDisableOutboundLogoutRequests"
/>
<label class="form-check-label" for="idpDisableOutboundLogoutRequests"> <label class="form-check-label" for="idpDisableOutboundLogoutRequests">
{{'idpDisableOutboundLogoutRequests' | i18n}} {{ "idpDisableOutboundLogoutRequests" | i18n }}
</label> </label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="idpWantAuthnRequestsSigned" <input
formControlName="idpWantAuthnRequestsSigned"> class="form-check-input"
type="checkbox"
id="idpWantAuthnRequestsSigned"
formControlName="idpWantAuthnRequestsSigned"
/>
<label class="form-check-label" for="idpWantAuthnRequestsSigned"> <label class="form-check-label" for="idpWantAuthnRequestsSigned">
{{'idpWantAuthnRequestsSigned' | i18n}} {{ "idpWantAuthnRequestsSigned" | i18n }}
</label> </label>
</div> </div>
</div> </div>
@@ -357,7 +482,7 @@
</div> </div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"> <button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{'save' | i18n}}</span> <span>{{ "save" | i18n }}</span>
</button> </button>
</form> </form>

View File

@@ -1,30 +1,26 @@
import { import { Component, OnInit } from "@angular/core";
Component, import { FormBuilder } from "@angular/forms";
OnInit, import { ActivatedRoute } from "@angular/router";
} from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
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 { Organization } from 'jslib-common/models/domain/organization'; import { Organization } from "jslib-common/models/domain/organization";
import { OrganizationSsoRequest } from 'jslib-common/models/request/organization/organizationSsoRequest'; import { OrganizationSsoRequest } from "jslib-common/models/request/organization/organizationSsoRequest";
@Component({ @Component({
selector: 'app-org-manage-sso', selector: "app-org-manage-sso",
templateUrl: 'sso.component.html', templateUrl: "sso.component.html",
}) })
export class SsoComponent implements OnInit { export class SsoComponent implements OnInit {
samlSigningAlgorithms = [ samlSigningAlgorithms = [
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
'http://www.w3.org/2000/09/xmldsig#rsa-sha384', "http://www.w3.org/2000/09/xmldsig#rsa-sha384",
'http://www.w3.org/2000/09/xmldsig#rsa-sha512', "http://www.w3.org/2000/09/xmldsig#rsa-sha512",
'http://www.w3.org/2000/09/xmldsig#rsa-sha1', "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
]; ];
loading = true; loading = true;
@@ -79,12 +75,17 @@ export class SsoComponent implements OnInit {
idpWantAuthnRequestsSigned: [], idpWantAuthnRequestsSigned: [],
}); });
constructor(private fb: FormBuilder, private route: ActivatedRoute, private apiService: ApiService, constructor(
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private fb: FormBuilder,
private organizationService: OrganizationService) { } private route: ActivatedRoute,
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private organizationService: OrganizationService
) {}
async ngOnInit() { async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => { this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId; this.organizationId = params.organizationId;
await this.load(); await this.load();
}); });
@@ -125,7 +126,7 @@ export class SsoComponent implements OnInit {
this.data.patchValue(response.data); this.data.patchValue(response.data);
this.enabled.setValue(response.enabled); this.enabled.setValue(response.enabled);
this.platformUtilsService.showToast('success', null, this.i18nService.t('ssoSettingsSaved')); this.platformUtilsService.showToast("success", null, this.i18nService.t("ssoSettingsSaved"));
} catch { } catch {
// Logged by appApiAction, do nothing // Logged by appApiAction, do nothing
} }
@@ -134,11 +135,11 @@ export class SsoComponent implements OnInit {
} }
async postData() { async postData() {
if (this.data.get('keyConnectorEnabled').value) { if (this.data.get("keyConnectorEnabled").value) {
await this.validateKeyConnectorUrl(); await this.validateKeyConnectorUrl();
if (this.keyConnectorUrl.hasError('invalidUrl')) { if (this.keyConnectorUrl.hasError("invalidUrl")) {
throw new Error(this.i18nService.t('keyConnectorTestFail')); throw new Error(this.i18nService.t("keyConnectorTestFail"));
} }
} }
@@ -169,12 +170,14 @@ export class SsoComponent implements OnInit {
} }
get enableTestKeyConnector() { get enableTestKeyConnector() {
return this.data.get('keyConnectorEnabled').value && return (
this.data.get("keyConnectorEnabled").value &&
this.keyConnectorUrl != null && this.keyConnectorUrl != null &&
this.keyConnectorUrl.value !== ''; this.keyConnectorUrl.value !== ""
);
} }
get keyConnectorUrl() { get keyConnectorUrl() {
return this.data.get('keyConnectorUrl'); return this.data.get("keyConnectorUrl");
} }
} }

View File

@@ -1,25 +1,25 @@
import { NgModule } from '@angular/core'; import { NgModule } from "@angular/core";
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service'; import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { Permissions } from 'jslib-common/enums/permissions'; import { Permissions } from "jslib-common/enums/permissions";
import { OrganizationLayoutComponent } from 'src/app/layouts/organization-layout.component'; import { OrganizationLayoutComponent } from "src/app/layouts/organization-layout.component";
import { ManageComponent } from 'src/app/organizations/manage/manage.component'; import { ManageComponent } from "src/app/organizations/manage/manage.component";
import { OrganizationGuardService } from 'src/app/services/organization-guard.service'; import { OrganizationGuardService } from "src/app/services/organization-guard.service";
import { OrganizationTypeGuardService } from 'src/app/services/organization-type-guard.service'; import { OrganizationTypeGuardService } from "src/app/services/organization-type-guard.service";
import { SsoComponent } from './manage/sso.component'; import { SsoComponent } from "./manage/sso.component";
const routes: Routes = [ const routes: Routes = [
{ {
path: 'organizations/:organizationId', path: "organizations/:organizationId",
component: OrganizationLayoutComponent, component: OrganizationLayoutComponent,
canActivate: [AuthGuardService, OrganizationGuardService], canActivate: [AuthGuardService, OrganizationGuardService],
children: [ children: [
{ {
path: 'manage', path: "manage",
component: ManageComponent, component: ManageComponent,
canActivate: [OrganizationTypeGuardService], canActivate: [OrganizationTypeGuardService],
data: { data: {
@@ -38,7 +38,7 @@ const routes: Routes = [
}, },
children: [ children: [
{ {
path: 'sso', path: "sso",
component: SsoComponent, component: SsoComponent,
}, },
], ],
@@ -51,4 +51,4 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], exports: [RouterModule],
}) })
export class OrganizationsRoutingModule { } export class OrganizationsRoutingModule {}

View File

@@ -1,22 +1,14 @@
import { CommonModule } from '@angular/common'; 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 { OssModule } from 'src/app/oss.module'; import { OssModule } from "src/app/oss.module";
import { SsoComponent } from './manage/sso.component'; import { SsoComponent } from "./manage/sso.component";
import { OrganizationsRoutingModule } from './organizations-routing.module'; import { OrganizationsRoutingModule } from "./organizations-routing.module";
@NgModule({ @NgModule({
imports: [ imports: [CommonModule, FormsModule, ReactiveFormsModule, OssModule, OrganizationsRoutingModule],
CommonModule, declarations: [SsoComponent],
FormsModule,
ReactiveFormsModule,
OssModule,
OrganizationsRoutingModule,
],
declarations: [
SsoComponent,
],
}) })
export class OrganizationsModule {} export class OrganizationsModule {}

View File

@@ -1,6 +1,12 @@
<div class="form-group"> <div class="form-group">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled"> <input
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label> class="form-check-input"
type="checkbox"
id="enabled"
[formControl]="enabled"
name="Enabled"
/>
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
</div> </div>
</div> </div>

View File

@@ -1,24 +1,26 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from "@angular/forms";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PolicyType } from 'jslib-common/enums/policyType'; import { PolicyType } from "jslib-common/enums/policyType";
import { PolicyRequest } from 'jslib-common/models/request/policyRequest'; import { PolicyRequest } from "jslib-common/models/request/policyRequest";
import { BasePolicy, BasePolicyComponent } from 'src/app/organizations/policies/base-policy.component'; import {
BasePolicy,
BasePolicyComponent,
} from "src/app/organizations/policies/base-policy.component";
export class DisablePersonalVaultExportPolicy extends BasePolicy { export class DisablePersonalVaultExportPolicy extends BasePolicy {
name = 'disablePersonalVaultExport'; name = "disablePersonalVaultExport";
description = 'disablePersonalVaultExportDesc'; description = "disablePersonalVaultExportDesc";
type = PolicyType.DisablePersonalVaultExport; type = PolicyType.DisablePersonalVaultExport;
component = DisablePersonalVaultExportPolicyComponent; component = DisablePersonalVaultExportPolicyComponent;
} }
@Component({ @Component({
selector: 'policy-disable-personal-vault-export', selector: "policy-disable-personal-vault-export",
templateUrl: 'disable-personal-vault-export.component.html', templateUrl: "disable-personal-vault-export.component.html",
}) })
export class DisablePersonalVaultExportPolicyComponent extends BasePolicyComponent { export class DisablePersonalVaultExportPolicyComponent extends BasePolicyComponent {}
}

View File

@@ -1,26 +1,46 @@
<app-callout type="tip" title="{{'prerequisite' | i18n}}"> <app-callout type="tip" title="{{ 'prerequisite' | i18n }}">
{{'requireSsoPolicyReq' | i18n}} {{ "requireSsoPolicyReq" | i18n }}
</app-callout> </app-callout>
<div class="form-group"> <div class="form-group">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled"> <input
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label> class="form-check-input"
type="checkbox"
id="enabled"
[formControl]="enabled"
name="Enabled"
/>
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
</div> </div>
</div> </div>
<div [formGroup]="data"> <div [formGroup]="data">
<div class="form-group"> <div class="form-group">
<label for="hours">{{'maximumVaultTimeoutLabel' | i18n}}</label> <label for="hours">{{ "maximumVaultTimeoutLabel" | i18n }}</label>
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<input id="hours" class="form-control" type="number" min="0" name="hours" formControlName="hours"> <input
<small>{{'hours' | i18n }}</small> id="hours"
class="form-control"
type="number"
min="0"
name="hours"
formControlName="hours"
/>
<small>{{ "hours" | i18n }}</small>
</div> </div>
<div class="col-6"> <div class="col-6">
<input id="minutes" class="form-control" type="number" min="0" max="59" name="minutes" <input
formControlName="minutes"> id="minutes"
<small>{{'minutes' | i18n }}</small> class="form-control"
type="number"
min="0"
max="59"
name="minutes"
formControlName="minutes"
/>
<small>{{ "minutes" | i18n }}</small>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,27 +1,29 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from "@angular/forms";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PolicyType } from 'jslib-common/enums/policyType'; import { PolicyType } from "jslib-common/enums/policyType";
import { PolicyRequest } from 'jslib-common/models/request/policyRequest'; import { PolicyRequest } from "jslib-common/models/request/policyRequest";
import { BasePolicy, BasePolicyComponent } from 'src/app/organizations/policies/base-policy.component'; import {
BasePolicy,
BasePolicyComponent,
} from "src/app/organizations/policies/base-policy.component";
export class MaximumVaultTimeoutPolicy extends BasePolicy { export class MaximumVaultTimeoutPolicy extends BasePolicy {
name = 'maximumVaultTimeout'; name = "maximumVaultTimeout";
description = 'maximumVaultTimeoutDesc'; description = "maximumVaultTimeoutDesc";
type = PolicyType.MaximumVaultTimeout; type = PolicyType.MaximumVaultTimeout;
component = MaximumVaultTimeoutPolicyComponent; component = MaximumVaultTimeoutPolicyComponent;
} }
@Component({ @Component({
selector: 'policy-maximum-timeout', selector: "policy-maximum-timeout",
templateUrl: 'maximum-vault-timeout.component.html', templateUrl: "maximum-vault-timeout.component.html",
}) })
export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent { export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent {
data = this.fb.group({ data = this.fb.group({
hours: [null], hours: [null],
minutes: [null], minutes: [null],
@@ -57,12 +59,12 @@ export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent {
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> { buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false; const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false;
if (this.enabled.value && !singleOrgEnabled) { if (this.enabled.value && !singleOrgEnabled) {
throw new Error(this.i18nService.t('requireSsoPolicyReqError')); throw new Error(this.i18nService.t("requireSsoPolicyReqError"));
} }
const data = this.buildRequestData(); const data = this.buildRequestData();
if (data?.minutes == null || data?.minutes <= 0) { if (data?.minutes == null || data?.minutes <= 0) {
throw new Error(this.i18nService.t('invalidMaximumVaultTimeout')); throw new Error(this.i18nService.t("invalidMaximumVaultTimeout"));
} }
return super.buildRequest(policiesEnabledMap); return super.buildRequest(policiesEnabledMap);

View File

@@ -3,16 +3,21 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h2 class="modal-title" id="addTitle"> <h2 class="modal-title" id="addTitle">
{{'addExistingOrganization' | i18n}} {{ "addExistingOrganization" | i18n }}
</h2> </h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}"> <button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="card-body text-center" *ngIf="loading"> <div class="card-body text-center" *ngIf="loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{'loading' | i18n}} {{ "loading" | i18n }}
</div> </div>
<ng-container *ngIf="!loading"> <ng-container *ngIf="!loading">
<table class="table table-hover table-list"> <table class="table table-hover table-list">
@@ -21,10 +26,16 @@
<app-avatar [data]="o.name" size="25" [circle]="true" [fontSize]="14"></app-avatar> <app-avatar [data]="o.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
</td> </td>
<td> <td>
{{o.name}} {{ o.name }}
</td> </td>
<td> <td>
<button class="btn btn-outline-secondary pull-right" (click)="add(o)" [disabled]="formPromise">Add</button> <button
class="btn btn-outline-secondary pull-right"
(click)="add(o)"
[disabled]="formPromise"
>
Add
</button>
</td> </td>
</tr> </tr>
</table> </table>

View File

@@ -1,28 +1,21 @@
import { import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
Component,
EventEmitter,
Input,
OnInit,
Output
} from '@angular/core';
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 { ProviderService } from 'jslib-common/abstractions/provider.service'; import { ProviderService } from "jslib-common/abstractions/provider.service";
import { ValidationService } from 'jslib-angular/services/validation.service'; import { ValidationService } from "jslib-angular/services/validation.service";
import { WebProviderService } from '../services/webProvider.service'; import { WebProviderService } from "../services/webProvider.service";
import { Organization } from 'jslib-common/models/domain/organization'; import { Organization } from "jslib-common/models/domain/organization";
import { Provider } from 'jslib-common/models/domain/provider'; import { Provider } from "jslib-common/models/domain/provider";
@Component({ @Component({
selector: 'provider-add-organization', selector: "provider-add-organization",
templateUrl: 'add-organization.component.html', templateUrl: "add-organization.component.html",
}) })
export class AddOrganizationComponent implements OnInit { export class AddOrganizationComponent implements OnInit {
@Input() providerId: string; @Input() providerId: string;
@Input() organizations: Organization[]; @Input() organizations: Organization[];
@Output() onAddedOrganization = new EventEmitter(); @Output() onAddedOrganization = new EventEmitter();
@@ -37,7 +30,7 @@ export class AddOrganizationComponent implements OnInit {
private i18nService: I18nService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private validationService: ValidationService private validationService: ValidationService
) { } ) {}
async ngOnInit() { async ngOnInit() {
await this.load(); await this.load();
@@ -59,15 +52,22 @@ export class AddOrganizationComponent implements OnInit {
} }
const confirmed = await this.platformUtilsService.showDialog( const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('addOrganizationConfirmation', organization.name, this.provider.name), organization.name, this.i18nService.t("addOrganizationConfirmation", organization.name, this.provider.name),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); organization.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) { if (!confirmed) {
return false; return false;
} }
try { try {
this.formPromise = this.webProviderService.addOrganizationToProvider(this.providerId, organization.id); this.formPromise = this.webProviderService.addOrganizationToProvider(
this.providerId,
organization.id
);
await this.formPromise; await this.formPromise;
} catch (e) { } catch (e) {
this.validationService.showError(e); this.validationService.showError(e);
@@ -76,7 +76,11 @@ export class AddOrganizationComponent implements OnInit {
this.formPromise = null; this.formPromise = null;
} }
this.platformUtilsService.showToast('success', null, this.i18nService.t('organizationJoinedProvider')); this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("organizationJoinedProvider")
);
this.onAddedOrganization.emit(); this.onAddedOrganization.emit();
} }
} }

View File

@@ -1,54 +1,78 @@
<div class="page-header d-flex"> <div class="page-header d-flex">
<h1>{{'clients' | i18n}}</h1> <h1>{{ "clients" | i18n }}</h1>
<div class="ml-auto d-flex"> <div class="ml-auto d-flex">
<div> <div>
<label class="sr-only" for="search">{{'search' | i18n}}</label> <label class="sr-only" for="search">{{ "search" | i18n }}</label>
<input type="search" class="form-control form-control-sm" id="search" placeholder="{{'search' | i18n}}" <input
[(ngModel)]="searchText"> type="search"
class="form-control form-control-sm"
id="search"
placeholder="{{ 'search' | i18n }}"
[(ngModel)]="searchText"
/>
</div> </div>
<a class="btn btn-sm btn-outline-primary ml-3" routerLink="create" *ngIf="manageOrganizations"> <a class="btn btn-sm btn-outline-primary ml-3" routerLink="create" *ngIf="manageOrganizations">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i> <i class="fa fa-plus fa-fw" aria-hidden="true"></i>
{{'newClientOrganization' | i18n}} {{ "newClientOrganization" | i18n }}
</a> </a>
<button class="btn btn-sm btn-outline-primary ml-3" (click)="addExistingOrganization()" <button
*ngIf="manageOrganizations && showAddExisting"> class="btn btn-sm btn-outline-primary ml-3"
(click)="addExistingOrganization()"
*ngIf="manageOrganizations && showAddExisting"
>
<i class="fa fa-plus fa-fw" aria-hidden="true"></i> <i class="fa fa-plus fa-fw" aria-hidden="true"></i>
{{'addExistingOrganization' | i18n}} {{ "addExistingOrganization" | i18n }}
</button> </button>
</div> </div>
</div> </div>
<ng-container *ngIf="loading"> <ng-container *ngIf="loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span> <span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container> </ng-container>
<ng-container <ng-container
*ngIf="!loading && (clients | search:searchText:'organizationName':'id') as searchedClients"> *ngIf="!loading && (clients | search: searchText:'organizationName':'id') as searchedClients"
<p *ngIf="!searchedClients.length">{{'noClientsInList' | i18n}}</p> >
<p *ngIf="!searchedClients.length">{{ "noClientsInList" | i18n }}</p>
<ng-container *ngIf="searchedClients.length"> <ng-container *ngIf="searchedClients.length">
<table class="table table-hover table-list" infiniteScroll [infiniteScrollDistance]="1" <table
[infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()"> class="table table-hover table-list"
infiniteScroll
[infiniteScrollDistance]="1"
[infiniteScrollDisabled]="!isPaging()"
(scrolled)="loadMore()"
>
<tbody> <tbody>
<tr *ngFor="let o of searchedClients"> <tr *ngFor="let o of searchedClients">
<td width="30"> <td width="30">
<app-avatar [data]="o.organizationName" size="25" [circle]="true" [fontSize]="14"></app-avatar> <app-avatar
[data]="o.organizationName"
size="25"
[circle]="true"
[fontSize]="14"
></app-avatar>
</td> </td>
<td> <td>
<a [routerLink]="['/organizations', o.organizationId]">{{o.organizationName}}</a> <a [routerLink]="['/organizations', o.organizationId]">{{ o.organizationName }}</a>
</td> </td>
<td class="table-list-options" *ngIf="manageOrganizations"> <td class="table-list-options" *ngIf="manageOrganizations">
<div class="dropdown" appListDropdown> <div class="dropdown" appListDropdown>
<button class="btn btn-outline-secondary dropdown-toggle" type="button" <button
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-outline-secondary dropdown-toggle"
appA11yTitle="{{'options' | i18n}}"> type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
appA11yTitle="{{ 'options' | i18n }}"
>
<i class="fa fa-cog fa-lg" aria-hidden="true"></i> <i class="fa fa-cog fa-lg" aria-hidden="true"></i>
</button> </button>
<div class="dropdown-menu dropdown-menu-right"> <div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(o)"> <a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(o)">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i> <i class="fa fa-fw fa-remove" aria-hidden="true"></i>
{{'remove' | i18n}} {{ "remove" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,44 +1,40 @@
import { import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
Component, import { ActivatedRoute } from "@angular/router";
OnInit,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { first } from 'rxjs/operators'; import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from 'jslib-common/abstractions/organization.service'; import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from 'jslib-common/abstractions/provider.service'; import { ProviderService } from "jslib-common/abstractions/provider.service";
import { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from "jslib-common/abstractions/search.service";
import { ModalService } from 'jslib-angular/services/modal.service'; import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from 'jslib-angular/services/validation.service'; import { ValidationService } from "jslib-angular/services/validation.service";
import { PlanType } from 'jslib-common/enums/planType'; import { PlanType } from "jslib-common/enums/planType";
import { ProviderUserType } from 'jslib-common/enums/providerUserType'; import { ProviderUserType } from "jslib-common/enums/providerUserType";
import { Organization } from 'jslib-common/models/domain/organization'; import { Organization } from "jslib-common/models/domain/organization";
import { import { ProviderOrganizationOrganizationDetailsResponse } from "jslib-common/models/response/provider/providerOrganizationResponse";
ProviderOrganizationOrganizationDetailsResponse
} from 'jslib-common/models/response/provider/providerOrganizationResponse';
import { WebProviderService } from '../services/webProvider.service'; import { WebProviderService } from "../services/webProvider.service";
import { AddOrganizationComponent } from './add-organization.component'; import { AddOrganizationComponent } from "./add-organization.component";
const DisallowedPlanTypes = [PlanType.Free, PlanType.FamiliesAnnually2019, PlanType.FamiliesAnnually]; const DisallowedPlanTypes = [
PlanType.Free,
PlanType.FamiliesAnnually2019,
PlanType.FamiliesAnnually,
];
@Component({ @Component({
templateUrl: 'clients.component.html', templateUrl: "clients.component.html",
}) })
export class ClientsComponent implements OnInit { export class ClientsComponent implements OnInit {
@ViewChild("add", { read: ViewContainerRef, static: true }) addModalRef: ViewContainerRef;
@ViewChild('add', { read: ViewContainerRef, static: true }) addModalRef: ViewContainerRef;
providerId: any; providerId: any;
searchText: string; searchText: string;
@@ -67,15 +63,15 @@ export class ClientsComponent implements OnInit {
private logService: LogService, private logService: LogService,
private modalService: ModalService, private modalService: ModalService,
private organizationService: OrganizationService private organizationService: OrganizationService
) { } ) {}
async ngOnInit() { async ngOnInit() {
this.route.parent.params.subscribe(async params => { this.route.parent.params.subscribe(async (params) => {
this.providerId = params.providerId; this.providerId = params.providerId;
await this.load(); await this.load();
this.route.queryParams.pipe(first()).subscribe(async qParams => { this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
this.searchText = qParams.search; this.searchText = qParams.search;
}); });
}); });
@@ -84,12 +80,17 @@ export class ClientsComponent implements OnInit {
async load() { async load() {
const response = await this.apiService.getProviderClients(this.providerId); const response = await this.apiService.getProviderClients(this.providerId);
this.clients = response.data != null && response.data.length > 0 ? response.data : []; this.clients = response.data != null && response.data.length > 0 ? response.data : [];
this.manageOrganizations = (await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin; this.manageOrganizations =
const candidateOrgs = (await this.organizationService.getAll()).filter(o => o.isOwner && o.providerId == null); (await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin;
const allowedOrgsIds = await Promise.all(candidateOrgs.map(o => this.apiService.getOrganization(o.id))).then(orgs => const candidateOrgs = (await this.organizationService.getAll()).filter(
orgs.filter(o => !DisallowedPlanTypes.includes(o.planType)) (o) => o.isOwner && o.providerId == null
.map(o => o.id)); );
this.addableOrganizations = candidateOrgs.filter(o => allowedOrgsIds.includes(o.id)); const allowedOrgsIds = await Promise.all(
candidateOrgs.map((o) => this.apiService.getOrganization(o.id))
).then((orgs) =>
orgs.filter((o) => !DisallowedPlanTypes.includes(o.planType)).map((o) => o.id)
);
this.addableOrganizations = candidateOrgs.filter((o) => allowedOrgsIds.includes(o.id));
this.showAddExisting = this.addableOrganizations.length !== 0; this.showAddExisting = this.addableOrganizations.length !== 0;
this.loading = false; this.loading = false;
@@ -112,7 +113,6 @@ export class ClientsComponent implements OnInit {
this.loadMore(); this.loadMore();
} }
loadMore() { loadMore() {
if (!this.clients || this.clients.length <= this.pageSize) { if (!this.clients || this.clients.length <= this.pageSize) {
return; return;
@@ -123,14 +123,19 @@ export class ClientsComponent implements OnInit {
pagedSize = this.pagedClientsCount; pagedSize = this.pagedClientsCount;
} }
if (this.clients.length > pagedLength) { if (this.clients.length > pagedLength) {
this.pagedClients = this.pagedClients.concat(this.clients.slice(pagedLength, pagedLength + pagedSize)); this.pagedClients = this.pagedClients.concat(
this.clients.slice(pagedLength, pagedLength + pagedSize)
);
} }
this.pagedClientsCount = this.pagedClients.length; this.pagedClientsCount = this.pagedClients.length;
this.didScroll = this.pagedClients.length > this.pageSize; this.didScroll = this.pagedClients.length > this.pageSize;
} }
async addExistingOrganization() { async addExistingOrganization() {
const [modal] = await this.modalService.openViewRef(AddOrganizationComponent, this.addModalRef, comp => { const [modal] = await this.modalService.openViewRef(
AddOrganizationComponent,
this.addModalRef,
(comp) => {
comp.providerId = this.providerId; comp.providerId = this.providerId;
comp.organizations = this.addableOrganizations; comp.organizations = this.addableOrganizations;
comp.onAddedOrganization.subscribe(async () => { comp.onAddedOrganization.subscribe(async () => {
@@ -141,23 +146,34 @@ export class ClientsComponent implements OnInit {
this.logService.error(`Handled exception: ${e}`); this.logService.error(`Handled exception: ${e}`);
} }
}); });
}); }
);
} }
async remove(organization: ProviderOrganizationOrganizationDetailsResponse) { async remove(organization: ProviderOrganizationOrganizationDetailsResponse) {
const confirmed = await this.platformUtilsService.showDialog( const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('detachOrganizationConfirmation'), organization.organizationName, this.i18nService.t("detachOrganizationConfirmation"),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); organization.organizationName,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) { if (!confirmed) {
return false; return false;
} }
this.actionPromise = this.webProviderService.detachOrganizastion(this.providerId, organization.id); this.actionPromise = this.webProviderService.detachOrganizastion(
this.providerId,
organization.id
);
try { try {
await this.actionPromise; await this.actionPromise;
this.platformUtilsService.showToast('success', null, this.platformUtilsService.showToast(
this.i18nService.t('detachedOrganization', organization.organizationName)); "success",
null,
this.i18nService.t("detachedOrganization", organization.organizationName)
);
await this.load(); await this.load();
} catch (e) { } catch (e) {
this.validationService.showError(e); this.validationService.showError(e);

View File

@@ -1,5 +1,5 @@
<div class="page-header"> <div class="page-header">
<h1>{{'newClientOrganization' | i18n}}</h1> <h1>{{ "newClientOrganization" | i18n }}</h1>
</div> </div>
<p>{{'newClientOrganizationDesc' | i18n}}</p> <p>{{ "newClientOrganizationDesc" | i18n }}</p>
<app-organization-plans [providerId]="providerId"></app-organization-plans> <app-organization-plans [providerId]="providerId"></app-organization-plans>

View File

@@ -1,25 +1,22 @@
import { import { Component, OnInit, ViewChild } from "@angular/core";
Component, import { ActivatedRoute } from "@angular/router";
OnInit,
ViewChild,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { OrganizationPlansComponent } from 'src/app/settings/organization-plans.component'; import { OrganizationPlansComponent } from "src/app/settings/organization-plans.component";
@Component({ @Component({
selector: 'app-create-organization', selector: "app-create-organization",
templateUrl: 'create-organization.component.html', templateUrl: "create-organization.component.html",
}) })
export class CreateOrganizationComponent implements OnInit { export class CreateOrganizationComponent implements OnInit {
@ViewChild(OrganizationPlansComponent, { static: true }) orgPlansComponent: OrganizationPlansComponent; @ViewChild(OrganizationPlansComponent, { static: true })
orgPlansComponent: OrganizationPlansComponent;
providerId: string; providerId: string;
constructor(private route: ActivatedRoute) { } constructor(private route: ActivatedRoute) {}
ngOnInit() { ngOnInit() {
this.route.parent.params.subscribe(async params => { this.route.parent.params.subscribe(async (params) => {
this.providerId = params.providerId; this.providerId = params.providerId;
}); });
} }

View File

@@ -1,31 +1,38 @@
<div class="mt-5 d-flex justify-content-center" *ngIf="loading"> <div class="mt-5 d-flex justify-content-center" *ngIf="loading">
<div> <div>
<img class="mb-4 logo logo-themed" alt="Bitwarden"> <img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center"> <p class="text-center">
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'loading' | i18n}}</span> class="fa fa-spinner fa-spin fa-2x text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p> </p>
</div> </div>
</div> </div>
<div class="container" *ngIf="!loading && !authed"> <div class="container" *ngIf="!loading && !authed">
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5"> <div class="col-5">
<p class="lead text-center mb-4">{{'joinProvider' | i18n}}</p> <p class="lead text-center mb-4">{{ "joinProvider" | i18n }}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">
<p class="text-center"> <p class="text-center">
{{providerName}} {{ providerName }}
<strong class="d-block mt-2">{{email}}</strong> <strong class="d-block mt-2">{{ email }}</strong>
</p> </p>
<p>{{'joinProviderDesc' | i18n}}</p> <p>{{ "joinProviderDesc" | i18n }}</p>
<hr> <hr />
<div class="d-flex"> <div class="d-flex">
<a routerLink="/" [queryParams]="{email: email}" class="btn btn-primary btn-block"> <a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
{{'logIn' | i18n}} {{ "logIn" | i18n }}
</a> </a>
<a routerLink="/register" [queryParams]="{email: email}" <a
class="btn btn-primary btn-block ml-2 mt-0"> routerLink="/register"
{{'createAccount' | i18n}} [queryParams]="{ email: email }"
class="btn btn-primary btn-block ml-2 mt-0"
>
{{ "createAccount" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,25 +1,25 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from "@angular/router";
import { BaseAcceptComponent } from 'src/app/common/base.accept.component'; import { BaseAcceptComponent } from "src/app/common/base.accept.component";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.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 { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { ProviderUserAcceptRequest } from 'jslib-common/models/request/provider/providerUserAcceptRequest'; import { ProviderUserAcceptRequest } from "jslib-common/models/request/provider/providerUserAcceptRequest";
@Component({ @Component({
selector: 'app-accept-provider', selector: "app-accept-provider",
templateUrl: 'accept-provider.component.html', templateUrl: "accept-provider.component.html",
}) })
export class AcceptProviderComponent extends BaseAcceptComponent { export class AcceptProviderComponent extends BaseAcceptComponent {
providerName: string; providerName: string;
failedMessage = 'providerInviteAcceptFailed'; failedMessage = "providerInviteAcceptFailed";
requiredParameters = ['providerId', 'providerUserId', 'token']; requiredParameters = ["providerId", "providerUserId", "token"];
constructor( constructor(
router: Router, router: Router,
@@ -27,7 +27,7 @@ export class AcceptProviderComponent extends BaseAcceptComponent {
route: ActivatedRoute, route: ActivatedRoute,
stateService: StateService, stateService: StateService,
private apiService: ApiService, private apiService: ApiService,
platformUtilService: PlatformUtilsService, platformUtilService: PlatformUtilsService
) { ) {
super(router, platformUtilService, i18nService, route, stateService); super(router, platformUtilService, i18nService, route, stateService);
} }
@@ -36,10 +36,18 @@ export class AcceptProviderComponent extends BaseAcceptComponent {
const request = new ProviderUserAcceptRequest(); const request = new ProviderUserAcceptRequest();
request.token = qParams.token; request.token = qParams.token;
await this.apiService.postProviderUserAccept(qParams.providerId, qParams.providerUserId, request); await this.apiService.postProviderUserAccept(
this.platformUtilService.showToast('success', this.i18nService.t('inviteAccepted'), qParams.providerId,
this.i18nService.t('providerInviteAcceptedDesc'), { timeout: 10000 }); qParams.providerUserId,
this.router.navigate(['/vault']); request
);
this.platformUtilService.showToast(
"success",
this.i18nService.t("inviteAccepted"),
this.i18nService.t("providerInviteAcceptedDesc"),
{ timeout: 10000 }
);
this.router.navigate(["/vault"]);
} }
async unauthedHandler(qParams: any) { async unauthedHandler(qParams: any) {

View File

@@ -1,21 +1,17 @@
import { import { Component, Input } from "@angular/core";
Component,
Input,
} from '@angular/core';
import { ProviderUserBulkConfirmRequest } from 'jslib-common/models/request/provider/providerUserBulkConfirmRequest'; import { ProviderUserBulkConfirmRequest } from "jslib-common/models/request/provider/providerUserBulkConfirmRequest";
import { ProviderUserBulkRequest } from 'jslib-common/models/request/provider/providerUserBulkRequest'; import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest";
import { ProviderUserStatusType } from 'jslib-common/enums/providerUserStatusType'; import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
import { BulkConfirmComponent as OrganizationBulkConfirmComponent } from 'src/app/organizations/manage/bulk/bulk-confirm.component'; import { BulkConfirmComponent as OrganizationBulkConfirmComponent } from "src/app/organizations/manage/bulk/bulk-confirm.component";
import { BulkUserDetails } from 'src/app/organizations/manage/bulk/bulk-status.component'; import { BulkUserDetails } from "src/app/organizations/manage/bulk/bulk-status.component";
@Component({ @Component({
templateUrl: '../../../../../../src/app/organizations/manage/bulk/bulk-confirm.component.html', templateUrl: "../../../../../../src/app/organizations/manage/bulk/bulk-confirm.component.html",
}) })
export class BulkConfirmComponent extends OrganizationBulkConfirmComponent { export class BulkConfirmComponent extends OrganizationBulkConfirmComponent {
@Input() providerId: string; @Input() providerId: string;
protected isAccepted(user: BulkUserDetails) { protected isAccepted(user: BulkUserDetails) {
@@ -23,7 +19,7 @@ export class BulkConfirmComponent extends OrganizationBulkConfirmComponent {
} }
protected async getPublicKeys() { protected async getPublicKeys() {
const request = new ProviderUserBulkRequest(this.filteredUsers.map(user => user.id)); const request = new ProviderUserBulkRequest(this.filteredUsers.map((user) => user.id));
return await this.apiService.postProviderUsersPublicKey(this.providerId, request); return await this.apiService.postProviderUsersPublicKey(this.providerId, request);
} }

View File

@@ -1,21 +1,17 @@
import { import { Component, Input } from "@angular/core";
Component,
Input,
} from '@angular/core';
import { ProviderUserBulkRequest } from 'jslib-common/models/request/provider/providerUserBulkRequest'; import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest";
import { BulkRemoveComponent as OrganizationBulkRemoveComponent } from 'src/app/organizations/manage/bulk/bulk-remove.component'; import { BulkRemoveComponent as OrganizationBulkRemoveComponent } from "src/app/organizations/manage/bulk/bulk-remove.component";
@Component({ @Component({
templateUrl: '../../../../../../src/app/organizations/manage/bulk/bulk-remove.component.html', templateUrl: "../../../../../../src/app/organizations/manage/bulk/bulk-remove.component.html",
}) })
export class BulkRemoveComponent extends OrganizationBulkRemoveComponent { export class BulkRemoveComponent extends OrganizationBulkRemoveComponent {
@Input() providerId: string; @Input() providerId: string;
async deleteUsers() { async deleteUsers() {
const request = new ProviderUserBulkRequest(this.users.map(user => user.id)); const request = new ProviderUserBulkRequest(this.users.map((user) => user.id));
return await this.apiService.deleteManyProviderUsers(this.providerId, request); return await this.apiService.deleteManyProviderUsers(this.providerId, request);
} }
} }

View File

@@ -1,68 +1,103 @@
<div class="page-header d-flex"> <div class="page-header d-flex">
<h1>{{'eventLogs' | i18n}}</h1> <h1>{{ "eventLogs" | i18n }}</h1>
<div class="ml-auto d-flex"> <div class="ml-auto d-flex">
<div class="form-inline"> <div class="form-inline">
<label class="sr-only" for="start">{{'startDate' | i18n}}</label> <label class="sr-only" for="start">{{ "startDate" | i18n }}</label>
<input type="datetime-local" class="form-control form-control-sm" id="start" <input
placeholder="{{'startDate' | i18n}}" [(ngModel)]="start" placeholder="YYYY-MM-DDTHH:MM" type="datetime-local"
(change)="dirtyDates = true"> class="form-control form-control-sm"
id="start"
placeholder="{{ 'startDate' | i18n }}"
[(ngModel)]="start"
placeholder="YYYY-MM-DDTHH:MM"
(change)="dirtyDates = true"
/>
<span class="mx-2">-</span> <span class="mx-2">-</span>
<label class="sr-only" for="end">{{'endDate' | i18n}}</label> <label class="sr-only" for="end">{{ "endDate" | i18n }}</label>
<input type="datetime-local" class="form-control form-control-sm" id="end" <input
placeholder="{{'endDate' | i18n}}" [(ngModel)]="end" placeholder="YYYY-MM-DDTHH:MM" type="datetime-local"
(change)="dirtyDates = true"> class="form-control form-control-sm"
id="end"
placeholder="{{ 'endDate' | i18n }}"
[(ngModel)]="end"
placeholder="YYYY-MM-DDTHH:MM"
(change)="dirtyDates = true"
/>
</div> </div>
<form #refreshForm [appApiAction]="refreshPromise" class="d-inline"> <form #refreshForm [appApiAction]="refreshPromise" class="d-inline">
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="loadEvents(true)" <button
[disabled]="loaded && refreshForm.loading"> type="button"
<i class="fa fa-refresh fa-fw" aria-hidden="true" [ngClass]="{'fa-spin': loaded && refreshForm.loading}"></i> class="btn btn-sm btn-outline-primary ml-3"
{{'refresh' | i18n}} (click)="loadEvents(true)"
[disabled]="loaded && refreshForm.loading"
>
<i
class="fa fa-refresh fa-fw"
aria-hidden="true"
[ngClass]="{ 'fa-spin': loaded && refreshForm.loading }"
></i>
{{ "refresh" | i18n }}
</button> </button>
</form> </form>
<form #exportForm [appApiAction]="exportPromise" class="d-inline"> <form #exportForm [appApiAction]="exportPromise" class="d-inline">
<button type="button" class="btn btn-sm btn-outline-primary btn-submit manual ml-3" <button
[ngClass]="{loading:exportForm.loading}" (click)="exportEvents()" type="button"
[disabled]="loaded && exportForm.loading || dirtyDates"> class="btn btn-sm btn-outline-primary btn-submit manual ml-3"
[ngClass]="{ loading: exportForm.loading }"
(click)="exportEvents()"
[disabled]="(loaded && exportForm.loading) || dirtyDates"
>
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
<span>{{'export' | i18n}}</span> <span>{{ "export" | i18n }}</span>
</button> </button>
</form> </form>
</div> </div>
</div> </div>
<ng-container *ngIf="!loaded"> <ng-container *ngIf="!loaded">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span> <span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="loaded"> <ng-container *ngIf="loaded">
<p *ngIf="!events || !events.length">{{'noEventsInList' | i18n}}</p> <p *ngIf="!events || !events.length">{{ "noEventsInList" | i18n }}</p>
<table class="table table-hover" *ngIf="events && events.length"> <table class="table table-hover" *ngIf="events && events.length">
<thead> <thead>
<tr> <tr>
<th class="border-top-0" width="210">{{'timestamp' | i18n}}</th> <th class="border-top-0" width="210">{{ "timestamp" | i18n }}</th>
<th class="border-top-0" width="40"> <th class="border-top-0" width="40">
<span class="sr-only">{{'device' | i18n}}</span> <span class="sr-only">{{ "device" | i18n }}</span>
</th> </th>
<th class="border-top-0" width="150">{{'user' | i18n}}</th> <th class="border-top-0" width="150">{{ "user" | i18n }}</th>
<th class="border-top-0">{{'event' | i18n}}</th> <th class="border-top-0">{{ "event" | i18n }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let e of events"> <tr *ngFor="let e of events">
<td>{{e.date | date:'medium'}}</td> <td>{{ e.date | date: "medium" }}</td>
<td> <td>
<i class="text-muted fa fa-lg {{e.appIcon}}" title="{{e.appName}}, {{e.ip}}" aria-hidden="true"></i> <i
<span class="sr-only">{{e.appName}}, {{e.ip}}</span> class="text-muted fa fa-lg {{ e.appIcon }}"
title="{{ e.appName }}, {{ e.ip }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ e.appName }}, {{ e.ip }}</span>
</td> </td>
<td> <td>
<span title="{{e.userEmail}}">{{e.userName}}</span> <span title="{{ e.userEmail }}">{{ e.userName }}</span>
</td> </td>
<td [innerHTML]="e.message"></td> <td [innerHTML]="e.message"></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<button #moreBtn [appApiAction]="morePromise" type="button" class="btn btn-block btn-link btn-submit" <button
(click)="loadEvents(false)" [disabled]="loaded && moreBtn.loading" *ngIf="continuationToken"> #moreBtn
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> [appApiAction]="morePromise"
<span>{{'loadMore' | i18n}}</span> type="button"
class="btn btn-block btn-link btn-submit"
(click)="loadEvents(false)"
[disabled]="loaded && moreBtn.loading"
*ngIf="continuationToken"
>
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "loadMore" | i18n }}</span>
</button> </button>
</ng-container> </ng-container>

View File

@@ -1,30 +1,27 @@
import { import { Component, OnInit } from "@angular/core";
Component, import { ActivatedRoute, Router } from "@angular/router";
OnInit,
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { ExportService } from 'jslib-common/abstractions/export.service'; import { ExportService } from "jslib-common/abstractions/export.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.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 { UserNamePipe } from 'jslib-angular/pipes/user-name.pipe'; import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { EventResponse } from 'jslib-common/models/response/eventResponse'; import { EventResponse } from "jslib-common/models/response/eventResponse";
import { EventService } from 'src/app/services/event.service'; import { EventService } from "src/app/services/event.service";
import { BaseEventsComponent } from 'src/app/common/base.events.component'; import { BaseEventsComponent } from "src/app/common/base.events.component";
@Component({ @Component({
selector: 'provider-events', selector: "provider-events",
templateUrl: 'events.component.html', templateUrl: "events.component.html",
}) })
export class EventsComponent extends BaseEventsComponent implements OnInit { export class EventsComponent extends BaseEventsComponent implements OnInit {
exportFileName: string = 'provider-events'; exportFileName: string = "provider-events";
providerId: string; providerId: string;
private providerUsersUserIdMap = new Map<string, any>(); private providerUsersUserIdMap = new Map<string, any>();
@@ -40,23 +37,17 @@ export class EventsComponent extends BaseEventsComponent implements OnInit {
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
private router: Router, private router: Router,
logService: LogService, logService: LogService,
private userNamePipe: UserNamePipe, private userNamePipe: UserNamePipe
) { ) {
super( super(eventService, i18nService, exportService, platformUtilsService, logService);
eventService,
i18nService,
exportService,
platformUtilsService,
logService,
);
} }
async ngOnInit() { async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => { this.route.parent.parent.params.subscribe(async (params) => {
this.providerId = params.providerId; this.providerId = params.providerId;
const provider = await this.providerService.get(this.providerId); const provider = await this.providerService.get(this.providerId);
if (provider == null || !provider.useEvents) { if (provider == null || !provider.useEvents) {
this.router.navigate(['/providers', this.providerId]); this.router.navigate(["/providers", this.providerId]);
return; return;
} }
await this.load(); await this.load();
@@ -65,7 +56,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit {
async load() { async load() {
const response = await this.apiService.getProviderUsers(this.providerId); const response = await this.apiService.getProviderUsers(this.providerId);
response.data.forEach(u => { response.data.forEach((u) => {
const name = this.userNamePipe.transform(u); const name = this.userNamePipe.transform(u);
this.providerUsersIdMap.set(u.id, { name: name, email: u.email }); this.providerUsersIdMap.set(u.id, { name: name, email: u.email });
this.providerUsersUserIdMap.set(u.userId, { name: name, email: u.email }); this.providerUsersUserIdMap.set(u.userId, { name: name, email: u.email });
@@ -75,10 +66,17 @@ export class EventsComponent extends BaseEventsComponent implements OnInit {
} }
protected requestEvents(startDate: string, endDate: string, continuationToken: string) { protected requestEvents(startDate: string, endDate: string, continuationToken: string) {
return this.apiService.getEventsProvider(this.providerId, startDate, endDate, continuationToken); return this.apiService.getEventsProvider(
this.providerId,
startDate,
endDate,
continuationToken
);
} }
protected getUserName(r: EventResponse, userId: string) { protected getUserName(r: EventResponse, userId: string) {
return userId != null && this.providerUsersUserIdMap.has(userId) ? this.providerUsersUserIdMap.get(userId) : null; return userId != null && this.providerUsersUserIdMap.has(userId)
? this.providerUsersUserIdMap.get(userId)
: null;
} }
} }

View File

@@ -2,15 +2,23 @@
<div class="row"> <div class="row">
<div class="col-3"> <div class="col-3">
<div class="card" *ngIf="provider"> <div class="card" *ngIf="provider">
<div class="card-header">{{'manage' | i18n}}</div> <div class="card-header">{{ "manage" | i18n }}</div>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<a routerLink="people" class="list-group-item" routerLinkActive="active" <a
*ngIf="provider.canManageUsers"> routerLink="people"
{{'people' | i18n}} class="list-group-item"
routerLinkActive="active"
*ngIf="provider.canManageUsers"
>
{{ "people" | i18n }}
</a> </a>
<a routerLink="events" class="list-group-item" routerLinkActive="active" <a
*ngIf="provider.canAccessEventLogs && accessEvents"> routerLink="events"
{{'eventLogs' | i18n}} class="list-group-item"
routerLinkActive="active"
*ngIf="provider.canAccessEventLogs && accessEvents"
>
{{ "eventLogs" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,25 +1,22 @@
import { import { Component, OnInit } from "@angular/core";
Component, import { ActivatedRoute } from "@angular/router";
OnInit,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ProviderService } from 'jslib-common/abstractions/provider.service'; import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Provider } from 'jslib-common/models/domain/provider'; import { Provider } from "jslib-common/models/domain/provider";
@Component({ @Component({
selector: 'provider-manage', selector: "provider-manage",
templateUrl: 'manage.component.html', templateUrl: "manage.component.html",
}) })
export class ManageComponent implements OnInit { export class ManageComponent implements OnInit {
provider: Provider; provider: Provider;
accessEvents = false; accessEvents = false;
constructor(private route: ActivatedRoute, private providerService: ProviderService) { } constructor(private route: ActivatedRoute, private providerService: ProviderService) {}
ngOnInit() { ngOnInit() {
this.route.parent.params.subscribe(async params => { this.route.parent.params.subscribe(async (params) => {
this.provider = await this.providerService.get(params.providerId); this.provider = await this.providerService.get(params.providerId);
this.accessEvents = this.provider.useEvents; this.accessEvents = this.provider.useEvents;
}); });

View File

@@ -1,137 +1,212 @@
<div class="page-header d-flex"> <div class="page-header d-flex">
<h1>{{'people' | i18n}}</h1> <h1>{{ "people" | i18n }}</h1>
<div class="ml-auto d-flex"> <div class="ml-auto d-flex">
<div class="btn-group btn-group-sm" role="group"> <div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-outline-secondary" [ngClass]="{active: status == null}" <button
(click)="filter(null)"> type="button"
{{'all' | i18n}} class="btn btn-outline-secondary"
<span class="badge badge-pill badge-info" *ngIf="allCount">{{allCount}}</span> [ngClass]="{ active: status == null }"
(click)="filter(null)"
>
{{ "all" | i18n }}
<span class="badge badge-pill badge-info" *ngIf="allCount">{{ allCount }}</span>
</button> </button>
<button type="button" class="btn btn-outline-secondary" <button
[ngClass]="{active: status == userStatusType.Invited}" type="button"
(click)="filter(userStatusType.Invited)"> class="btn btn-outline-secondary"
{{'invited' | i18n}} [ngClass]="{ active: status == userStatusType.Invited }"
<span class="badge badge-pill badge-info" *ngIf="invitedCount">{{invitedCount}}</span> (click)="filter(userStatusType.Invited)"
>
{{ "invited" | i18n }}
<span class="badge badge-pill badge-info" *ngIf="invitedCount">{{ invitedCount }}</span>
</button> </button>
<button type="button" class="btn btn-outline-secondary" <button
[ngClass]="{active: status == userStatusType.Accepted}" type="button"
(click)="filter(userStatusType.Accepted)"> class="btn btn-outline-secondary"
{{'accepted' | i18n}} [ngClass]="{ active: status == userStatusType.Accepted }"
<span class="badge badge-pill badge-warning" *ngIf="acceptedCount">{{acceptedCount}}</span> (click)="filter(userStatusType.Accepted)"
>
{{ "accepted" | i18n }}
<span class="badge badge-pill badge-warning" *ngIf="acceptedCount">{{
acceptedCount
}}</span>
</button> </button>
</div> </div>
<div class="ml-3"> <div class="ml-3">
<label class="sr-only" for="search">{{'search' | i18n}}</label> <label class="sr-only" for="search">{{ "search" | i18n }}</label>
<input type="search" class="form-control form-control-sm" id="search" placeholder="{{'search' | i18n}}" <input
[(ngModel)]="searchText"> type="search"
class="form-control form-control-sm"
id="search"
placeholder="{{ 'search' | i18n }}"
[(ngModel)]="searchText"
/>
</div> </div>
<div class="dropdown ml-3" appListDropdown> <div class="dropdown ml-3" appListDropdown>
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" id="bulkActionsButton" <button
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" appA11yTitle="{{'options' | i18n}}"> class="btn btn-sm btn-outline-secondary dropdown-toggle"
type="button"
id="bulkActionsButton"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
appA11yTitle="{{ 'options' | i18n }}"
>
<i class="fa fa-cog" aria-hidden="true"></i> <i class="fa fa-cog" aria-hidden="true"></i>
</button> </button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton"> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
<button class="dropdown-item" appStopClick (click)="bulkReinvite()"> <button class="dropdown-item" appStopClick (click)="bulkReinvite()">
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i> <i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i>
{{'reinviteSelected' | i18n}} {{ "reinviteSelected" | i18n }}
</button> </button>
<button class="dropdown-item text-success" appStopClick (click)="bulkConfirm()" <button
*ngIf="showBulkConfirmUsers"> class="dropdown-item text-success"
appStopClick
(click)="bulkConfirm()"
*ngIf="showBulkConfirmUsers"
>
<i class="fa fa-fw fa-check" aria-hidden="true"></i> <i class="fa fa-fw fa-check" aria-hidden="true"></i>
{{'confirmSelected' | i18n}} {{ "confirmSelected" | i18n }}
</button> </button>
<button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()"> <button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i> <i class="fa fa-fw fa-remove" aria-hidden="true"></i>
{{'remove' | i18n}} {{ "remove" | i18n }}
</button> </button>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<button class="dropdown-item" appStopClick (click)="selectAll(true)"> <button class="dropdown-item" appStopClick (click)="selectAll(true)">
<i class="fa fa-fw fa-check-square-o" aria-hidden="true"></i> <i class="fa fa-fw fa-check-square-o" aria-hidden="true"></i>
{{'selectAll' | i18n}} {{ "selectAll" | i18n }}
</button> </button>
<button class="dropdown-item" appStopClick (click)="selectAll(false)"> <button class="dropdown-item" appStopClick (click)="selectAll(false)">
<i class="fa fa-fw fa-minus-square-o" aria-hidden="true"></i> <i class="fa fa-fw fa-minus-square-o" aria-hidden="true"></i>
{{'unselectAll' | i18n}} {{ "unselectAll" | i18n }}
</button> </button>
</div> </div>
</div> </div>
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="invite()"> <button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="invite()">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i> <i class="fa fa-plus fa-fw" aria-hidden="true"></i>
{{'inviteUser' | i18n}} {{ "inviteUser" | i18n }}
</button> </button>
</div> </div>
</div> </div>
<ng-container *ngIf="loading"> <ng-container *ngIf="loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span> <span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container> </ng-container>
<ng-container <ng-container
*ngIf="!loading && (isPaging() ? pagedUsers : users | search:searchText:'name':'email':'id') as searchedUsers"> *ngIf="
<p *ngIf="!searchedUsers.length">{{'noUsersInList' | i18n}}</p> !loading &&
(isPaging() ? pagedUsers : (users | search: searchText:'name':'email':'id')) as searchedUsers
"
>
<p *ngIf="!searchedUsers.length">{{ "noUsersInList" | i18n }}</p>
<ng-container *ngIf="searchedUsers.length"> <ng-container *ngIf="searchedUsers.length">
<app-callout type="info" title="{{'confirmUsers' | i18n}}" icon="fa-check-circle" *ngIf="showConfirmUsers"> <app-callout
{{'providerUsersNeedConfirmed' | i18n}} type="info"
title="{{ 'confirmUsers' | i18n }}"
icon="fa-check-circle"
*ngIf="showConfirmUsers"
>
{{ "providerUsersNeedConfirmed" | i18n }}
</app-callout> </app-callout>
<table class="table table-hover table-list" infiniteScroll [infiniteScrollDistance]="1" <table
[infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()"> class="table table-hover table-list"
infiniteScroll
[infiniteScrollDistance]="1"
[infiniteScrollDisabled]="!isPaging()"
(scrolled)="loadMore()"
>
<tbody> <tbody>
<tr *ngFor="let u of searchedUsers"> <tr *ngFor="let u of searchedUsers">
<td (click)="checkUser(u)" class="table-list-checkbox"> <td (click)="checkUser(u)" class="table-list-checkbox">
<input type="checkbox" [(ngModel)]="u.checked" appStopProp> <input type="checkbox" [(ngModel)]="u.checked" appStopProp />
</td> </td>
<td width="30"> <td width="30">
<app-avatar [data]="u | userName" [email]="u.email" size="25" [circle]="true" <app-avatar
[fontSize]="14"></app-avatar> [data]="u | userName"
[email]="u.email"
size="25"
[circle]="true"
[fontSize]="14"
></app-avatar>
</td> </td>
<td> <td>
<a href="#" appStopClick (click)="edit(u)">{{u.email}}</a> <a href="#" appStopClick (click)="edit(u)">{{ u.email }}</a>
<span class="badge badge-secondary" <span class="badge badge-secondary" *ngIf="u.status === userStatusType.Invited">{{
*ngIf="u.status === userStatusType.Invited">{{'invited' | i18n}}</span> "invited" | i18n
<span class="badge badge-warning" }}</span>
*ngIf="u.status === userStatusType.Accepted">{{'accepted' | i18n}}</span> <span class="badge badge-warning" *ngIf="u.status === userStatusType.Accepted">{{
<small class="text-muted d-block" *ngIf="u.name">{{u.name}}</small> "accepted" | i18n
}}</span>
<small class="text-muted d-block" *ngIf="u.name">{{ u.name }}</small>
</td> </td>
<td> <td>
<ng-container *ngIf="u.twoFactorEnabled"> <ng-container *ngIf="u.twoFactorEnabled">
<i class="fa fa-lock" title="{{'userUsingTwoStep' | i18n}}" aria-hidden="true"></i> <i class="fa fa-lock" title="{{ 'userUsingTwoStep' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{'userUsingTwoStep' | i18n}}</span> <span class="sr-only">{{ "userUsingTwoStep" | i18n }}</span>
</ng-container> </ng-container>
</td> </td>
<td> <td>
<span *ngIf="u.type === userType.ProviderAdmin">{{'providerAdmin' | i18n}}</span> <span *ngIf="u.type === userType.ProviderAdmin">{{ "providerAdmin" | i18n }}</span>
<span *ngIf="u.type === userType.ServiceUser">{{'serviceUser' | i18n}}</span> <span *ngIf="u.type === userType.ServiceUser">{{ "serviceUser" | i18n }}</span>
<span *ngIf="u.type === userType.Custom">{{'custom' | i18n}}</span> <span *ngIf="u.type === userType.Custom">{{ "custom" | i18n }}</span>
</td> </td>
<td class="table-list-options"> <td class="table-list-options">
<div class="dropdown" appListDropdown> <div class="dropdown" appListDropdown>
<button class="btn btn-outline-secondary dropdown-toggle" type="button" <button
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-outline-secondary dropdown-toggle"
appA11yTitle="{{'options' | i18n}}"> type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
appA11yTitle="{{ 'options' | i18n }}"
>
<i class="fa fa-cog fa-lg" aria-hidden="true"></i> <i class="fa fa-cog fa-lg" aria-hidden="true"></i>
</button> </button>
<div class="dropdown-menu dropdown-menu-right"> <div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="#" appStopClick (click)="reinvite(u)" <a
*ngIf="u.status === userStatusType.Invited"> class="dropdown-item"
href="#"
appStopClick
(click)="reinvite(u)"
*ngIf="u.status === userStatusType.Invited"
>
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i> <i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i>
{{'resendInvitation' | i18n}} {{ "resendInvitation" | i18n }}
</a> </a>
<a class="dropdown-item text-success" href="#" appStopClick (click)="confirm(u)" <a
*ngIf="u.status === userStatusType.Accepted"> class="dropdown-item text-success"
href="#"
appStopClick
(click)="confirm(u)"
*ngIf="u.status === userStatusType.Accepted"
>
<i class="fa fa-fw fa-check" aria-hidden="true"></i> <i class="fa fa-fw fa-check" aria-hidden="true"></i>
{{'confirm' | i18n}} {{ "confirm" | i18n }}
</a> </a>
<a class="dropdown-item" href="#" appStopClick (click)="groups(u)" *ngIf="accessGroups"> <a
class="dropdown-item"
href="#"
appStopClick
(click)="groups(u)"
*ngIf="accessGroups"
>
<i class="fa fa-fw fa-sitemap" aria-hidden="true"></i> <i class="fa fa-fw fa-sitemap" aria-hidden="true"></i>
{{'groups' | i18n}} {{ "groups" | i18n }}
</a> </a>
<a class="dropdown-item" href="#" appStopClick (click)="events(u)" <a
*ngIf="accessEvents && u.status === userStatusType.Confirmed"> class="dropdown-item"
href="#"
appStopClick
(click)="events(u)"
*ngIf="accessEvents && u.status === userStatusType.Confirmed"
>
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i> <i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>
{{'eventLogs' | i18n}} {{ "eventLogs" | i18n }}
</a> </a>
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)"> <a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i> <i class="fa fa-fw fa-remove" aria-hidden="true"></i>
{{'remove' | i18n}} {{ "remove" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,57 +1,59 @@
import { import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
Component, import { ActivatedRoute, Router } from "@angular/router";
OnInit,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { first } from 'rxjs/operators'; import { first } from "rxjs/operators";
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";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.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 { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from "jslib-common/abstractions/search.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { ModalService } from 'jslib-angular/services/modal.service'; import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from 'jslib-angular/services/validation.service'; import { ValidationService } from "jslib-angular/services/validation.service";
import { ProviderUserStatusType } from 'jslib-common/enums/providerUserStatusType'; import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
import { ProviderUserType } from 'jslib-common/enums/providerUserType'; import { ProviderUserType } from "jslib-common/enums/providerUserType";
import { SearchPipe } from 'jslib-angular/pipes/search.pipe'; import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { UserNamePipe } from 'jslib-angular/pipes/user-name.pipe'; import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { ListResponse } from 'jslib-common/models/response/listResponse'; import { ListResponse } from "jslib-common/models/response/listResponse";
import { ProviderUserUserDetailsResponse } from 'jslib-common/models/response/provider/providerUserResponse'; import { ProviderUserUserDetailsResponse } from "jslib-common/models/response/provider/providerUserResponse";
import { ProviderUserBulkRequest } from 'jslib-common/models/request/provider/providerUserBulkRequest'; import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest";
import { ProviderUserConfirmRequest } from 'jslib-common/models/request/provider/providerUserConfirmRequest'; import { ProviderUserConfirmRequest } from "jslib-common/models/request/provider/providerUserConfirmRequest";
import { ProviderUserBulkResponse } from 'jslib-common/models/response/provider/providerUserBulkResponse'; import { ProviderUserBulkResponse } from "jslib-common/models/response/provider/providerUserBulkResponse";
import { BasePeopleComponent } from 'src/app/common/base.people.component'; import { BasePeopleComponent } from "src/app/common/base.people.component";
import { BulkStatusComponent } from 'src/app/organizations/manage/bulk/bulk-status.component'; import { BulkStatusComponent } from "src/app/organizations/manage/bulk/bulk-status.component";
import { EntityEventsComponent } from 'src/app/organizations/manage/entity-events.component'; import { EntityEventsComponent } from "src/app/organizations/manage/entity-events.component";
import { BulkConfirmComponent } from './bulk/bulk-confirm.component'; import { BulkConfirmComponent } from "./bulk/bulk-confirm.component";
import { BulkRemoveComponent } from './bulk/bulk-remove.component'; import { BulkRemoveComponent } from "./bulk/bulk-remove.component";
import { UserAddEditComponent } from './user-add-edit.component'; import { UserAddEditComponent } from "./user-add-edit.component";
@Component({ @Component({
selector: 'provider-people', selector: "provider-people",
templateUrl: 'people.component.html', templateUrl: "people.component.html",
}) })
export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetailsResponse> implements OnInit { export class PeopleComponent
extends BasePeopleComponent<ProviderUserUserDetailsResponse>
@ViewChild('addEdit', { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef; implements OnInit
@ViewChild('groupsTemplate', { read: ViewContainerRef, static: true }) groupsModalRef: ViewContainerRef; {
@ViewChild('eventsTemplate', { read: ViewContainerRef, static: true }) eventsModalRef: ViewContainerRef; @ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
@ViewChild('bulkStatusTemplate', { read: ViewContainerRef, static: true }) bulkStatusModalRef: ViewContainerRef; @ViewChild("groupsTemplate", { read: ViewContainerRef, static: true })
@ViewChild('bulkConfirmTemplate', { read: ViewContainerRef, static: true }) bulkConfirmModalRef: ViewContainerRef; groupsModalRef: ViewContainerRef;
@ViewChild('bulkRemoveTemplate', { read: ViewContainerRef, static: true }) bulkRemoveModalRef: ViewContainerRef; @ViewChild("eventsTemplate", { read: ViewContainerRef, static: true })
eventsModalRef: ViewContainerRef;
@ViewChild("bulkStatusTemplate", { read: ViewContainerRef, static: true })
bulkStatusModalRef: ViewContainerRef;
@ViewChild("bulkConfirmTemplate", { read: ViewContainerRef, static: true })
bulkConfirmModalRef: ViewContainerRef;
@ViewChild("bulkRemoveTemplate", { read: ViewContainerRef, static: true })
bulkRemoveModalRef: ViewContainerRef;
userType = ProviderUserType; userType = ProviderUserType;
userStatusType = ProviderUserStatusType; userStatusType = ProviderUserStatusType;
@@ -72,7 +74,7 @@ export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetails
searchPipe: SearchPipe, searchPipe: SearchPipe,
userNamePipe: UserNamePipe, userNamePipe: UserNamePipe,
stateService: StateService, stateService: StateService,
private providerService: ProviderService, private providerService: ProviderService
) { ) {
super( super(
apiService, apiService,
@@ -85,17 +87,17 @@ export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetails
logService, logService,
searchPipe, searchPipe,
userNamePipe, userNamePipe,
stateService, stateService
); );
} }
ngOnInit() { ngOnInit() {
this.route.parent.params.subscribe(async params => { this.route.parent.params.subscribe(async (params) => {
this.providerId = params.providerId; this.providerId = params.providerId;
const provider = await this.providerService.get(this.providerId); const provider = await this.providerService.get(this.providerId);
if (!provider.canManageUsers) { if (!provider.canManageUsers) {
this.router.navigate(['../'], { relativeTo: this.route }); this.router.navigate(["../"], { relativeTo: this.route });
return; return;
} }
@@ -103,10 +105,10 @@ export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetails
await this.load(); await this.load();
this.route.queryParams.pipe(first()).subscribe(async qParams => { this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
this.searchText = qParams.search; this.searchText = qParams.search;
if (qParams.viewEvents != null) { if (qParams.viewEvents != null) {
const user = this.users.filter(u => u.id === qParams.viewEvents); const user = this.users.filter((u) => u.id === qParams.viewEvents);
if (user.length > 0 && user[0].status === ProviderUserStatusType.Confirmed) { if (user.length > 0 && user[0].status === ProviderUserStatusType.Confirmed) {
this.events(user[0]); this.events(user[0]);
} }
@@ -136,7 +138,10 @@ export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetails
} }
async edit(user: ProviderUserUserDetailsResponse) { async edit(user: ProviderUserUserDetailsResponse) {
const [modal] = await this.modalService.openViewRef(UserAddEditComponent, this.addEditModalRef, comp => { const [modal] = await this.modalService.openViewRef(
UserAddEditComponent,
this.addEditModalRef,
(comp) => {
comp.name = this.userNamePipe.transform(user); comp.name = this.userNamePipe.transform(user);
comp.providerId = this.providerId; comp.providerId = this.providerId;
comp.providerUserId = user != null ? user.id : null; comp.providerUserId = user != null ? user.id : null;
@@ -148,17 +153,22 @@ export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetails
modal.close(); modal.close();
this.removeUser(user); this.removeUser(user);
}); });
}); }
);
} }
async events(user: ProviderUserUserDetailsResponse) { async events(user: ProviderUserUserDetailsResponse) {
const [modal] = await this.modalService.openViewRef(EntityEventsComponent, this.eventsModalRef, comp => { const [modal] = await this.modalService.openViewRef(
EntityEventsComponent,
this.eventsModalRef,
(comp) => {
comp.name = this.userNamePipe.transform(user); comp.name = this.userNamePipe.transform(user);
comp.providerId = this.providerId; comp.providerId = this.providerId;
comp.entityId = user.id; comp.entityId = user.id;
comp.showUser = false; comp.showUser = false;
comp.entity = 'user'; comp.entity = "user";
}); }
);
} }
async bulkRemove() { async bulkRemove() {
@@ -166,10 +176,14 @@ export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetails
return; return;
} }
const [modal] = await this.modalService.openViewRef(BulkRemoveComponent, this.bulkRemoveModalRef, comp => { const [modal] = await this.modalService.openViewRef(
BulkRemoveComponent,
this.bulkRemoveModalRef,
(comp) => {
comp.providerId = this.providerId; comp.providerId = this.providerId;
comp.users = this.getCheckedUsers(); comp.users = this.getCheckedUsers();
}); }
);
await modal.onClosedPromise(); await modal.onClosedPromise();
await this.load(); await this.load();
@@ -181,18 +195,26 @@ export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetails
} }
const users = this.getCheckedUsers(); const users = this.getCheckedUsers();
const filteredUsers = users.filter(u => u.status === ProviderUserStatusType.Invited); const filteredUsers = users.filter((u) => u.status === ProviderUserStatusType.Invited);
if (filteredUsers.length <= 0) { if (filteredUsers.length <= 0) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.platformUtilsService.showToast(
this.i18nService.t('noSelectedUsersApplicable')); "error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("noSelectedUsersApplicable")
);
return; return;
} }
try { try {
const request = new ProviderUserBulkRequest(filteredUsers.map(user => user.id)); const request = new ProviderUserBulkRequest(filteredUsers.map((user) => user.id));
const response = this.apiService.postManyProviderUserReinvite(this.providerId, request); const response = this.apiService.postManyProviderUserReinvite(this.providerId, request);
this.showBulkStatus(users, filteredUsers, response, this.i18nService.t('bulkReinviteMessage')); this.showBulkStatus(
users,
filteredUsers,
response,
this.i18nService.t("bulkReinviteMessage")
);
} catch (e) { } catch (e) {
this.validationService.showError(e); this.validationService.showError(e);
} }
@@ -204,21 +226,32 @@ export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetails
return; return;
} }
const [modal] = await this.modalService.openViewRef(BulkConfirmComponent, this.bulkConfirmModalRef, comp => { const [modal] = await this.modalService.openViewRef(
BulkConfirmComponent,
this.bulkConfirmModalRef,
(comp) => {
comp.providerId = this.providerId; comp.providerId = this.providerId;
comp.users = this.getCheckedUsers(); comp.users = this.getCheckedUsers();
}); }
);
await modal.onClosedPromise(); await modal.onClosedPromise();
await this.load(); await this.load();
} }
private async showBulkStatus(users: ProviderUserUserDetailsResponse[], filteredUsers: ProviderUserUserDetailsResponse[], private async showBulkStatus(
request: Promise<ListResponse<ProviderUserBulkResponse>>, successfullMessage: string) { users: ProviderUserUserDetailsResponse[],
filteredUsers: ProviderUserUserDetailsResponse[],
const [modal, childComponent] = await this.modalService.openViewRef(BulkStatusComponent, this.bulkStatusModalRef, comp => { request: Promise<ListResponse<ProviderUserBulkResponse>>,
successfullMessage: string
) {
const [modal, childComponent] = await this.modalService.openViewRef(
BulkStatusComponent,
this.bulkStatusModalRef,
(comp) => {
comp.loading = true; comp.loading = true;
}); }
);
// Workaround to handle closing the modal shortly after it has been opened // Workaround to handle closing the modal shortly after it has been opened
let close = false; let close = false;
@@ -232,13 +265,15 @@ export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetails
const response = await request; const response = await request;
if (modal) { if (modal) {
const keyedErrors: any = response.data.filter(r => r.error !== '').reduce((a, x) => ({ ...a, [x.id]: x.error }), {}); const keyedErrors: any = response.data
.filter((r) => r.error !== "")
.reduce((a, x) => ({ ...a, [x.id]: x.error }), {});
const keyedFilteredUsers: any = filteredUsers.reduce((a, x) => ({ ...a, [x.id]: x }), {}); const keyedFilteredUsers: any = filteredUsers.reduce((a, x) => ({ ...a, [x.id]: x }), {});
childComponent.users = users.map(user => { childComponent.users = users.map((user) => {
let message = keyedErrors[user.id] ?? successfullMessage; let message = keyedErrors[user.id] ?? successfullMessage;
if (!keyedFilteredUsers.hasOwnProperty(user.id)) { if (!keyedFilteredUsers.hasOwnProperty(user.id)) {
message = this.i18nService.t('bulkFilteredMessage'); message = this.i18nService.t("bulkFilteredMessage");
} }
return { return {

View File

@@ -1,68 +1,121 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="userAddEditTitle"> <div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="userAddEditTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document"> <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate> <form
class="modal-content"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="modal-header"> <div class="modal-header">
<h2 class="modal-title" id="userAddEditTitle"> <h2 class="modal-title" id="userAddEditTitle">
{{title}} {{ title }}
<small class="text-muted" *ngIf="name">{{name}}</small> <small class="text-muted" *ngIf="name">{{ name }}</small>
</h2> </h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}"> <button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body" *ngIf="loading"> <div class="modal-body" *ngIf="loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'loading' | i18n}}</span> class="fa fa-spinner fa-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</div> </div>
<div class="modal-body" *ngIf="!loading"> <div class="modal-body" *ngIf="!loading">
<ng-container *ngIf="!editMode"> <ng-container *ngIf="!editMode">
<p>{{'providerInviteUserDesc' | i18n}}</p> <p>{{ "providerInviteUserDesc" | i18n }}</p>
<div class="form-group mb-4"> <div class="form-group mb-4">
<label for="emails">{{'email' | i18n}}</label> <label for="emails">{{ "email" | i18n }}</label>
<input id="emails" class="form-control" type="text" name="Emails" [(ngModel)]="emails" required <input
appAutoFocus> id="emails"
<small class="text-muted">{{'inviteMultipleEmailDesc' | i18n : '20'}}</small> class="form-control"
type="text"
name="Emails"
[(ngModel)]="emails"
required
appAutoFocus
/>
<small class="text-muted">{{ "inviteMultipleEmailDesc" | i18n: "20" }}</small>
</div> </div>
</ng-container> </ng-container>
<h3> <h3>
{{'userType' | i18n}} {{ "userType" | i18n }}
<a target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}" <a
href="https://bitwarden.com/help/article/user-types-access-control/#user-types"> target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
href="https://bitwarden.com/help/article/user-types-access-control/#user-types"
>
<i class="fa fa-question-circle-o" aria-hidden="true"></i> <i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a> </a>
</h3> </h3>
<div class="form-check mt-2 form-check-block"> <div class="form-check mt-2 form-check-block">
<input class="form-check-input" type="radio" name="userType" id="userTypeServiceUser" <input
[value]="userType.ServiceUser" [(ngModel)]="type"> class="form-check-input"
type="radio"
name="userType"
id="userTypeServiceUser"
[value]="userType.ServiceUser"
[(ngModel)]="type"
/>
<label class="form-check-label" for="userTypeServiceUser"> <label class="form-check-label" for="userTypeServiceUser">
{{'serviceUser' | i18n}} {{ "serviceUser" | i18n }}
<small>{{'serviceUserDesc' | i18n}}</small> <small>{{ "serviceUserDesc" | i18n }}</small>
</label> </label>
</div> </div>
<div class="form-check mt-2 form-check-block"> <div class="form-check mt-2 form-check-block">
<input class="form-check-input" type="radio" name="userType" id="userTypeProviderAdmin" <input
[value]="userType.ProviderAdmin" [(ngModel)]="type"> class="form-check-input"
type="radio"
name="userType"
id="userTypeProviderAdmin"
[value]="userType.ProviderAdmin"
[(ngModel)]="type"
/>
<label class="form-check-label" for="userTypeProviderAdmin"> <label class="form-check-label" for="userTypeProviderAdmin">
{{'providerAdmin' | i18n}} {{ "providerAdmin" | i18n }}
<small>{{'providerAdminDesc' | i18n}}</small> <small>{{ "providerAdminDesc" | i18n }}</small>
</label> </label>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"> <button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{'save' | i18n}}</span> <span>{{ "save" | i18n }}</span>
</button> </button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal"> <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{'cancel' | i18n}} {{ "cancel" | i18n }}
</button> </button>
<div class="ml-auto"> <div class="ml-auto">
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger" <button
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading" #deleteBtn
[appApiAction]="deletePromise"> type="button"
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i> (click)="delete()"
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" class="btn btn-outline-danger"
title="{{'loading' | i18n}}" aria-hidden="true"></i> appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
[disabled]="deleteBtn.loading"
[appApiAction]="deletePromise"
>
<i
class="fa fa-trash-o fa-lg fa-fw"
[hidden]="deleteBtn.loading"
aria-hidden="true"
></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!deleteBtn.loading"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,26 +1,20 @@
import { import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderUserInviteRequest } from 'jslib-common/models/request/provider/providerUserInviteRequest'; import { ProviderUserInviteRequest } from "jslib-common/models/request/provider/providerUserInviteRequest";
import { PermissionsApi } from 'jslib-common/models/api/permissionsApi'; import { PermissionsApi } from "jslib-common/models/api/permissionsApi";
import { ProviderUserType } from 'jslib-common/enums/providerUserType'; import { ProviderUserType } from "jslib-common/enums/providerUserType";
import { ProviderUserUpdateRequest } from 'jslib-common/models/request/provider/providerUserUpdateRequest'; import { ProviderUserUpdateRequest } from "jslib-common/models/request/provider/providerUserUpdateRequest";
@Component({ @Component({
selector: 'provider-user-add-edit', selector: "provider-user-add-edit",
templateUrl: 'user-add-edit.component.html', templateUrl: "user-add-edit.component.html",
}) })
export class UserAddEditComponent implements OnInit { export class UserAddEditComponent implements OnInit {
@Input() name: string; @Input() name: string;
@@ -36,20 +30,24 @@ export class UserAddEditComponent implements OnInit {
type: ProviderUserType = ProviderUserType.ServiceUser; type: ProviderUserType = ProviderUserType.ServiceUser;
permissions = new PermissionsApi(); permissions = new PermissionsApi();
showCustom = false; showCustom = false;
access: 'all' | 'selected' = 'selected'; access: "all" | "selected" = "selected";
formPromise: Promise<any>; formPromise: Promise<any>;
deletePromise: Promise<any>; deletePromise: Promise<any>;
userType = ProviderUserType; userType = ProviderUserType;
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(
private platformUtilsService: PlatformUtilsService, private logService: LogService) { } private apiService: ApiService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
async ngOnInit() { async ngOnInit() {
this.editMode = this.loading = this.providerUserId != null; this.editMode = this.loading = this.providerUserId != null;
if (this.editMode) { if (this.editMode) {
this.editMode = true; this.editMode = true;
this.title = this.i18nService.t('editUser'); this.title = this.i18nService.t("editUser");
try { try {
const user = await this.apiService.getProviderUser(this.providerId, this.providerUserId); const user = await this.apiService.getProviderUser(this.providerId, this.providerUserId);
this.type = user.type; this.type = user.type;
@@ -57,7 +55,7 @@ export class UserAddEditComponent implements OnInit {
this.logService.error(e); this.logService.error(e);
} }
} else { } else {
this.title = this.i18nService.t('inviteUser'); this.title = this.i18nService.t("inviteUser");
} }
this.loading = false; this.loading = false;
@@ -68,7 +66,11 @@ export class UserAddEditComponent implements OnInit {
if (this.editMode) { if (this.editMode) {
const request = new ProviderUserUpdateRequest(); const request = new ProviderUserUpdateRequest();
request.type = this.type; request.type = this.type;
this.formPromise = this.apiService.putProviderUser(this.providerId, this.providerUserId, request); this.formPromise = this.apiService.putProviderUser(
this.providerId,
this.providerUserId,
request
);
} else { } else {
const request = new ProviderUserInviteRequest(); const request = new ProviderUserInviteRequest();
request.emails = this.emails.trim().split(/\s*,\s*/); request.emails = this.emails.trim().split(/\s*,\s*/);
@@ -76,8 +78,11 @@ export class UserAddEditComponent implements OnInit {
this.formPromise = this.apiService.postProviderUserInvite(this.providerId, request); this.formPromise = this.apiService.postProviderUserInvite(this.providerId, request);
} }
await this.formPromise; await this.formPromise;
this.platformUtilsService.showToast('success', null, this.platformUtilsService.showToast(
this.i18nService.t(this.editMode ? 'editedUserId' : 'invitedUsers', this.name)); "success",
null,
this.i18nService.t(this.editMode ? "editedUserId" : "invitedUsers", this.name)
);
this.onSavedUser.emit(); this.onSavedUser.emit();
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
@@ -90,8 +95,12 @@ export class UserAddEditComponent implements OnInit {
} }
const confirmed = await this.platformUtilsService.showDialog( const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('removeUserConfirmation'), this.name, this.i18nService.t("removeUserConfirmation"),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); this.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) { if (!confirmed) {
return false; return false;
} }
@@ -99,11 +108,14 @@ export class UserAddEditComponent implements OnInit {
try { try {
this.deletePromise = this.apiService.deleteProviderUser(this.providerId, this.providerUserId); this.deletePromise = this.apiService.deleteProviderUser(this.providerId, this.providerUserId);
await this.deletePromise; await this.deletePromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedUserId', this.name)); this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("removedUserId", this.name)
);
this.onDeletedUser.emit(); this.onDeletedUser.emit();
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
} }
} }

View File

@@ -5,13 +5,13 @@
<div class="my-auto d-flex align-items-center pl-1"> <div class="my-auto d-flex align-items-center pl-1">
<app-avatar [data]="provider.name" size="45" [circle]="true"></app-avatar> <app-avatar [data]="provider.name" size="45" [circle]="true"></app-avatar>
<div class="org-name ml-3"> <div class="org-name ml-3">
<span>{{provider.name}}</span> <span>{{ provider.name }}</span>
<small class="text-muted">{{'provider' | i18n}}</small> <small class="text-muted">{{ "provider" | i18n }}</small>
</div> </div>
<div class="ml-3 card border-danger text-danger bg-transparent" *ngIf="!provider.enabled"> <div class="ml-3 card border-danger text-danger bg-transparent" *ngIf="!provider.enabled">
<div class="card-body py-2"> <div class="card-body py-2">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
{{'providerIsDisabled' | i18n}} {{ "providerIsDisabled" | i18n }}
</div> </div>
</div> </div>
</div> </div>
@@ -19,19 +19,19 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" routerLink="clients" routerLinkActive="active"> <a class="nav-link" routerLink="clients" routerLinkActive="active">
<i class="fa fa-university" aria-hidden="true"></i> <i class="fa fa-university" aria-hidden="true"></i>
{{'clients' | i18n}} {{ "clients" | i18n }}
</a> </a>
</li> </li>
<li class="nav-item" *ngIf="showManageTab"> <li class="nav-item" *ngIf="showManageTab">
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active"> <a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
<i class="fa fa-sliders" aria-hidden="true"></i> <i class="fa fa-sliders" aria-hidden="true"></i>
{{'manage' | i18n}} {{ "manage" | i18n }}
</a> </a>
</li> </li>
<li class="nav-item" *ngIf="showSettingsTab"> <li class="nav-item" *ngIf="showSettingsTab">
<a class="nav-link" routerLink="settings" routerLinkActive="active"> <a class="nav-link" routerLink="settings" routerLinkActive="active">
<i class="fa fa-cogs" aria-hidden="true"></i> <i class="fa fa-cogs" aria-hidden="true"></i>
{{'settings' | i18n}} {{ "settings" | i18n }}
</a> </a>
</li> </li>
</ul> </ul>

View File

@@ -1,24 +1,23 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from "@angular/router";
import { ProviderService } from 'jslib-common/abstractions/provider.service'; import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Provider } from 'jslib-common/models/domain/provider'; import { Provider } from "jslib-common/models/domain/provider";
@Component({ @Component({
selector: 'providers-layout', selector: "providers-layout",
templateUrl: 'providers-layout.component.html', templateUrl: "providers-layout.component.html",
}) })
export class ProvidersLayoutComponent { export class ProvidersLayoutComponent {
provider: Provider; provider: Provider;
private providerId: string; private providerId: string;
constructor(private route: ActivatedRoute, private providerService: ProviderService) { } constructor(private route: ActivatedRoute, private providerService: ProviderService) {}
ngOnInit() { ngOnInit() {
document.body.classList.remove('layout_frontend'); document.body.classList.remove("layout_frontend");
this.route.params.subscribe(async params => { this.route.params.subscribe(async (params) => {
this.providerId = params.providerId; this.providerId = params.providerId;
await this.load(); await this.load();
}); });
@@ -43,9 +42,9 @@ export class ProvidersLayoutComponent {
get manageRoute(): string { get manageRoute(): string {
switch (true) { switch (true) {
case this.provider.canManageUsers: case this.provider.canManageUsers:
return 'manage/people'; return "manage/people";
case this.provider.canAccessEventLogs: case this.provider.canAccessEventLogs:
return 'manage/events'; return "manage/events";
} }
} }
} }

View File

@@ -1,110 +1,110 @@
import { NgModule } from '@angular/core'; import { NgModule } from "@angular/core";
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service'; import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { Permissions } from 'jslib-common/enums/permissions'; import { Permissions } from "jslib-common/enums/permissions";
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 { 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 { 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";
import { SetupComponent } from './setup/setup.component'; import { SetupComponent } from "./setup/setup.component";
import { FrontendLayoutComponent } from 'src/app/layouts/frontend-layout.component'; import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component";
import { ProvidersComponent } from 'src/app/providers/providers.component'; import { ProvidersComponent } from "src/app/providers/providers.component";
import { ProviderGuardService } from './services/provider-guard.service'; import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from './services/provider-type-guard.service'; import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { AccountComponent } from './settings/account.component'; import { AccountComponent } from "./settings/account.component";
const routes: Routes = [ const routes: Routes = [
{ {
path: '', path: "",
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
component: ProvidersComponent, component: ProvidersComponent,
}, },
{ {
path: '', path: "",
component: FrontendLayoutComponent, component: FrontendLayoutComponent,
children: [ children: [
{ {
path: 'setup-provider', path: "setup-provider",
component: SetupProviderComponent, component: SetupProviderComponent,
data: { titleId: 'setupProvider' }, data: { titleId: "setupProvider" },
}, },
{ {
path: 'accept-provider', path: "accept-provider",
component: AcceptProviderComponent, component: AcceptProviderComponent,
data: { titleId: 'acceptProvider' }, data: { titleId: "acceptProvider" },
}, },
], ],
}, },
{ {
path: '', path: "",
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
children: [ children: [
{ {
path: 'setup', path: "setup",
component: SetupComponent, component: SetupComponent,
}, },
{ {
path: ':providerId', path: ":providerId",
component: ProvidersLayoutComponent, component: ProvidersLayoutComponent,
canActivate: [ProviderGuardService], canActivate: [ProviderGuardService],
children: [ children: [
{ path: '', pathMatch: 'full', redirectTo: 'clients' }, { path: "", pathMatch: "full", redirectTo: "clients" },
{ path: 'clients/create', component: CreateOrganizationComponent }, { path: "clients/create", component: CreateOrganizationComponent },
{ path: 'clients', component: ClientsComponent, data: { titleId: 'clients' } }, { path: "clients", component: ClientsComponent, data: { titleId: "clients" } },
{ {
path: 'manage', path: "manage",
component: ManageComponent, component: ManageComponent,
children: [ children: [
{ {
path: '', path: "",
pathMatch: 'full', pathMatch: "full",
redirectTo: 'people', redirectTo: "people",
}, },
{ {
path: 'people', path: "people",
component: PeopleComponent, component: PeopleComponent,
canActivate: [ProviderTypeGuardService], canActivate: [ProviderTypeGuardService],
data: { data: {
titleId: 'people', titleId: "people",
permissions: [Permissions.ManageUsers], permissions: [Permissions.ManageUsers],
}, },
}, },
{ {
path: 'events', path: "events",
component: EventsComponent, component: EventsComponent,
canActivate: [ProviderTypeGuardService], canActivate: [ProviderTypeGuardService],
data: { data: {
titleId: 'eventLogs', titleId: "eventLogs",
permissions: [Permissions.AccessEventLogs], permissions: [Permissions.AccessEventLogs],
}, },
}, },
], ],
}, },
{ {
path: 'settings', path: "settings",
component: SettingsComponent, component: SettingsComponent,
children: [ children: [
{ {
path: '', path: "",
pathMatch: 'full', pathMatch: "full",
redirectTo: 'account', redirectTo: "account",
}, },
{ {
path: 'account', path: "account",
component: AccountComponent, component: AccountComponent,
canActivate: [ProviderTypeGuardService], canActivate: [ProviderTypeGuardService],
data: { data: {
titleId: 'myProvider', titleId: "myProvider",
permissions: [Permissions.ManageProvider], permissions: [Permissions.ManageProvider],
}, },
}, },
@@ -120,4 +120,4 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], exports: [RouterModule],
}) })
export class ProvidersRoutingModule { } export class ProvidersRoutingModule {}

View File

@@ -1,44 +1,39 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from "@angular/common";
import { ComponentFactoryResolver } from '@angular/core'; import { ComponentFactoryResolver } from "@angular/core";
import { NgModule } from '@angular/core'; import { NgModule } from "@angular/core";
import { FormsModule } from '@angular/forms'; import { FormsModule } from "@angular/forms";
import { ModalService } from 'jslib-angular/services/modal.service'; import { ModalService } from "jslib-angular/services/modal.service";
import { ProviderGuardService } from './services/provider-guard.service'; import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from './services/provider-type-guard.service'; import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { WebProviderService } from './services/webProvider.service'; import { WebProviderService } from "./services/webProvider.service";
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 { 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 { 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";
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 { UserAddEditComponent } from './manage/user-add-edit.component'; import { UserAddEditComponent } from "./manage/user-add-edit.component";
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";
import { SetupComponent } from './setup/setup.component'; import { SetupComponent } from "./setup/setup.component";
import { OssModule } from 'src/app/oss.module'; import { OssModule } from "src/app/oss.module";
@NgModule({ @NgModule({
imports: [ imports: [CommonModule, FormsModule, OssModule, ProvidersRoutingModule],
CommonModule,
FormsModule,
OssModule,
ProvidersRoutingModule,
],
declarations: [ declarations: [
AcceptProviderComponent, AcceptProviderComponent,
AccountComponent, AccountComponent,
@@ -56,14 +51,13 @@ import { OssModule } from 'src/app/oss.module';
SetupProviderComponent, SetupProviderComponent,
UserAddEditComponent, UserAddEditComponent,
], ],
providers: [ providers: [WebProviderService, ProviderGuardService, ProviderTypeGuardService],
WebProviderService,
ProviderGuardService,
ProviderTypeGuardService,
],
}) })
export class ProvidersModule { export class ProvidersModule {
constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) { constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) {
modalService.registerComponentFactoryResolver(AddOrganizationComponent, componentFactoryResolver); modalService.registerComponentFactoryResolver(
AddOrganizationComponent,
componentFactoryResolver
);
} }
} }

View File

@@ -1,13 +1,9 @@
import { Injectable } from '@angular/core'; import { Injectable } from "@angular/core";
import { import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
ActivatedRouteSnapshot,
CanActivate,
Router,
} from '@angular/router';
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 { ProviderService } from 'jslib-common/abstractions/provider.service'; import { ProviderService } from "jslib-common/abstractions/provider.service";
@Injectable() @Injectable()
export class ProviderGuardService implements CanActivate { export class ProviderGuardService implements CanActivate {
@@ -15,18 +11,18 @@ export class ProviderGuardService implements CanActivate {
private router: Router, private router: Router,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService, private i18nService: I18nService,
private providerService: ProviderService, private providerService: ProviderService
) { } ) {}
async canActivate(route: ActivatedRouteSnapshot) { async canActivate(route: ActivatedRouteSnapshot) {
const provider = await this.providerService.get(route.params.providerId); const provider = await this.providerService.get(route.params.providerId);
if (provider == null) { if (provider == null) {
this.router.navigate(['/']); this.router.navigate(["/"]);
return false; return false;
} }
if (!provider.isProviderAdmin && !provider.enabled) { if (!provider.isProviderAdmin && !provider.enabled) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('providerIsDisabled')); this.platformUtilsService.showToast("error", null, this.i18nService.t("providerIsDisabled"));
this.router.navigate(['/']); this.router.navigate(["/"]);
return false; return false;
} }

View File

@@ -1,21 +1,17 @@
import { Injectable } from '@angular/core'; import { Injectable } from "@angular/core";
import { import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
ActivatedRouteSnapshot,
CanActivate,
Router,
} from '@angular/router';
import { ProviderService } from 'jslib-common/abstractions/provider.service'; import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Permissions } from 'jslib-common/enums/permissions'; import { Permissions } from "jslib-common/enums/permissions";
@Injectable() @Injectable()
export class ProviderTypeGuardService implements CanActivate { export class 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) {
const provider = await this.providerService.get(route.params.providerId); const provider = await this.providerService.get(route.params.providerId);
const permissions = route.data == null ? null : route.data.permissions as Permissions[]; const permissions = route.data == null ? null : (route.data.permissions as Permissions[]);
if ( if (
(permissions.indexOf(Permissions.AccessEventLogs) !== -1 && provider.canAccessEventLogs) || (permissions.indexOf(Permissions.AccessEventLogs) !== -1 && provider.canAccessEventLogs) ||
@@ -25,7 +21,7 @@ export class ProviderTypeGuardService implements CanActivate {
return true; return true;
} }
this.router.navigate(['/providers', provider.id]); this.router.navigate(["/providers", provider.id]);
return false; return false;
} }
} }

View File

@@ -1,14 +1,18 @@
import { Injectable } from '@angular/core'; import { Injectable } 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";
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from "jslib-common/abstractions/sync.service";
import { ProviderAddOrganizationRequest } from 'jslib-common/models/request/provider/providerAddOrganizationRequest'; import { ProviderAddOrganizationRequest } from "jslib-common/models/request/provider/providerAddOrganizationRequest";
@Injectable() @Injectable()
export class WebProviderService { export class WebProviderService {
constructor(private cryptoService: CryptoService, private syncService: SyncService, private apiService: ApiService) {} constructor(
private cryptoService: CryptoService,
private syncService: SyncService,
private apiService: ApiService
) {}
async addOrganizationToProvider(providerId: string, organizationId: string) { async addOrganizationToProvider(providerId: string, organizationId: string) {
const orgKey = await this.cryptoService.getOrgKey(organizationId); const orgKey = await this.cryptoService.getOrgKey(organizationId);

View File

@@ -1,30 +1,48 @@
<div class="page-header"> <div class="page-header">
<h1>{{'myProvider' | i18n}}</h1> <h1>{{ "myProvider" | i18n }}</h1>
</div> </div>
<div *ngIf="loading"> <div *ngIf="loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin text-muted" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span> <span class="sr-only">{{ "loading" | i18n }}</span>
</div> </div>
<form *ngIf="provider && !loading" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate> <form
*ngIf="provider && !loading"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<div class="form-group"> <div class="form-group">
<label for="name">{{'providerName' | i18n}}</label> <label for="name">{{ "providerName" | i18n }}</label>
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="provider.name" <input
[disabled]="selfHosted"> id="name"
class="form-control"
type="text"
name="Name"
[(ngModel)]="provider.name"
[disabled]="selfHosted"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="billingEmail">{{'billingEmail' | i18n}}</label> <label for="billingEmail">{{ "billingEmail" | i18n }}</label>
<input id="billingEmail" class="form-control" type="text" name="BillingEmail" <input
[(ngModel)]="provider.billingEmail" [disabled]="selfHosted"> id="billingEmail"
class="form-control"
type="text"
name="BillingEmail"
[(ngModel)]="provider.billingEmail"
[disabled]="selfHosted"
/>
</div> </div>
</div> </div>
<div class="col-6"> <div class="col-6">
<app-avatar data="{{provider.name}}" dynamic="true" size="75" fontSize="35"></app-avatar> <app-avatar data="{{ provider.name }}" dynamic="true" size="75" fontSize="35"></app-avatar>
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"> <button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{'save' | i18n}}</span> <span>{{ "save" | i18n }}</span>
</button> </button>
</form> </form>

View File

@@ -1,19 +1,19 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from "jslib-common/abstractions/sync.service";
import { ProviderUpdateRequest } from 'jslib-common/models/request/provider/providerUpdateRequest'; import { ProviderUpdateRequest } from "jslib-common/models/request/provider/providerUpdateRequest";
import { ProviderResponse } from 'jslib-common/models/response/provider/providerResponse'; import { ProviderResponse } from "jslib-common/models/response/provider/providerResponse";
@Component({ @Component({
selector: 'provider-account', selector: "provider-account",
templateUrl: 'account.component.html', templateUrl: "account.component.html",
}) })
export class AccountComponent { export class AccountComponent {
selfHosted = false; selfHosted = false;
@@ -24,13 +24,18 @@ export class AccountComponent {
private providerId: string; private providerId: string;
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(
private route: ActivatedRoute, private syncService: SyncService, private apiService: ApiService,
private platformUtilsService: PlatformUtilsService, private logService: LogService) { } private i18nService: I18nService,
private route: ActivatedRoute,
private syncService: SyncService,
private platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
async ngOnInit() { async ngOnInit() {
this.selfHosted = this.platformUtilsService.isSelfHost(); this.selfHosted = this.platformUtilsService.isSelfHost();
this.route.parent.parent.params.subscribe(async params => { this.route.parent.parent.params.subscribe(async (params) => {
this.providerId = params.providerId; this.providerId = params.providerId;
try { try {
this.provider = await this.apiService.getProvider(this.providerId); this.provider = await this.apiService.getProvider(this.providerId);
@@ -52,7 +57,7 @@ export class AccountComponent {
return this.syncService.fullSync(true); return this.syncService.fullSync(true);
}); });
await this.formPromise; await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('providerUpdated')); this.platformUtilsService.showToast("success", null, this.i18nService.t("providerUpdated"));
} catch (e) { } catch (e) {
this.logService.error(`Handled exception: ${e}`); this.logService.error(`Handled exception: ${e}`);
} }

View File

@@ -2,10 +2,10 @@
<div class="row"> <div class="row">
<div class="col-3"> <div class="col-3">
<div class="card"> <div class="card">
<div class="card-header">{{'settings' | i18n}}</div> <div class="card-header">{{ "settings" | i18n }}</div>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<a routerLink="account" class="list-group-item" routerLinkActive="active"> <a routerLink="account" class="list-group-item" routerLinkActive="active">
{{'myProvider' | i18n}} {{ "myProvider" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,19 +1,22 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from "@angular/router";
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";
@Component({ @Component({
selector: 'provider-settings', selector: "provider-settings",
templateUrl: 'settings.component.html', templateUrl: "settings.component.html",
}) })
export class SettingsComponent { export class SettingsComponent {
constructor(private route: ActivatedRoute, private providerService: ProviderService, constructor(
private platformUtilsService: PlatformUtilsService) { } private route: ActivatedRoute,
private providerService: ProviderService,
private platformUtilsService: PlatformUtilsService
) {}
ngOnInit() { ngOnInit() {
this.route.parent.params.subscribe(async params => { this.route.parent.params.subscribe(async (params) => {
const provider = await this.providerService.get(params.providerId); const provider = await this.providerService.get(params.providerId);
}); });
} }

View File

@@ -1,23 +1,27 @@
<div class="mt-5 d-flex justify-content-center" *ngIf="loading"> <div class="mt-5 d-flex justify-content-center" *ngIf="loading">
<div> <div>
<img class="mb-4 logo logo-themed" alt="Bitwarden"> <img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center"> <p class="text-center">
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'loading' | i18n}}</span> class="fa fa-spinner fa-spin fa-2x text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p> </p>
</div> </div>
</div> </div>
<div class="container" *ngIf="!loading && !authed"> <div class="container" *ngIf="!loading && !authed">
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5"> <div class="col-5">
<p class="lead text-center mb-4">{{'setupProvider' | i18n}}</p> <p class="lead text-center mb-4">{{ "setupProvider" | i18n }}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">
<p>{{'setupProviderLoginDesc' | i18n}}</p> <p>{{ "setupProviderLoginDesc" | i18n }}</p>
<hr> <hr />
<div class="d-flex"> <div class="d-flex">
<a routerLink="/" [queryParams]="{email: email}" class="btn btn-primary btn-block"> <a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
{{'logIn' | i18n}} {{ "logIn" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,20 +1,19 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { BaseAcceptComponent } from 'src/app/common/base.accept.component'; import { BaseAcceptComponent } from "src/app/common/base.accept.component";
@Component({ @Component({
selector: 'app-setup-provider', selector: "app-setup-provider",
templateUrl: 'setup-provider.component.html', templateUrl: "setup-provider.component.html",
}) })
export class SetupProviderComponent extends BaseAcceptComponent { export class SetupProviderComponent extends BaseAcceptComponent {
failedShortMessage = "inviteAcceptFailedShort";
failedMessage = "inviteAcceptFailed";
failedShortMessage = 'inviteAcceptFailedShort'; requiredParameters = ["providerId", "email", "token"];
failedMessage = 'inviteAcceptFailed';
requiredParameters = ['providerId', 'email', 'token'];
async authedHandler(qParams: any) { async authedHandler(qParams: any) {
this.router.navigate(['/providers/setup'], {queryParams: qParams}); this.router.navigate(["/providers/setup"], { queryParams: qParams });
} }
// tslint:disable-next-line // tslint:disable-next-line

View File

@@ -1,30 +1,37 @@
<app-navbar></app-navbar> <app-navbar></app-navbar>
<div class="container page-content"> <div class="container page-content">
<div class="page-header"> <div class="page-header">
<h1>{{'setupProvider' | i18n}}</h1> <h1>{{ "setupProvider" | i18n }}</h1>
</div> </div>
<p>{{'setupProviderDesc' | i18n}}</p> <p>{{ "setupProviderDesc" | i18n }}</p>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="loading"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="loading">
<h2 class="mt-5">{{'generalInformation' | i18n}}</h2> <h2 class="mt-5">{{ "generalInformation" | i18n }}</h2>
<div class="row"> <div class="row">
<div class="form-group col-6"> <div class="form-group col-6">
<label for="name">{{'providerName' | i18n}}</label> <label for="name">{{ "providerName" | i18n }}</label>
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name" required> <input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name" required />
</div> </div>
<div class="form-group col-6"> <div class="form-group col-6">
<label for="billingEmail">{{'billingEmail' | i18n}}</label> <label for="billingEmail">{{ "billingEmail" | i18n }}</label>
<input id="billingEmail" class="form-control" type="text" name="BillingEmail" [(ngModel)]="billingEmail" required> <input
id="billingEmail"
class="form-control"
type="text"
name="BillingEmail"
[(ngModel)]="billingEmail"
required
/>
</div> </div>
</div> </div>
<div class="mt-4"> <div class="mt-4">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"> <button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{'submit' | i18n}}</span> <span>{{ "submit" | i18n }}</span>
</button> </button>
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" *ngIf="showCancel"> <button type="button" class="btn btn-outline-secondary" (click)="cancel()" *ngIf="showCancel">
{{'cancel' | i18n}} {{ "cancel" | i18n }}
</button> </button>
</div> </div>
</form> </form>

View File

@@ -1,26 +1,20 @@
import { import { Component, OnInit } from "@angular/core";
Component, import { ActivatedRoute, Router } from "@angular/router";
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { first } from 'rxjs/operators'; import { first } from "rxjs/operators";
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";
import { ValidationService } from 'jslib-angular/services/validation.service'; import { ValidationService } from "jslib-angular/services/validation.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 { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from "jslib-common/abstractions/sync.service";
import { ProviderSetupRequest } from 'jslib-common/models/request/provider/providerSetupRequest'; import { ProviderSetupRequest } from "jslib-common/models/request/provider/providerSetupRequest";
@Component({ @Component({
selector: 'provider-setup', selector: "provider-setup",
templateUrl: 'setup.component.html', templateUrl: "setup.component.html",
}) })
export class SetupComponent implements OnInit { export class SetupComponent implements OnInit {
loading = true; loading = true;
@@ -33,20 +27,32 @@ export class SetupComponent implements OnInit {
name: string; name: string;
billingEmail: string; billingEmail: string;
constructor(private router: Router, private platformUtilsService: PlatformUtilsService, constructor(
private i18nService: I18nService, private route: ActivatedRoute, private router: Router,
private cryptoService: CryptoService, private apiService: ApiService, private platformUtilsService: PlatformUtilsService,
private syncService: SyncService, private validationService: ValidationService) { } private i18nService: I18nService,
private route: ActivatedRoute,
private cryptoService: CryptoService,
private apiService: ApiService,
private syncService: SyncService,
private validationService: ValidationService
) {}
ngOnInit() { ngOnInit() {
document.body.classList.remove('layout_frontend'); document.body.classList.remove("layout_frontend");
this.route.queryParams.pipe(first()).subscribe(async qParams => { this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
const error = qParams.providerId == null || qParams.email == null || qParams.token == null; const error = qParams.providerId == null || qParams.email == null || qParams.token == null;
if (error) { if (error) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('emergencyInviteAcceptFailed'), this.platformUtilsService.showToast(
{ timeout: 10000 }); "error",
this.router.navigate(['/']); null,
this.i18nService.t("emergencyInviteAcceptFailed"),
{
timeout: 10000,
}
);
this.router.navigate(["/"]);
return; return;
} }
@@ -57,11 +63,11 @@ export class SetupComponent implements OnInit {
try { try {
const provider = await this.apiService.getProvider(this.providerId); const provider = await this.apiService.getProvider(this.providerId);
if (provider.name != null) { if (provider.name != null) {
this.router.navigate(['/providers', provider.id], { replaceUrl: true }); this.router.navigate(["/providers", provider.id], { replaceUrl: true });
} }
} catch (e) { } catch (e) {
this.validationService.showError(e); this.validationService.showError(e);
this.router.navigate(['/']); this.router.navigate(["/"]);
} }
}); });
} }
@@ -84,10 +90,10 @@ export class SetupComponent implements OnInit {
request.key = key; request.key = key;
const provider = await this.apiService.postProviderSetup(this.providerId, request); const provider = await this.apiService.postProviderSetup(this.providerId, request);
this.platformUtilsService.showToast('success', null, this.i18nService.t('providerSetup')); this.platformUtilsService.showToast("success", null, this.i18nService.t("providerSetup"));
await this.syncService.fullSync(true); await this.syncService.fullSync(true);
this.router.navigate(['/providers', provider.id]); this.router.navigate(["/providers", provider.id]);
} catch (e) { } catch (e) {
this.validationService.showError(e); this.validationService.showError(e);
} }

View File

@@ -1,11 +1,11 @@
const { AngularWebpackPlugin } = require('@ngtools/webpack'); const { AngularWebpackPlugin } = require("@ngtools/webpack");
const webpackConfig = require('../webpack.config'); const webpackConfig = require("../webpack.config");
webpackConfig.entry['app/main'] = './bitwarden_license/src/app/main.ts'; webpackConfig.entry["app/main"] = "./bitwarden_license/src/app/main.ts";
webpackConfig.plugins[webpackConfig.plugins.length -1] = new AngularWebpackPlugin({ webpackConfig.plugins[webpackConfig.plugins.length - 1] = new AngularWebpackPlugin({
tsConfigPath: 'tsconfig.json', tsConfigPath: "tsconfig.json",
entryModule: 'bitwarden_license/src/app/app.module#AppModule', entryModule: "bitwarden_license/src/app/app.module#AppModule",
sourceMap: true, sourceMap: true,
}); });

View File

@@ -1,12 +1,12 @@
function load(envName) { function load(envName) {
return { return {
...require('./config/base.json'), ...require("./config/base.json"),
...loadConfig(envName), ...loadConfig(envName),
...loadConfig('local'), ...loadConfig("local"),
dev: { dev: {
...require('./config/base.json').dev, ...require("./config/base.json").dev,
...loadConfig(envName).dev, ...loadConfig(envName).dev,
...loadConfig('local').dev, ...loadConfig("local").dev,
}, },
}; };
} }
@@ -24,8 +24,7 @@ function loadConfig(configName) {
} catch (e) { } catch (e) {
if (e instanceof Error && e.code === "MODULE_NOT_FOUND") { if (e instanceof Error && e.code === "MODULE_NOT_FOUND") {
return {}; return {};
} } else {
else {
throw e; throw e;
} }
} }
@@ -33,5 +32,5 @@ function loadConfig(configName) {
module.exports = { module.exports = {
load, load,
log log,
}; };

View File

@@ -29,7 +29,7 @@
"dist:bit:selfhost": "npm run build:bit:selfhost:prod", "dist:bit:selfhost": "npm run build:bit:selfhost:prod",
"deploy": "npm run dist:bit && gh-pages -d build", "deploy": "npm run dist:bit && gh-pages -d build",
"deploy:dev": "npm run dist:bit && gh-pages -d build -r git@github.com:kspearrin/bitwarden-web-dev.git", "deploy:dev": "npm run dist:bit && gh-pages -d build -r git@github.com:kspearrin/bitwarden-web-dev.git",
"lint": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts'", "lint": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts' && prettier --check .",
"lint:fix": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts' --fix", "lint:fix": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts' --fix",
"prettier": "prettier --write .", "prettier": "prettier --write .",
"prepare": "husky install" "prepare": "husky install"

View File

@@ -1,34 +1,39 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="/404/bootstrap.min.css" rel="stylesheet" type="text/css" <link
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"> href="/404/bootstrap.min.css"
<link href="/404/font-awesome.min.css" rel="stylesheet" type="text/css" rel="stylesheet"
integrity="sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw=="> type="text/css"
<link href="/404/styles.css" rel="stylesheet" type="text/css"> integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
/>
<link
href="/404/font-awesome.min.css"
rel="stylesheet"
type="text/css"
integrity="sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw=="
/>
<link href="/404/styles.css" rel="stylesheet" type="text/css" />
<link rel="apple-touch-icon" sizes="180x180" href="/images/icons/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="/images/icons/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/images/icons/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/images/icons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/images/icons/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="/images/icons/favicon-16x16.png" />
<link rel="mask-icon" href="/images/icons/safari-pinned-tab.svg" color="#175DDC"> <link rel="mask-icon" href="/images/icons/safari-pinned-tab.svg" color="#175DDC" />
<link rel="manifest" href="/manifest.json"> <link rel="manifest" href="/manifest.json" />
<title>Page not found!</title> <title>Page not found!</title>
<meta name="description" content="404 Page Not Found"> <meta name="description" content="404 Page Not Found" />
</head> </head>
<body> <body>
<div class="banner"> <div class="banner">
<div class="container inner banner"> <div class="container inner banner">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col brand"> <div class="col brand"><i class="fa fa-shield"></i>&nbsp; <strong>bit</strong>warden</div>
<i class="fa fa-shield"></i>&nbsp;
<strong>bit</strong>warden
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -37,14 +42,15 @@
<p>Sorry, but the page you were looking for could not be found.</p> <p>Sorry, but the page you were looking for could not be found.</p>
<p> <p>
<a href="/"> <a href="/">
<img src="/images/404.png" class="img-fluid" alt="404 image" width="80%"/> <img src="/images/404.png" class="img-fluid" alt="404 image" width="80%" />
</a> </a>
</p> </p>
<p>You can <a href="/">return to the web vault</a>, check our <a href="https://status.bitwarden.com/">status page</a> <p>
or <a href="https://bitwarden.com/contact/">contact us</a>.</p> You can <a href="/">return to the web vault</a>, check our
</div> <a href="https://status.bitwarden.com/">status page</a> or
<div class="container footer text-muted content"> <a href="https://bitwarden.com/contact/">contact us</a>.
© Copyright 2021 Bitwarden, Inc. </p>
</div> </div>
<div class="container footer text-muted content">© Copyright 2021 Bitwarden, Inc.</div>
</body> </body>
</html> </html>

View File

@@ -1,88 +1,90 @@
@font-face { @font-face {
font-family: 'Open Sans'; font-family: "Open Sans";
font-style: italic; font-style: italic;
font-weight: 300; font-weight: 300;
src: url(../fonts/Open_Sans-italic-300.woff) format('woff'); src: url(../fonts/Open_Sans-italic-300.woff) format("woff");
unicode-range: U+0-10FFFF; unicode-range: U+0-10FFFF;
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: "Open Sans";
font-style: italic; font-style: italic;
font-weight: 400; font-weight: 400;
src: url(../fonts/Open_Sans-italic-400.woff) format('woff'); src: url(../fonts/Open_Sans-italic-400.woff) format("woff");
unicode-range: U+0-10FFFF; unicode-range: U+0-10FFFF;
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: "Open Sans";
font-style: italic; font-style: italic;
font-weight: 600; font-weight: 600;
src: url(../fonts/Open_Sans-italic-600.woff) format('woff'); src: url(../fonts/Open_Sans-italic-600.woff) format("woff");
unicode-range: U+0-10FFFF; unicode-range: U+0-10FFFF;
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: "Open Sans";
font-style: italic; font-style: italic;
font-weight: 700; font-weight: 700;
src: url(../fonts/Open_Sans-italic-700.woff) format('woff'); src: url(../fonts/Open_Sans-italic-700.woff) format("woff");
unicode-range: U+0-10FFFF; unicode-range: U+0-10FFFF;
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: "Open Sans";
font-style: italic; font-style: italic;
font-weight: 800; font-weight: 800;
src: url(../fonts/Open_Sans-italic-800.woff) format('woff'); src: url(../fonts/Open_Sans-italic-800.woff) format("woff");
unicode-range: U+0-10FFFF; unicode-range: U+0-10FFFF;
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: "Open Sans";
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
src: url(../fonts/Open_Sans-normal-300.woff) format('woff'); src: url(../fonts/Open_Sans-normal-300.woff) format("woff");
unicode-range: U+0-10FFFF; unicode-range: U+0-10FFFF;
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: "Open Sans";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url(../fonts/Open_Sans-normal-400.woff) format('woff'); src: url(../fonts/Open_Sans-normal-400.woff) format("woff");
unicode-range: U+0-10FFFF; unicode-range: U+0-10FFFF;
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: "Open Sans";
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
src: url(../fonts/Open_Sans-normal-600.woff) format('woff'); src: url(../fonts/Open_Sans-normal-600.woff) format("woff");
unicode-range: U+0-10FFFF; unicode-range: U+0-10FFFF;
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: "Open Sans";
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
src: url(../fonts/Open_Sans-normal-700.woff) format('woff'); src: url(../fonts/Open_Sans-normal-700.woff) format("woff");
unicode-range: U+0-10FFFF; unicode-range: U+0-10FFFF;
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: "Open Sans";
font-style: normal; font-style: normal;
font-weight: 800; font-weight: 800;
src: url(../fonts/Open_Sans-normal-800.woff) format('woff'); src: url(../fonts/Open_Sans-normal-800.woff) format("woff");
unicode-range: U+0-10FFFF; unicode-range: U+0-10FFFF;
} }
body { body {
font-family: 'Open Sans'; font-family: "Open Sans";
} }
html, body, .row { html,
body,
.row {
height: 100%; height: 100%;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} }
@@ -98,11 +100,11 @@ h2 {
font-size: 23px; font-size: 23px;
line-height: 25px; line-height: 25px;
color: #fff; color: #fff;
font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif; font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
} }
.banner { .banner {
background-color: #175DDC; background-color: #175ddc;
height: 56px; height: 56px;
} }

View File

@@ -1,30 +1,37 @@
<div class="mt-5 d-flex justify-content-center" *ngIf="loading"> <div class="mt-5 d-flex justify-content-center" *ngIf="loading">
<div> <div>
<img class="mb-4 logo logo-themed" alt="Bitwarden"> <img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center"> <p class="text-center">
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'loading' | i18n}}</span> class="fa fa-spinner fa-spin fa-2x text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p> </p>
</div> </div>
</div> </div>
<div class="container" *ngIf="!loading && !authed"> <div class="container" *ngIf="!loading && !authed">
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5"> <div class="col-5">
<p class="lead text-center mb-4">{{'emergencyAccess' | i18n}}</p> <p class="lead text-center mb-4">{{ "emergencyAccess" | i18n }}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">
<p class="text-center"> <p class="text-center">
{{name}} {{ name }}
</p> </p>
<p>{{'acceptEmergencyAccess' | i18n}}</p> <p>{{ "acceptEmergencyAccess" | i18n }}</p>
<hr> <hr />
<div class="d-flex"> <div class="d-flex">
<a routerLink="/" [queryParams]="{email: email}" class="btn btn-primary btn-block"> <a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
{{'logIn' | i18n}} {{ "logIn" | i18n }}
</a> </a>
<a routerLink="/register" [queryParams]="{email: email}" <a
class="btn btn-primary btn-block ml-2 mt-0"> routerLink="/register"
{{'createAccount' | i18n}} [queryParams]="{ email: email }"
class="btn btn-primary btn-block ml-2 mt-0"
>
{{ "createAccount" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,27 +1,23 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { import { ActivatedRoute, Router } from "@angular/router";
ActivatedRoute,
Router,
} from '@angular/router';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.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 { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { EmergencyAccessAcceptRequest } from 'jslib-common/models/request/emergencyAccessAcceptRequest'; import { EmergencyAccessAcceptRequest } from "jslib-common/models/request/emergencyAccessAcceptRequest";
import { BaseAcceptComponent } from '../common/base.accept.component'; import { BaseAcceptComponent } from "../common/base.accept.component";
@Component({ @Component({
selector: 'app-accept-emergency', selector: "app-accept-emergency",
templateUrl: 'accept-emergency.component.html', templateUrl: "accept-emergency.component.html",
}) })
export class AcceptEmergencyComponent extends BaseAcceptComponent { export class AcceptEmergencyComponent extends BaseAcceptComponent {
name: string; name: string;
protected requiredParameters: string[] = ['id', 'name', 'email', 'token']; protected requiredParameters: string[] = ["id", "name", "email", "token"];
protected failedShortMessage = 'emergencyInviteAcceptFailedShort'; protected failedShortMessage = "emergencyInviteAcceptFailedShort";
protected failedMessage = 'emergencyInviteAcceptFailed'; protected failedMessage = "emergencyInviteAcceptFailed";
constructor( constructor(
router: Router, router: Router,
@@ -31,13 +27,7 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent {
private apiService: ApiService, private apiService: ApiService,
stateService: StateService stateService: StateService
) { ) {
super( super(router, platformUtilsService, i18nService, route, stateService);
router,
platformUtilsService,
i18nService,
route,
stateService
);
} }
async authedHandler(qParams: any): Promise<void> { async authedHandler(qParams: any): Promise<void> {
@@ -45,16 +35,20 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent {
request.token = qParams.token; request.token = qParams.token;
this.actionPromise = this.apiService.postEmergencyAccessAccept(qParams.id, request); this.actionPromise = this.apiService.postEmergencyAccessAccept(qParams.id, request);
await this.actionPromise; await this.actionPromise;
this.platformUtilService.showToast('success', this.i18nService.t('inviteAccepted'), this.platformUtilService.showToast(
this.i18nService.t('emergencyInviteAcceptedDesc'), {timeout: 10000}); "success",
this.router.navigate(['/vault']); this.i18nService.t("inviteAccepted"),
this.i18nService.t("emergencyInviteAcceptedDesc"),
{ timeout: 10000 }
);
this.router.navigate(["/vault"]);
} }
async unauthedHandler(qParams: any): Promise<void> { async unauthedHandler(qParams: any): Promise<void> {
this.name = qParams.name; this.name = qParams.name;
if (this.name != null) { if (this.name != null) {
// Fix URL encoding of space issue with Angular // Fix URL encoding of space issue with Angular
this.name = this.name.replace(/\+/g, ' '); this.name = this.name.replace(/\+/g, " ");
} }
} }
} }

View File

@@ -1,31 +1,38 @@
<div class="mt-5 d-flex justify-content-center" *ngIf="loading"> <div class="mt-5 d-flex justify-content-center" *ngIf="loading">
<div> <div>
<img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden"> <img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden" />
<p class="text-center"> <p class="text-center">
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'loading' | i18n}}</span> class="fa fa-spinner fa-spin fa-2x text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p> </p>
</div> </div>
</div> </div>
<div class="container" *ngIf="!loading && !authed"> <div class="container" *ngIf="!loading && !authed">
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5"> <div class="col-5">
<p class="lead text-center mb-4">{{'joinOrganization' | i18n}}</p> <p class="lead text-center mb-4">{{ "joinOrganization" | i18n }}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">
<p class="text-center"> <p class="text-center">
{{orgName}} {{ orgName }}
<strong class="d-block mt-2">{{email}}</strong> <strong class="d-block mt-2">{{ email }}</strong>
</p> </p>
<p>{{'joinOrganizationDesc' | i18n}}</p> <p>{{ "joinOrganizationDesc" | i18n }}</p>
<hr> <hr />
<div class="d-flex"> <div class="d-flex">
<a routerLink="/" [queryParams]="{email: email}" class="btn btn-primary btn-block"> <a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
{{'logIn' | i18n}} {{ "logIn" | i18n }}
</a> </a>
<a routerLink="/register" [queryParams]="{email: email}" <a
class="btn btn-primary btn-block ml-2 mt-0"> routerLink="/register"
{{'createAccount' | i18n}} [queryParams]="{ email: email }"
class="btn btn-primary btn-block ml-2 mt-0"
>
{{ "createAccount" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,32 +1,29 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { import { ActivatedRoute, Router } from "@angular/router";
ActivatedRoute,
Router,
} from '@angular/router';
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";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { OrganizationUserAcceptRequest } from 'jslib-common/models/request/organizationUserAcceptRequest'; import { OrganizationUserAcceptRequest } from "jslib-common/models/request/organizationUserAcceptRequest";
import { OrganizationUserResetPasswordEnrollmentRequest } from 'jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest'; import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
import { Policy } from 'jslib-common/models/domain/policy'; import { Policy } from "jslib-common/models/domain/policy";
import { BaseAcceptComponent } from '../common/base.accept.component'; import { BaseAcceptComponent } from "../common/base.accept.component";
@Component({ @Component({
selector: 'app-accept-organization', selector: "app-accept-organization",
templateUrl: 'accept-organization.component.html', templateUrl: "accept-organization.component.html",
}) })
export class AcceptOrganizationComponent extends BaseAcceptComponent { export class AcceptOrganizationComponent extends BaseAcceptComponent {
orgName: string; orgName: string;
protected requiredParameters: string[] = ['organizationId', 'organizationUserId', 'token']; protected requiredParameters: string[] = ["organizationId", "organizationUserId", "token"];
constructor( constructor(
router: Router, router: Router,
@@ -39,26 +36,22 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
private policyService: PolicyService, private policyService: PolicyService,
private logService: LogService private logService: LogService
) { ) {
super( super(router, platformUtilsService, i18nService, route, stateService);
router,
platformUtilsService,
i18nService,
route,
stateService
);
} }
async authedHandler(qParams: any): Promise<void> { async authedHandler(qParams: any): Promise<void> {
const request = new OrganizationUserAcceptRequest(); const request = new OrganizationUserAcceptRequest();
request.token = qParams.token; request.token = qParams.token;
if (await this.performResetPasswordAutoEnroll(qParams)) { if (await this.performResetPasswordAutoEnroll(qParams)) {
this.actionPromise = this.apiService.postOrganizationUserAccept(qParams.organizationId, this.actionPromise = this.apiService
qParams.organizationUserId, request).then(() => { .postOrganizationUserAccept(qParams.organizationId, qParams.organizationUserId, request)
.then(() => {
// Retrieve Public Key // Retrieve Public Key
return this.apiService.getOrganizationKeys(qParams.organizationId); return this.apiService.getOrganizationKeys(qParams.organizationId);
}).then(async response => { })
.then(async (response) => {
if (response == null) { if (response == null) {
throw new Error(this.i18nService.t('resetPasswordOrgKeysError')); throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
} }
const publicKey = Utils.fromB64ToArray(response.publicKey); const publicKey = Utils.fromB64ToArray(response.publicKey);
@@ -71,26 +64,37 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(); const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.resetPasswordKey = encryptedKey.encryptedString; resetRequest.resetPasswordKey = encryptedKey.encryptedString;
return this.apiService.putOrganizationUserResetPasswordEnrollment(qParams.organizationId, await this.stateService.getUserId(), resetRequest); return this.apiService.putOrganizationUserResetPasswordEnrollment(
qParams.organizationId,
await this.stateService.getUserId(),
resetRequest
);
}); });
} else { } else {
this.actionPromise = this.apiService.postOrganizationUserAccept(qParams.organizationId, this.actionPromise = this.apiService.postOrganizationUserAccept(
qParams.organizationUserId, request); qParams.organizationId,
qParams.organizationUserId,
request
);
} }
await this.actionPromise; await this.actionPromise;
this.platformUtilService.showToast('success', this.i18nService.t('inviteAccepted'), this.platformUtilService.showToast(
this.i18nService.t('inviteAcceptedDesc'), {timeout: 10000}); "success",
this.i18nService.t("inviteAccepted"),
this.i18nService.t("inviteAcceptedDesc"),
{ timeout: 10000 }
);
await this.stateService.setOrganizationInvitation(null); await this.stateService.setOrganizationInvitation(null);
this.router.navigate(['/vault']); this.router.navigate(["/vault"]);
} }
async unauthedHandler(qParams: any): Promise<void> { async unauthedHandler(qParams: any): Promise<void> {
this.orgName = qParams.organizationName; this.orgName = qParams.organizationName;
if (this.orgName != null) { if (this.orgName != null) {
// Fix URL encoding of space issue with Angular // Fix URL encoding of space issue with Angular
this.orgName = this.orgName.replace(/\+/g, ' '); this.orgName = this.orgName.replace(/\+/g, " ");
} }
await this.stateService.setOrganizationInvitation(qParams); await this.stateService.setOrganizationInvitation(qParams);
} }
@@ -98,15 +102,22 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
private async performResetPasswordAutoEnroll(qParams: any): Promise<boolean> { private async performResetPasswordAutoEnroll(qParams: any): Promise<boolean> {
let policyList: Policy[] = null; let policyList: Policy[] = null;
try { try {
const policies = await this.apiService.getPoliciesByToken(qParams.organizationId, qParams.token, const policies = await this.apiService.getPoliciesByToken(
qParams.email, qParams.organizationUserId); qParams.organizationId,
qParams.token,
qParams.email,
qParams.organizationUserId
);
policyList = this.policyService.mapPoliciesFromToken(policies); policyList = this.policyService.mapPoliciesFromToken(policies);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
if (policyList != null) { if (policyList != null) {
const result = this.policyService.getResetPasswordPolicyOptions(policyList, qParams.organizationId); const result = this.policyService.getResetPasswordPolicyOptions(
policyList,
qParams.organizationId
);
// Return true if policy enabled and auto-enroll enabled // Return true if policy enabled and auto-enroll enabled
return result[1] && result[0].autoEnrollEnabled; return result[1] && result[0].autoEnrollEnabled;
} }

View File

@@ -1,23 +1,40 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5"> <div class="col-5">
<p class="lead text-center mb-4">{{'passwordHint' | i18n}}</p> <p class="lead text-center mb-4">{{ "passwordHint" | i18n }}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">
<div class="form-group"> <div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label> <label for="email">{{ "emailAddress" | i18n }}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required <input
appAutofocus inputmode="email" appInputVerbatim="false"> id="email"
<small class="form-text text-muted">{{'enterEmailToGetHint' | i18n}}</small> class="form-control"
type="text"
name="Email"
[(ngModel)]="email"
required
appAutofocus
inputmode="email"
appInputVerbatim="false"
/>
<small class="form-text text-muted">{{ "enterEmailToGetHint" | i18n }}</small>
</div> </div>
<hr> <hr />
<div class="d-flex"> <div class="d-flex">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading"> <button
<span [hidden]="form.loading">{{'submit' | i18n}}</span> type="submit"
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
>
<span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button> </button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0"> <a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}} {{ "cancel" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,21 +1,25 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { Router } from '@angular/router'; import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { HintComponent as BaseHintComponent } from 'jslib-angular/components/hint.component'; import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component";
@Component({ @Component({
selector: 'app-hint', selector: "app-hint",
templateUrl: 'hint.component.html', templateUrl: "hint.component.html",
}) })
export class HintComponent extends BaseHintComponent { export class HintComponent extends BaseHintComponent {
constructor(router: Router, i18nService: I18nService, constructor(
apiService: ApiService, platformUtilsService: PlatformUtilsService, router: Router,
logService: LogService) { i18nService: I18nService,
apiService: ApiService,
platformUtilsService: PlatformUtilsService,
logService: LogService
) {
super(router, i18nService, apiService, platformUtilsService, logService); super(router, i18nService, apiService, platformUtilsService, logService);
} }
} }

View File

@@ -4,35 +4,61 @@
<p class="text-center mb-4"> <p class="text-center mb-4">
<i class="fa fa-lock fa-4x text-muted" aria-hidden="true"></i> <i class="fa fa-lock fa-4x text-muted" aria-hidden="true"></i>
</p> </p>
<p class="lead text-center mx-4 mb-4">{{'yourVaultIsLocked' | i18n}}</p> <p class="lead text-center mx-4 mb-4">{{ "yourVaultIsLocked" | i18n }}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">
<div class="form-group"> <div class="form-group">
<label for="masterPassword">{{'masterPass' | i18n}}</label> <label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex"> <div class="d-flex">
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPassword" class="text-monospace form-control" [(ngModel)]="masterPassword" id="masterPassword"
required appAutofocus appInputVerbatim> type="{{ showPassword ? 'text' : 'password' }}"
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}" name="MasterPassword"
(click)="togglePassword()"> class="text-monospace form-control"
<i class="fa fa-lg" aria-hidden="true" [(ngModel)]="masterPassword"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> required
appAutofocus
appInputVerbatim
/>
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
</div> </div>
<small class="text-muted form-text"> <small class="text-muted form-text">
{{'loggedInAsEmailOn' | i18n : email : webVaultHostname}} {{ "loggedInAsEmailOn" | i18n: email:webVaultHostname }}
</small> </small>
</div> </div>
<hr> <hr />
<div class="d-flex"> <div class="d-flex">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading"> <button
type="submit"
class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
>
<span> <span>
<i class="fa fa-unlock-alt" aria-hidden="true"></i> {{'unlock' | i18n}} <i class="fa fa-unlock-alt" aria-hidden="true"></i> {{ "unlock" | i18n }}
</span> </span>
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button> </button>
<button type="button" class="btn btn-outline-secondary btn-block ml-2 mt-0" (click)="logOut()"> <button
{{'logOut' | i18n}} type="button"
class="btn btn-outline-secondary btn-block ml-2 mt-0"
(click)="logOut()"
>
{{ "logOut" | i18n }}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,45 +1,62 @@
import { import { Component, NgZone } from "@angular/core";
Component, import { Router } from "@angular/router";
NgZone,
} from '@angular/core';
import { Router } from '@angular/router';
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";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.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 { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { LogService } from 'jslib-common/abstractions/log.service'; 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 { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { RouterService } from '../services/router.service'; import { RouterService } from "../services/router.service";
import { LockComponent as BaseLockComponent } from 'jslib-angular/components/lock.component'; import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
@Component({ @Component({
selector: 'app-lock', selector: "app-lock",
templateUrl: 'lock.component.html', templateUrl: "lock.component.html",
}) })
export class LockComponent extends BaseLockComponent { export class LockComponent extends BaseLockComponent {
constructor(router: Router, i18nService: I18nService, constructor(
platformUtilsService: PlatformUtilsService, messagingService: MessagingService, router: Router,
cryptoService: CryptoService, vaultTimeoutService: VaultTimeoutService, i18nService: I18nService,
environmentService: EnvironmentService, private routerService: RouterService, platformUtilsService: PlatformUtilsService,
stateService: StateService, apiService: ApiService, logService: LogService, messagingService: MessagingService,
keyConnectorService: KeyConnectorService, ngZone: NgZone) { cryptoService: CryptoService,
super(router, i18nService, platformUtilsService, messagingService, cryptoService, vaultTimeoutService: VaultTimeoutService,
vaultTimeoutService, environmentService, stateService, apiService, logService, environmentService: EnvironmentService,
keyConnectorService, ngZone); private routerService: RouterService,
stateService: StateService,
apiService: ApiService,
logService: LogService,
keyConnectorService: KeyConnectorService,
ngZone: NgZone
) {
super(
router,
i18nService,
platformUtilsService,
messagingService,
cryptoService,
vaultTimeoutService,
environmentService,
stateService,
apiService,
logService,
keyConnectorService,
ngZone
);
} }
async ngOnInit() { async ngOnInit() {
await super.ngOnInit(); await super.ngOnInit();
this.onSuccessfulSubmit = async () => { this.onSuccessfulSubmit = async () => {
const previousUrl = this.routerService.getPreviousUrl(); const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl !== '/' && previousUrl.indexOf('lock') === -1) { if (previousUrl !== "/" && previousUrl.indexOf("lock") === -1) {
this.successRoute = previousUrl; this.successRoute = previousUrl;
} }
this.router.navigate([this.successRoute]); this.router.navigate([this.successRoute]);

View File

@@ -1,57 +1,97 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5"> <div class="col-5">
<img class="mb-2 logo logo-themed" alt="Bitwarden"> <img class="mb-2 logo logo-themed" alt="Bitwarden" />
<p class="lead text-center mx-4 mb-4">{{'loginOrCreateNewAccount' | i18n}}</p> <p class="lead text-center mx-4 mb-4">{{ "loginOrCreateNewAccount" | i18n }}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">
<app-callout type="warning" title="{{'resetPasswordPolicyAutoEnroll' | i18n}}" <app-callout
*ngIf="showResetPasswordAutoEnrollWarning"> type="warning"
{{'resetPasswordAutoEnrollInviteWarning' | i18n}} title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="showResetPasswordAutoEnrollWarning"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout> </app-callout>
<div class="form-group"> <div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label> <label for="email">{{ "emailAddress" | i18n }}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required <input
inputmode="email" appInputVerbatim="false"> id="email"
class="form-control"
type="text"
name="Email"
[(ngModel)]="email"
required
inputmode="email"
appInputVerbatim="false"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="masterPassword">{{'masterPass' | i18n}}</label> <label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex"> <div class="d-flex">
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPassword" class="text-monospace form-control" [(ngModel)]="masterPassword" id="masterPassword"
required appInputVerbatim> type="{{ showPassword ? 'text' : 'password' }}"
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}" name="MasterPassword"
(click)="togglePassword()"> class="text-monospace form-control"
<i class="fa fa-lg" aria-hidden="true" [(ngModel)]="masterPassword"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> required
appInputVerbatim
/>
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
</div> </div>
<small class="form-text"> <small class="form-text">
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a> <a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
</small> </small>
</div> </div>
<div class="form-check mb-3"> <div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="rememberEmail" name="RememberEmail" <input
[(ngModel)]="rememberEmail"> type="checkbox"
<label class="form-check-label" for="rememberEmail">{{'rememberEmail' | i18n}}</label> class="form-check-input"
id="rememberEmail"
name="RememberEmail"
[(ngModel)]="rememberEmail"
/>
<label class="form-check-label" for="rememberEmail">{{ "rememberEmail" | i18n }}</label>
</div> </div>
<div class="mb-n3" [hidden]="!showCaptcha()"><iframe id="hcaptcha_iframe" height="80"></iframe></div> <div class="mb-n3" [hidden]="!showCaptcha()">
<hr> <iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
<hr />
<div class="d-flex"> <div class="d-flex">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading"> <button
<span> type="submit"
<i class="fa fa-sign-in" aria-hidden="true"></i> {{'logIn' | i18n}} class="btn btn-primary btn-block btn-submit"
</span> [disabled]="form.loading"
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> >
<span> <i class="fa fa-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }} </span>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button> </button>
<a routerLink="/register" [queryParams]="{email: email}" <a
class="btn btn-outline-secondary btn-block ml-2 mt-0"> routerLink="/register"
<i class="fa fa-pencil-square-o" aria-hidden="true"></i> {{'createAccount' | i18n}} [queryParams]="{ email: email }"
class="btn btn-outline-secondary btn-block ml-2 mt-0"
>
<i class="fa fa-pencil-square-o" aria-hidden="true"></i> {{ "createAccount" | i18n }}
</a> </a>
</div> </div>
<div class="d-flex"> <div class="d-flex">
<a routerLink="/sso" class="btn btn-outline-secondary btn-block mt-2"> <a routerLink="/sso" class="btn btn-outline-secondary btn-block mt-2">
<i class="fa fa-bank" aria-hidden="true"></i> {{'enterpriseSingleSignOn' | i18n}} <i class="fa fa-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,68 +1,79 @@
import { import { Component, NgZone } from "@angular/core";
Component, import { ActivatedRoute, Router } from "@angular/router";
NgZone,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { first } from 'rxjs/operators'; import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.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 { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { LoginComponent as BaseLoginComponent } from 'jslib-angular/components/login.component'; import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.component";
import { Policy } from 'jslib-common/models/domain/policy'; import { Policy } from "jslib-common/models/domain/policy";
@Component({ @Component({
selector: 'app-login', selector: "app-login",
templateUrl: 'login.component.html', templateUrl: "login.component.html",
}) })
export class LoginComponent extends BaseLoginComponent { export class LoginComponent extends BaseLoginComponent {
showResetPasswordAutoEnrollWarning = false; showResetPasswordAutoEnrollWarning = false;
constructor(authService: AuthService, router: Router, constructor(
i18nService: I18nService, private route: ActivatedRoute, stateService: StateService, authService: AuthService,
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, router: Router,
passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService, i18nService: I18nService,
private apiService: ApiService, private policyService: PolicyService, logService: LogService, private route: ActivatedRoute,
ngZone: NgZone) { stateService: StateService,
super(authService, router, platformUtilsService: PlatformUtilsService,
platformUtilsService, i18nService, environmentService: EnvironmentService,
stateService, environmentService, passwordGenerationService: PasswordGenerationService,
passwordGenerationService, cryptoFunctionService, cryptoFunctionService: CryptoFunctionService,
logService, ngZone); private apiService: ApiService,
private policyService: PolicyService,
logService: LogService,
ngZone: NgZone
) {
super(
authService,
router,
platformUtilsService,
i18nService,
stateService,
environmentService,
passwordGenerationService,
cryptoFunctionService,
logService,
ngZone
);
this.onSuccessfulLoginNavigate = this.goAfterLogIn; this.onSuccessfulLoginNavigate = this.goAfterLogIn;
} }
async ngOnInit() { async ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => { this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.email != null && qParams.email.indexOf('@') > -1) { if (qParams.email != null && qParams.email.indexOf("@") > -1) {
this.email = qParams.email; this.email = qParams.email;
} }
if (qParams.premium != null) { if (qParams.premium != null) {
this.stateService.setLoginRedirect({ route: '/settings/premium' }); this.stateService.setLoginRedirect({ route: "/settings/premium" });
} else if (qParams.org != null) { } else if (qParams.org != null) {
this.stateService.setLoginRedirect( this.stateService.setLoginRedirect({
{ route: '/settings/create-organization', qParams: { plan: qParams.org } }); route: "/settings/create-organization",
qParams: { plan: qParams.org },
});
} }
// Are they coming from an email for sponsoring a families organization // Are they coming from an email for sponsoring a families organization
if (qParams.sponsorshipToken != null) { if (qParams.sponsorshipToken != null) {
// After logging in redirect them to setup the families sponsorship // After logging in redirect them to setup the families sponsorship
this.stateService.setLoginRedirect({ this.stateService.setLoginRedirect({
route: '/setup/families-for-enterprise', route: "/setup/families-for-enterprise",
qParams: { token: qParams.sponsorshipToken }, qParams: { token: qParams.sponsorshipToken },
}); });
} }
@@ -73,15 +84,22 @@ export class LoginComponent extends BaseLoginComponent {
if (invite != null) { if (invite != null) {
let policyList: Policy[] = null; let policyList: Policy[] = null;
try { try {
const policies = await this.apiService.getPoliciesByToken(invite.organizationId, invite.token, const policies = await this.apiService.getPoliciesByToken(
invite.email, invite.organizationUserId); invite.organizationId,
invite.token,
invite.email,
invite.organizationUserId
);
policyList = this.policyService.mapPoliciesFromToken(policies); policyList = this.policyService.mapPoliciesFromToken(policies);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
if (policyList != null) { if (policyList != null) {
const result = this.policyService.getResetPasswordPolicyOptions(policyList, invite.organizationId); const result = this.policyService.getResetPasswordPolicyOptions(
policyList,
invite.organizationId
);
// Set to true if policy enabled and auto-enroll enabled // Set to true if policy enabled and auto-enroll enabled
this.showResetPasswordAutoEnrollWarning = result[1] && result[0].autoEnrollEnabled; this.showResetPasswordAutoEnrollWarning = result[1] && result[0].autoEnrollEnabled;
} }

View File

@@ -1,23 +1,40 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5"> <div class="col-5">
<p class="lead text-center mb-4">{{'deleteAccount' | i18n}}</p> <p class="lead text-center mb-4">{{ "deleteAccount" | i18n }}</p>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<p>{{'deleteRecoverDesc' | i18n}}</p> <p>{{ "deleteRecoverDesc" | i18n }}</p>
<div class="form-group"> <div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label> <label for="email">{{ "emailAddress" | i18n }}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required <input
appAutofocus inputmode="email" appInputVerbatim="false"> id="email"
class="form-control"
type="text"
name="Email"
[(ngModel)]="email"
required
appAutofocus
inputmode="email"
appInputVerbatim="false"
/>
</div> </div>
<hr> <hr />
<div class="d-flex"> <div class="d-flex">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading"> <button
<span>{{'submit' | i18n}}</span> type="submit"
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
>
<span>{{ "submit" | i18n }}</span>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button> </button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0"> <a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}} {{ "cancel" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,25 +1,28 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { Router } from '@angular/router'; import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { DeleteRecoverRequest } from 'jslib-common/models/request/deleteRecoverRequest'; import { DeleteRecoverRequest } from "jslib-common/models/request/deleteRecoverRequest";
@Component({ @Component({
selector: 'app-recover-delete', selector: "app-recover-delete",
templateUrl: 'recover-delete.component.html', templateUrl: "recover-delete.component.html",
}) })
export class RecoverDeleteComponent { export class RecoverDeleteComponent {
email: string; email: string;
formPromise: Promise<any>; formPromise: Promise<any>;
constructor(private router: Router, private apiService: ApiService, constructor(
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private router: Router,
private logService: LogService) { private apiService: ApiService,
} private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private logService: LogService
) {}
async submit() { async submit() {
try { try {
@@ -27,8 +30,12 @@ export class RecoverDeleteComponent {
request.email = this.email.trim().toLowerCase(); request.email = this.email.trim().toLowerCase();
this.formPromise = this.apiService.postAccountRecoverDelete(request); this.formPromise = this.apiService.postAccountRecoverDelete(request);
await this.formPromise; await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('deleteRecoverEmailSent')); this.platformUtilsService.showToast(
this.router.navigate(['/']); "success",
null,
this.i18nService.t("deleteRecoverEmailSent")
);
this.router.navigate(["/"]);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }

View File

@@ -1,36 +1,72 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5"> <div class="col-5">
<p class="lead text-center mb-4">{{'recoverAccountTwoStep' | i18n}}</p> <p class="lead text-center mb-4">{{ "recoverAccountTwoStep" | i18n }}</p>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<p>{{'recoverAccountTwoStepDesc' | i18n}} <p>
<a href="https://help.bitwarden.com/article/lost-two-step-device/" target="_blank" {{ "recoverAccountTwoStepDesc" | i18n }}
rel="noopener">{{'learnMore' | i18n}}</a> <a
href="https://help.bitwarden.com/article/lost-two-step-device/"
target="_blank"
rel="noopener"
>{{ "learnMore" | i18n }}</a
>
</p> </p>
<div class="form-group"> <div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label> <label for="email">{{ "emailAddress" | i18n }}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required <input
appAutofocus inputmode="email" appInputVerbatim="false"> id="email"
class="form-control"
type="text"
name="Email"
[(ngModel)]="email"
required
appAutofocus
inputmode="email"
appInputVerbatim="false"
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="masterPassword">{{'masterPass' | i18n}}</label> <label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input id="masterPassword" type="password" name="MasterPassword" class="form-control" <input
[(ngModel)]="masterPassword" required appInputVerbatim> id="masterPassword"
type="password"
name="MasterPassword"
class="form-control"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="recoveryCode">{{'recoveryCodeTitle' | i18n}}</label> <label for="recoveryCode">{{ "recoveryCodeTitle" | i18n }}</label>
<input id="recoveryCode" class="text-monospace form-control" type="text" name="RecoveryCode" <input
[(ngModel)]="recoveryCode" required appInputVerbatim> id="recoveryCode"
class="text-monospace form-control"
type="text"
name="RecoveryCode"
[(ngModel)]="recoveryCode"
required
appInputVerbatim
/>
</div> </div>
<hr> <hr />
<div class="d-flex"> <div class="d-flex">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading"> <button
<span>{{'submit' | i18n}}</span> type="submit"
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
>
<span>{{ "submit" | i18n }}</span>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button> </button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0"> <a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}} {{ "cancel" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,18 +1,18 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { Router } from '@angular/router'; import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TwoFactorRecoveryRequest } from 'jslib-common/models/request/twoFactorRecoveryRequest'; import { TwoFactorRecoveryRequest } from "jslib-common/models/request/twoFactorRecoveryRequest";
@Component({ @Component({
selector: 'app-recover-two-factor', selector: "app-recover-two-factor",
templateUrl: 'recover-two-factor.component.html', templateUrl: "recover-two-factor.component.html",
}) })
export class RecoverTwoFactorComponent { export class RecoverTwoFactorComponent {
email: string; email: string;
@@ -20,22 +20,31 @@ export class RecoverTwoFactorComponent {
recoveryCode: string; recoveryCode: string;
formPromise: Promise<any>; formPromise: Promise<any>;
constructor(private router: Router, private apiService: ApiService, constructor(
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private router: Router,
private cryptoService: CryptoService, private authService: AuthService, private apiService: ApiService,
private logService: LogService) { } private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private cryptoService: CryptoService,
private authService: AuthService,
private logService: LogService
) {}
async submit() { async submit() {
try { try {
const request = new TwoFactorRecoveryRequest(); const request = new TwoFactorRecoveryRequest();
request.recoveryCode = this.recoveryCode.replace(/\s/g, '').toLowerCase(); request.recoveryCode = this.recoveryCode.replace(/\s/g, "").toLowerCase();
request.email = this.email.trim().toLowerCase(); request.email = this.email.trim().toLowerCase();
const key = await this.authService.makePreloginKey(this.masterPassword, request.email); const key = await this.authService.makePreloginKey(this.masterPassword, request.email);
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key); request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
this.formPromise = this.apiService.postTwoFactorRecover(request); this.formPromise = this.apiService.postTwoFactorRecover(request);
await this.formPromise; await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('twoStepRecoverDisabled')); this.platformUtilsService.showToast(
this.router.navigate(['/']); "success",
null,
this.i18nService.t("twoStepRecoverDisabled")
);
this.router.navigate(["/"]);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }

View File

@@ -3,7 +3,11 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-7"> <div class="col-7">
<img alt="Bitwarden" class="logo mb-2" src="../../images/register-layout/logo-horizontal-white.png"> <img
alt="Bitwarden"
class="logo mb-2"
src="../../images/register-layout/logo-horizontal-white.png"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -21,12 +25,12 @@
<figure> <figure>
<figcaption> <figcaption>
<cite> <cite>
<img src="../../images/register-layout/wired-logo.png" alt="Wired"> <img src="../../images/register-layout/wired-logo.png" alt="Wired" />
</cite> </cite>
</figcaption> </figcaption>
<blockquote> <blockquote>
"Bitwarden has become a popular choice among open-source software advocates. After using "Bitwarden has become a popular choice among open-source software advocates. After
it for a few months, I can see why." - February 2020 using it for a few months, I can see why." - February 2020
</blockquote> </blockquote>
</figure> </figure>
</div> </div>
@@ -38,97 +42,168 @@
</div> </div>
</div> </div>
</div> </div>
<div [ngClass]="{'col-5': layout, 'col-12': !layout}"> <div [ngClass]="{ 'col-5': layout, 'col-12': !layout }">
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div [ngClass]="{'col-5': !layout, 'col-12': layout}"> <div [ngClass]="{ 'col-5': !layout, 'col-12': layout }">
<p class="lead text-center mb-4" *ngIf="!layout">{{'createAccount' | i18n}}</p> <p class="lead text-center mb-4" *ngIf="!layout">{{ "createAccount" | i18n }}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">
<app-callout title="{{'createOrganizationStep1' | i18n}}" type="info" <app-callout
icon="fa-thumb-tack" *ngIf="showCreateOrgMessage"> title="{{ 'createOrganizationStep1' | i18n }}"
{{'createOrganizationCreatePersonalAccount' | i18n}} type="info"
icon="fa-thumb-tack"
*ngIf="showCreateOrgMessage"
>
{{ "createOrganizationCreatePersonalAccount" | i18n }}
</app-callout> </app-callout>
<div class="form-group"> <div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label> <label for="email">{{ "emailAddress" | i18n }}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" <input
required [appAutofocus]="email === ''" inputmode="email" id="email"
appInputVerbatim="false"> class="form-control"
<small class="form-text text-muted">{{'emailAddressDesc' | i18n}}</small> type="text"
name="Email"
[(ngModel)]="email"
required
[appAutofocus]="email === ''"
inputmode="email"
appInputVerbatim="false"
/>
<small class="form-text text-muted">{{ "emailAddressDesc" | i18n }}</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="name">{{'yourName' | i18n}}</label> <label for="name">{{ "yourName" | i18n }}</label>
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name" <input
[appAutofocus]="email !== ''"> id="name"
<small class="form-text text-muted">{{'yourNameDesc' | i18n}}</small> class="form-control"
type="text"
name="Name"
[(ngModel)]="name"
[appAutofocus]="email !== ''"
/>
<small class="form-text text-muted">{{ "yourNameDesc" | i18n }}</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" <app-callout
*ngIf="enforcedPolicyOptions"> type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout> </app-callout>
<label for="masterPassword">{{'masterPass' | i18n}}</label> <label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex"> <div class="d-flex">
<div class="w-100"> <div class="w-100">
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPassword" class="text-monospace form-control mb-1" id="masterPassword"
[(ngModel)]="masterPassword" (input)="updatePasswordStrength()" required type="{{ showPassword ? 'text' : 'password' }}"
appInputVerbatim> name="MasterPassword"
class="text-monospace form-control mb-1"
[(ngModel)]="masterPassword"
(input)="updatePasswordStrength()"
required
appInputVerbatim
/>
<app-password-strength [score]="masterPasswordScore" [showText]="true"> <app-password-strength [score]="masterPasswordScore" [showText]="true">
</app-password-strength> </app-password-strength>
</div> </div>
<div> <div>
<button type="button" class="ml-1 btn btn-link" <button
appA11yTitle="{{'toggleVisibility' | i18n}}" type="button"
(click)="togglePassword(false)"> class="ml-1 btn btn-link"
<i class="fa fa-lg" aria-hidden="true" appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> (click)="togglePassword(false)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{
'fa-eye': !showPassword,
'fa-eye-slash': showPassword
}"
></i>
</button> </button>
<div class="progress-bar invisible"></div> <div class="progress-bar invisible"></div>
</div> </div>
</div> </div>
<small class="form-text text-muted">{{'masterPassDesc' | i18n}}</small> <small class="form-text text-muted">{{ "masterPassDesc" | i18n }}</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label> <label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<div class="d-flex"> <div class="d-flex">
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPasswordRetype" class="text-monospace form-control" id="masterPasswordRetype"
[(ngModel)]="confirmMasterPassword" required appInputVerbatim> type="{{ showPassword ? 'text' : 'password' }}"
<button type="button" class="ml-1 btn btn-link" name="MasterPasswordRetype"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)"> class="text-monospace form-control"
<i class="fa fa-lg" aria-hidden="true" [(ngModel)]="confirmMasterPassword"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> required
appInputVerbatim
/>
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="hint">{{'masterPassHint' | i18n}}</label> <label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint"> <input
<small class="form-text text-muted">{{'masterPassHintDesc' | i18n}}</small> id="hint"
class="form-control"
type="text"
name="Hint"
[(ngModel)]="hint"
/>
<small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
</div>
<div [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div> </div>
<div [hidden]="!showCaptcha()"><iframe id="hcaptcha_iframe" height="80"></iframe></div>
<div class="form-group" *ngIf="showTerms"> <div class="form-group" *ngIf="showTerms">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="acceptPolicies" <input
[(ngModel)]="acceptPolicies" name="AcceptPolicies"> class="form-check-input"
type="checkbox"
id="acceptPolicies"
[(ngModel)]="acceptPolicies"
name="AcceptPolicies"
/>
<label class="form-check-label small text-muted" for="acceptPolicies"> <label class="form-check-label small text-muted" for="acceptPolicies">
{{'acceptPolicies' | i18n}}<br> {{ "acceptPolicies" | i18n }}<br />
<a href="https://bitwarden.com/terms/" target="_blank" <a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{
rel="noopener">{{'termsOfService' | i18n}}</a>, "termsOfService" | i18n
<a href="https://bitwarden.com/privacy/" target="_blank" }}</a
rel="noopener">{{'privacyPolicy' | i18n}}</a> >,
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{
"privacyPolicy" | i18n
}}</a>
</label> </label>
</div> </div>
</div> </div>
<hr> <hr />
<div class="d-flex mb-2"> <div class="d-flex mb-2">
<button type="submit" class="btn btn-primary btn-block btn-submit" <button
[disabled]="form.loading"> type="submit"
<span>{{'submit' | i18n}}</span> class="btn btn-primary btn-block btn-submit"
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" [disabled]="form.loading"
aria-hidden="true"></i> >
<span>{{ "submit" | i18n }}</span>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button> </button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0"> <a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}} {{ "cancel" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,64 +1,81 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { import { ActivatedRoute, Router } from "@angular/router";
ActivatedRoute,
Router,
} from '@angular/router';
import { first } from 'rxjs/operators'; import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.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 { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { RegisterComponent as BaseRegisterComponent } from 'jslib-angular/components/register.component'; import { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component";
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions'; import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
import { Policy } from 'jslib-common/models/domain/policy'; import { Policy } from "jslib-common/models/domain/policy";
import { PolicyData } from 'jslib-common/models/data/policyData'; import { PolicyData } from "jslib-common/models/data/policyData";
import { ReferenceEventRequest } from 'jslib-common/models/request/referenceEventRequest'; import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest";
@Component({ @Component({
selector: 'app-register', selector: "app-register",
templateUrl: 'register.component.html', templateUrl: "register.component.html",
}) })
export class RegisterComponent extends BaseRegisterComponent { export class RegisterComponent extends BaseRegisterComponent {
showCreateOrgMessage = false; showCreateOrgMessage = false;
layout = ''; layout = "";
enforcedPolicyOptions: MasterPasswordPolicyOptions; enforcedPolicyOptions: MasterPasswordPolicyOptions;
private policies: Policy[]; private policies: Policy[];
constructor(authService: AuthService, router: Router, constructor(
i18nService: I18nService, cryptoService: CryptoService, authService: AuthService,
apiService: ApiService, private route: ActivatedRoute, router: Router,
stateService: StateService, platformUtilsService: PlatformUtilsService, i18nService: I18nService,
passwordGenerationService: PasswordGenerationService, private policyService: PolicyService, cryptoService: CryptoService,
environmentService: EnvironmentService, logService: LogService) { apiService: ApiService,
super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService, private route: ActivatedRoute,
passwordGenerationService, environmentService, logService); stateService: StateService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
private policyService: PolicyService,
environmentService: EnvironmentService,
logService: LogService
) {
super(
authService,
router,
i18nService,
cryptoService,
apiService,
stateService,
platformUtilsService,
passwordGenerationService,
environmentService,
logService
);
} }
async ngOnInit() { async ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(qParams => { this.route.queryParams.pipe(first()).subscribe((qParams) => {
this.referenceData = new ReferenceEventRequest(); this.referenceData = new ReferenceEventRequest();
if (qParams.email != null && qParams.email.indexOf('@') > -1) { if (qParams.email != null && qParams.email.indexOf("@") > -1) {
this.email = qParams.email; this.email = qParams.email;
} }
if (qParams.premium != null) { if (qParams.premium != null) {
this.stateService.setLoginRedirect({ route: '/settings/premium' }); this.stateService.setLoginRedirect({ route: "/settings/premium" });
} else if (qParams.org != null) { } else if (qParams.org != null) {
this.showCreateOrgMessage = true; this.showCreateOrgMessage = true;
this.referenceData.flow = qParams.org; this.referenceData.flow = qParams.org;
this.stateService.setLoginRedirect( this.stateService.setLoginRedirect({
{ route: '/settings/create-organization', qParams: { plan: qParams.org } }); route: "/settings/create-organization",
qParams: { plan: qParams.org },
});
} }
if (qParams.layout != null) { if (qParams.layout != null) {
this.layout = this.referenceData.layout = qParams.layout; this.layout = this.referenceData.layout = qParams.layout;
@@ -66,28 +83,36 @@ export class RegisterComponent extends BaseRegisterComponent {
if (qParams.reference != null) { if (qParams.reference != null) {
this.referenceData.id = qParams.reference; this.referenceData.id = qParams.reference;
} else { } else {
this.referenceData.id = ('; ' + document.cookie).split('; reference=').pop().split(';').shift(); this.referenceData.id = ("; " + document.cookie)
.split("; reference=")
.pop()
.split(";")
.shift();
} }
// Are they coming from an email for sponsoring a families organization // Are they coming from an email for sponsoring a families organization
if (qParams.sponsorshipToken != null) { if (qParams.sponsorshipToken != null) {
// After logging in redirect them to setup the families sponsorship // After logging in redirect them to setup the families sponsorship
this.stateService.setLoginRedirect({ this.stateService.setLoginRedirect({
route: '/setup/families-for-enterprise', route: "/setup/families-for-enterprise",
qParams: { token: qParams.sponsorshipToken }, qParams: { token: qParams.sponsorshipToken },
}); });
} }
if (this.referenceData.id === '') { if (this.referenceData.id === "") {
this.referenceData.id = null; this.referenceData.id = null;
} }
}); });
const invite = await this.stateService.getOrganizationInvitation(); const invite = await this.stateService.getOrganizationInvitation();
if (invite != null) { if (invite != null) {
try { try {
const policies = await this.apiService.getPoliciesByToken(invite.organizationId, invite.token, const policies = await this.apiService.getPoliciesByToken(
invite.email, invite.organizationUserId); invite.organizationId,
invite.token,
invite.email,
invite.organizationUserId
);
if (policies.data != null) { if (policies.data != null) {
const policiesData = policies.data.map(p => new PolicyData(p)); const policiesData = policies.data.map((p) => new PolicyData(p));
this.policies = policiesData.map(p => new Policy(p)); this.policies = policiesData.map((p) => new Policy(p));
} }
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
@@ -95,18 +120,28 @@ export class RegisterComponent extends BaseRegisterComponent {
} }
if (this.policies != null) { if (this.policies != null) {
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(this.policies); this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(
this.policies
);
} }
await super.ngOnInit(); await super.ngOnInit();
} }
async submit() { async submit() {
if (this.enforcedPolicyOptions != null && if (
!this.policyService.evaluateMasterPassword(this.masterPasswordScore, this.masterPassword, this.enforcedPolicyOptions != null &&
this.enforcedPolicyOptions)) { !this.policyService.evaluateMasterPassword(
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.masterPasswordScore,
this.i18nService.t('masterPasswordPolicyRequirementsNotMet')); this.masterPassword,
this.enforcedPolicyOptions
)
) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPasswordPolicyRequirementsNotMet")
);
return; return;
} }

View File

@@ -1,28 +1,52 @@
<div class="mt-5 d-flex justify-content-center" *ngIf="loading"> <div class="mt-5 d-flex justify-content-center" *ngIf="loading">
<div> <div>
<img class="mb-4 logo logo-themed" alt="Bitwarden"> <img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center"> <p class="text-center">
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'loading' | i18n}}</span> class="fa fa-spinner fa-spin fa-2x text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p> </p>
</div> </div>
</div> </div>
<div class="container" *ngIf="!loading"> <div class="container" *ngIf="!loading">
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5"> <div class="col-5">
<p class="lead text-center mb-4">{{'removeMasterPassword' | i18n}}</p> <p class="lead text-center mb-4">{{ "removeMasterPassword" | i18n }}</p>
<hr> <hr />
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">
<p>{{'convertOrganizationEncryptionDesc' | i18n : organization.name}}</p> <p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
<button type="button" class="btn btn-primary btn-block" (click)="convert()" [disabled]="actionPromise"> <button
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="continuing"></i> type="button"
{{'removeMasterPassword' | i18n}} class="btn btn-primary btn-block"
(click)="convert()"
[disabled]="actionPromise"
>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
*ngIf="continuing"
></i>
{{ "removeMasterPassword" | i18n }}
</button> </button>
<button type="button" class="btn btn-outline-secondary btn-block" (click)="leave()" [disabled]="actionPromise"> <button
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="leaving"></i> type="button"
{{'leaveOrganization' | i18n}} class="btn btn-outline-secondary btn-block"
(click)="leave()"
[disabled]="actionPromise"
>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
*ngIf="leaving"
></i>
{{ "leaveOrganization" | i18n }}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,10 +1,9 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { RemovePasswordComponent as BaseRemovePasswordComponent } from 'jslib-angular/components/remove-password.component'; import { RemovePasswordComponent as BaseRemovePasswordComponent } from "jslib-angular/components/remove-password.component";
@Component({ @Component({
selector: 'app-remove-password', selector: "app-remove-password",
templateUrl: 'remove-password.component.html', templateUrl: "remove-password.component.html",
}) })
export class RemovePasswordComponent extends BaseRemovePasswordComponent { export class RemovePasswordComponent extends BaseRemovePasswordComponent {}
}

View File

@@ -1,69 +1,113 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5"> <div class="col-5">
<p class="lead text-center mb-4">{{'setMasterPassword' | i18n}}</p> <p class="lead text-center mb-4">{{ "setMasterPassword" | i18n }}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body text-center" *ngIf="syncLoading"> <div class="card-body text-center" *ngIf="syncLoading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{'loading' | i18n}} {{ "loading" | i18n }}
</div> </div>
<div class="card-body" *ngIf="!syncLoading"> <div class="card-body" *ngIf="!syncLoading">
<app-callout type="info">{{'ssoCompleteRegistration' | i18n}}</app-callout> <app-callout type="info">{{ "ssoCompleteRegistration" | i18n }}</app-callout>
<app-callout type="warning" title="{{'resetPasswordPolicyAutoEnroll' | i18n}}" <app-callout
*ngIf="resetPasswordAutoEnroll"> type="warning"
{{'resetPasswordAutoEnrollInviteWarning' | i18n}} title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout> </app-callout>
<div class="form-group"> <div class="form-group">
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" <app-callout
*ngIf="enforcedPolicyOptions"> type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout> </app-callout>
<label for="masterPassword">{{'masterPass' | i18n}}</label> <label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex"> <div class="d-flex">
<div class="w-100"> <div class="w-100">
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPasswordHash" class="text-monospace form-control mb-1" id="masterPassword"
[(ngModel)]="masterPassword" (input)="updatePasswordStrength()" required type="{{ showPassword ? 'text' : 'password' }}"
appInputVerbatim> name="MasterPasswordHash"
class="text-monospace form-control mb-1"
[(ngModel)]="masterPassword"
(input)="updatePasswordStrength()"
required
appInputVerbatim
/>
<app-password-strength [score]="masterPasswordScore" [showText]="true"> <app-password-strength [score]="masterPasswordScore" [showText]="true">
</app-password-strength> </app-password-strength>
</div> </div>
<div> <div>
<button type="button" class="ml-1 btn btn-link" <button
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)"> type="button"
<i class="fa fa-lg" aria-hidden="true" class="ml-1 btn btn-link"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
<div class="progress-bar invisible"></div> <div class="progress-bar invisible"></div>
</div> </div>
</div> </div>
<small class="form-text text-muted">{{'masterPassDesc' | i18n}}</small> <small class="form-text text-muted">{{ "masterPassDesc" | i18n }}</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label> <label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<div class="d-flex"> <div class="d-flex">
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPasswordRetype" class="text-monospace form-control" id="masterPasswordRetype"
[(ngModel)]="masterPasswordRetype" required appInputVerbatim> type="{{ showPassword ? 'text' : 'password' }}"
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}" name="MasterPasswordRetype"
(click)="togglePassword(true)"> class="text-monospace form-control"
<i class="fa fa-lg" aria-hidden="true" [(ngModel)]="masterPasswordRetype"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> required
appInputVerbatim
/>
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="hint">{{'masterPassHint' | i18n}}</label> <label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint"> <input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint" />
<small class="form-text text-muted">{{'masterPassHintDesc' | i18n}}</small> <small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
</div> </div>
<hr> <hr />
<div class="d-flex"> <div class="d-flex">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading"> <button
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> type="submit"
<span>{{'submit' | i18n}}</span> class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "submit" | i18n }}</span>
</button> </button>
<button type="button" class="btn btn-outline-secondary btn-block ml-2 mt-0" (click)="logOut()"> <button
{{'logOut' | i18n}} type="button"
class="btn btn-outline-secondary btn-block ml-2 mt-0"
(click)="logOut()"
>
{{ "logOut" | i18n }}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,34 +1,48 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { import { ActivatedRoute, Router } from "@angular/router";
ActivatedRoute,
Router,
} from '@angular/router';
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";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; 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 { 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 { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from "jslib-common/abstractions/sync.service";
import { import { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component";
SetPasswordComponent as BaseSetPasswordComponent,
} from 'jslib-angular/components/set-password.component';
@Component({ @Component({
selector: 'app-set-password', selector: "app-set-password",
templateUrl: 'set-password.component.html', templateUrl: "set-password.component.html",
}) })
export class SetPasswordComponent extends BaseSetPasswordComponent { export class SetPasswordComponent extends BaseSetPasswordComponent {
constructor(apiService: ApiService, i18nService: I18nService, constructor(
cryptoService: CryptoService, messagingService: MessagingService, apiService: ApiService,
passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService, i18nService: I18nService,
policyService: PolicyService, router: Router, cryptoService: CryptoService,
syncService: SyncService, route: ActivatedRoute, stateService: StateService) { messagingService: MessagingService,
super(i18nService, cryptoService, messagingService, passwordGenerationService, passwordGenerationService: PasswordGenerationService,
platformUtilsService, policyService, router, apiService, syncService, route, stateService); platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
router: Router,
syncService: SyncService,
route: ActivatedRoute,
stateService: StateService
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyService,
router,
apiService,
syncService,
route,
stateService
);
} }
} }

View File

@@ -1,29 +1,48 @@
<form #form (ngSubmit)="submit()" class="container" [appApiAction]="initiateSsoFormPromise" ngNativeValidate> <form
#form
(ngSubmit)="submit()"
class="container"
[appApiAction]="initiateSsoFormPromise"
ngNativeValidate
>
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5"> <div class="col-5">
<img class="logo mb-2 logo-themed" alt="Bitwarden"> <img class="logo mb-2 logo-themed" alt="Bitwarden" />
<div class="card d-block mt-4"> <div class="card d-block mt-4">
<div class="card-body" *ngIf="loggingIn"> <div class="card-body" *ngIf="loggingIn">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{'loading' | i18n}} {{ "loading" | i18n }}
</div> </div>
<div class="card-body" *ngIf="!loggingIn"> <div class="card-body" *ngIf="!loggingIn">
<p>{{'ssoLogInWithOrgIdentifier' | i18n}}</p> <p>{{ "ssoLogInWithOrgIdentifier" | i18n }}</p>
<div class="form-group"> <div class="form-group">
<label for="identifier">{{'organizationIdentifier' | i18n}}</label> <label for="identifier">{{ "organizationIdentifier" | i18n }}</label>
<input id="identifier" class="form-control" type="text" name="Identifier" <input
[(ngModel)]="identifier" required appAutofocus> id="identifier"
class="form-control"
type="text"
name="Identifier"
[(ngModel)]="identifier"
required
appAutofocus
/>
</div> </div>
<hr> <hr />
<div class="d-flex"> <div class="d-flex">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading"> <button
<span> type="submit"
<i class="fa fa-sign-in" aria-hidden="true"></i> {{'logIn' | i18n}} class="btn btn-primary btn-block btn-submit"
</span> [disabled]="form.loading"
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> >
<span> <i class="fa fa-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }} </span>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button> </button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0"> <a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}} {{ "cancel" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,43 +1,58 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { import { ActivatedRoute, Router } from "@angular/router";
ActivatedRoute,
Router,
} from '@angular/router';
import { first } from 'rxjs/operators'; import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.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 { StateService } from "jslib-common/abstractions/state.service";
import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.component'; import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
@Component({ @Component({
selector: 'app-sso', selector: "app-sso",
templateUrl: 'sso.component.html', templateUrl: "sso.component.html",
}) })
export class SsoComponent extends BaseSsoComponent { export class SsoComponent extends BaseSsoComponent {
constructor(authService: AuthService, router: Router, constructor(
i18nService: I18nService, route: ActivatedRoute, authService: AuthService,
stateService: StateService, platformUtilsService: PlatformUtilsService, router: Router,
apiService: ApiService, cryptoFunctionService: CryptoFunctionService, i18nService: I18nService,
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService, route: ActivatedRoute,
logService: LogService) { stateService: StateService,
super(authService, router, i18nService, route, stateService, platformUtilsService, platformUtilsService: PlatformUtilsService,
apiService, cryptoFunctionService, environmentService, passwordGenerationService, logService); apiService: ApiService,
this.redirectUri = window.location.origin + '/sso-connector.html'; cryptoFunctionService: CryptoFunctionService,
this.clientId = 'web'; environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService,
logService: LogService
) {
super(
authService,
router,
i18nService,
route,
stateService,
platformUtilsService,
apiService,
cryptoFunctionService,
environmentService,
passwordGenerationService,
logService
);
this.redirectUri = window.location.origin + "/sso-connector.html";
this.clientId = "web";
} }
async ngOnInit() { async ngOnInit() {
super.ngOnInit(); super.ngOnInit();
this.route.queryParams.pipe(first()).subscribe(async qParams => { this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.identifier != null) { if (qParams.identifier != null) {
this.identifier = qParams.identifier; this.identifier = qParams.identifier;
} else { } else {
@@ -51,8 +66,8 @@ export class SsoComponent extends BaseSsoComponent {
async submit() { async submit() {
await this.stateService.setSsoOrganizationIdentifier(this.identifier); await this.stateService.setSsoOrganizationIdentifier(this.identifier);
if (this.clientId === 'browser') { if (this.clientId === "browser") {
document.cookie = `ssoHandOffMessage=${this.i18nService.t('ssoHandOff')};SameSite=strict`; document.cookie = `ssoHandOffMessage=${this.i18nService.t("ssoHandOff")};SameSite=strict`;
} }
super.submit(); super.submit();
} }

View File

@@ -2,8 +2,13 @@
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document"> <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h2 class="modal-title" id="twoStepOptionsTitle">{{'twoStepOptions' | i18n}}</h2> <h2 class="modal-title" id="twoStepOptionsTitle">{{ "twoStepOptions" | i18n }}</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}"> <button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
@@ -12,16 +17,20 @@
<div *ngFor="let p of providers" class="list-group-item list-group-item-action"> <div *ngFor="let p of providers" class="list-group-item list-group-item-action">
<div class="two-factor-content"> <div class="two-factor-content">
<div class="logo-col"> <div class="logo-col">
<img [class]="'mfaType' + p.type" [alt]="p.name + ' logo'"> <img [class]="'mfaType' + p.type" [alt]="p.name + ' logo'" />
</div> </div>
<div class="text-col"> <div class="text-col">
<h3>{{p.name}}</h3> <h3>{{ p.name }}</h3>
{{p.description}} {{ p.description }}
</div> </div>
<div class="btn-col"> <div class="btn-col">
<button [attr.aria-describedby]="p.name" type="button" <button
class="btn btn-outline-secondary btn-sm" (click)="choose(p)"> [attr.aria-describedby]="p.name"
{{'select' | i18n}} type="button"
class="btn btn-outline-secondary btn-sm"
(click)="choose(p)"
>
{{ "select" | i18n }}
</button> </button>
</div> </div>
</div> </div>
@@ -29,16 +38,20 @@
<div class="list-group-item list-group-item-action" (click)="recover()"> <div class="list-group-item list-group-item-action" (click)="recover()">
<div class="two-factor-content"> <div class="two-factor-content">
<div class="logo-col"> <div class="logo-col">
<img class="recovery-code-img" alt="rc logo"> <img class="recovery-code-img" alt="rc logo" />
</div> </div>
<div class="text-col"> <div class="text-col">
<h3>{{'recoveryCodeTitle' | i18n}}</h3> <h3>{{ "recoveryCodeTitle" | i18n }}</h3>
{{'recoveryCodeDesc' | i18n}} {{ "recoveryCodeDesc" | i18n }}
</div> </div>
<div class="btn-col"> <div class="btn-col">
<button [attr.aria-descibedby]="'recoveryCodeTitle' | i18n" type="button" <button
class="btn btn-outline-secondary btn-sm" (click)="recover()"> [attr.aria-descibedby]="'recoveryCodeTitle' | i18n"
{{'select' | i18n}} type="button"
class="btn btn-outline-secondary btn-sm"
(click)="recover()"
>
{{ "select" | i18n }}
</button> </button>
</div> </div>
</div> </div>
@@ -46,8 +59,9 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
i18n}}</button> {{ "close" | i18n }}
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,21 +1,23 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { Router } from '@angular/router'; import { Router } from "@angular/router";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.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 { import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "jslib-angular/components/two-factor-options.component";
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
} from 'jslib-angular/components/two-factor-options.component';
@Component({ @Component({
selector: 'app-two-factor-options', selector: "app-two-factor-options",
templateUrl: 'two-factor-options.component.html', templateUrl: "two-factor-options.component.html",
}) })
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent { export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
constructor(authService: AuthService, router: Router, constructor(
i18nService: I18nService, platformUtilsService: PlatformUtilsService) { authService: AuthService,
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService
) {
super(authService, router, i18nService, platformUtilsService, window); super(authService, router, i18nService, platformUtilsService, window);
} }
} }

View File

@@ -1,36 +1,77 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate autocomplete="off"> <form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
class="container"
ngNativeValidate
autocomplete="off"
>
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5" <div
[ngClass]="{'col-9': selectedProviderType === providerType.Duo || selectedProviderType === providerType.OrganizationDuo}"> class="col-5"
<p class="lead text-center mb-4">{{title}}</p> [ngClass]="{
'col-9':
selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo
}"
>
<p class="lead text-center mb-4">{{ title }}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">
<ng-container <ng-container
*ngIf="selectedProviderType === providerType.Email || selectedProviderType === providerType.Authenticator"> *ngIf="
selectedProviderType === providerType.Email ||
selectedProviderType === providerType.Authenticator
"
>
<p *ngIf="selectedProviderType === providerType.Authenticator"> <p *ngIf="selectedProviderType === providerType.Authenticator">
{{'enterVerificationCodeApp' | i18n}}</p> {{ "enterVerificationCodeApp" | i18n }}
</p>
<p *ngIf="selectedProviderType === providerType.Email"> <p *ngIf="selectedProviderType === providerType.Email">
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}} {{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }}
</p> </p>
<div class="form-group"> <div class="form-group">
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label> <label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
<input id="code" type="text" name="Code" class="form-control" [(ngModel)]="token" required <input
appAutofocus inputmode="tel" appInputVerbatim> id="code"
type="text"
name="Code"
class="form-control"
[(ngModel)]="token"
required
appAutofocus
inputmode="tel"
appInputVerbatim
/>
<small class="form-text" *ngIf="selectedProviderType === providerType.Email"> <small class="form-text" *ngIf="selectedProviderType === providerType.Email">
<a href="#" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise" <a
*ngIf="selectedProviderType === providerType.Email"> href="#"
{{'sendVerificationCodeEmailAgain' | i18n}} appStopClick
(click)="sendEmail(true)"
[appApiAction]="emailPromise"
*ngIf="selectedProviderType === providerType.Email"
>
{{ "sendVerificationCodeEmailAgain" | i18n }}
</a> </a>
</small> </small>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey"> <ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<p class="text-center">{{'insertYubiKey' | i18n}}</p> <p class="text-center">{{ "insertYubiKey" | i18n }}</p>
<img src="../../images/yubikey.jpg" class="rounded img-fluid mb-3" alt=""> <img src="../../images/yubikey.jpg" class="rounded img-fluid mb-3" alt="" />
<div class="form-group"> <div class="form-group">
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label> <label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
<input id="code" type="password" name="Code" class="form-control" [(ngModel)]="token" <input
required appAutofocus appInputVerbatim autocomplete="new-password"> id="code"
type="password"
name="Code"
class="form-control"
[(ngModel)]="token"
required
appAutofocus
appInputVerbatim
autocomplete="new-password"
/>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn"> <ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
@@ -38,39 +79,66 @@
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe> <iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Duo || <ng-container
selectedProviderType === providerType.OrganizationDuo"> *ngIf="
selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo
"
>
<div id="duo-frame" class="mb-3"> <div id="duo-frame" class="mb-3">
<iframe id="duo_iframe"></iframe> <iframe id="duo_iframe"></iframe>
</div> </div>
</ng-container> </ng-container>
<i class="fa fa-spinner text-muted fa-spin pull-right" title="{{'loading' | i18n}}" <i
*ngIf="form.loading && selectedProviderType === providerType.WebAuthn" aria-hidden="true"></i> class="fa fa-spinner text-muted fa-spin pull-right"
title="{{ 'loading' | i18n }}"
*ngIf="form.loading && selectedProviderType === providerType.WebAuthn"
aria-hidden="true"
></i>
<div class="form-check" *ngIf="selectedProviderType != null"> <div class="form-check" *ngIf="selectedProviderType != null">
<input id="remember" type="checkbox" name="Remember" class="form-check-input" <input
[(ngModel)]="remember"> id="remember"
<label for="remember" class="form-check-label">{{'rememberMe' | i18n}}</label> type="checkbox"
name="Remember"
class="form-check-input"
[(ngModel)]="remember"
/>
<label for="remember" class="form-check-label">{{ "rememberMe" | i18n }}</label>
</div> </div>
<ng-container *ngIf="selectedProviderType == null"> <ng-container *ngIf="selectedProviderType == null">
<p>{{'noTwoStepProviders' | i18n}}</p> <p>{{ "noTwoStepProviders" | i18n }}</p>
<p>{{'noTwoStepProviders2' | i18n}}</p> <p>{{ "noTwoStepProviders2" | i18n }}</p>
</ng-container> </ng-container>
<hr> <hr />
<div class="d-flex mb-3"> <div class="d-flex mb-3">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading" <button
*ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo && type="submit"
selectedProviderType !== providerType.OrganizationDuo && selectedProviderType !== providerType.WebAuthn"> class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
*ngIf="
selectedProviderType != null &&
selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo &&
selectedProviderType !== providerType.WebAuthn
"
>
<span> <span>
<i class="fa fa-sign-in" aria-hidden="true"></i> {{'continue' | i18n}} <i class="fa fa-sign-in" aria-hidden="true"></i> {{ "continue" | i18n }}
</span> </span>
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button> </button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0"> <a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}} {{ "cancel" | i18n }}
</a> </a>
</div> </div>
<div class="text-center"> <div class="text-center">
<a href="#" appStopClick (click)="anotherMethod()">{{'useAnotherTwoStepMethod' | i18n}}</a> <a href="#" appStopClick (click)="anotherMethod()">{{
"useAnotherTwoStepMethod" | i18n
}}</a>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,49 +1,63 @@
import { import { Component, ViewChild, ViewContainerRef } from "@angular/core";
Component,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { import { ActivatedRoute, Router } from "@angular/router";
ActivatedRoute,
Router,
} from '@angular/router';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.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 { ModalService } from 'jslib-angular/services/modal.service'; import { ModalService } from "jslib-angular/services/modal.service";
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType'; import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib-angular/components/two-factor.component'; import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
import { TwoFactorOptionsComponent } from './two-factor-options.component'; import { TwoFactorOptionsComponent } from "./two-factor-options.component";
@Component({ @Component({
selector: 'app-two-factor', selector: "app-two-factor",
templateUrl: 'two-factor.component.html', templateUrl: "two-factor.component.html",
}) })
export class TwoFactorComponent extends BaseTwoFactorComponent { export class TwoFactorComponent extends BaseTwoFactorComponent {
@ViewChild('twoFactorOptions', { read: ViewContainerRef, static: true }) twoFactorOptionsModal: ViewContainerRef; @ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true })
twoFactorOptionsModal: ViewContainerRef;
constructor(authService: AuthService, router: Router, constructor(
i18nService: I18nService, apiService: ApiService, authService: AuthService,
platformUtilsService: PlatformUtilsService, stateService: StateService, router: Router,
environmentService: EnvironmentService, private modalService: ModalService, i18nService: I18nService,
route: ActivatedRoute, logService: LogService) { apiService: ApiService,
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService, platformUtilsService: PlatformUtilsService,
stateService, route, logService); stateService: StateService,
environmentService: EnvironmentService,
private modalService: ModalService,
route: ActivatedRoute,
logService: LogService
) {
super(
authService,
router,
i18nService,
apiService,
platformUtilsService,
window,
environmentService,
stateService,
route,
logService
);
this.onSuccessfulLoginNavigate = this.goAfterLogIn; this.onSuccessfulLoginNavigate = this.goAfterLogIn;
} }
async anotherMethod() { async anotherMethod() {
const [modal] = await this.modalService.openViewRef(TwoFactorOptionsComponent, this.twoFactorOptionsModal, comp => { const [modal] = await this.modalService.openViewRef(
TwoFactorOptionsComponent,
this.twoFactorOptionsModal,
(comp) => {
comp.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => { comp.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close(); modal.close();
this.selectedProviderType = provider; this.selectedProviderType = provider;
@@ -52,7 +66,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
comp.onRecoverSelected.subscribe(() => { comp.onRecoverSelected.subscribe(() => {
modal.close(); modal.close();
}); });
}); }
);
} }
async goAfterLogIn() { async goAfterLogIn() {

View File

@@ -1,61 +1,101 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off"> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-4"> <div class="col-4">
<p class="lead text-center mb-4">{{'updateMasterPassword' | i18n}}</p> <p class="lead text-center mb-4">{{ "updateMasterPassword" | i18n }}</p>
<div class="card d-block"> <div class="card d-block">
<div class="card-body"> <div class="card-body">
<app-callout type="warning">{{'updateMasterPasswordWarning' | i18n}} <app-callout type="warning">{{ "updateMasterPasswordWarning" | i18n }} </app-callout>
</app-callout>
<div class="form-group"> <div class="form-group">
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" <app-callout
*ngIf="enforcedPolicyOptions"> type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout> </app-callout>
<label for="masterPassword">{{'masterPass' | i18n}}</label> <label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex"> <div class="d-flex">
<div class="w-100"> <div class="w-100">
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPasswordHash" class="text-monospace form-control mb-1" id="masterPassword"
[(ngModel)]="masterPassword" (input)="updatePasswordStrength()" required type="{{ showPassword ? 'text' : 'password' }}"
appInputVerbatim> name="MasterPasswordHash"
class="text-monospace form-control mb-1"
[(ngModel)]="masterPassword"
(input)="updatePasswordStrength()"
required
appInputVerbatim
/>
<app-password-strength [score]="masterPasswordScore" [showText]="true"> <app-password-strength [score]="masterPasswordScore" [showText]="true">
</app-password-strength> </app-password-strength>
</div> </div>
<div> <div>
<button type="button" class="ml-1 btn btn-link" <button
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)"> type="button"
<i class="fa fa-lg" aria-hidden="true" class="ml-1 btn btn-link"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
<div class="progress-bar invisible"></div> <div class="progress-bar invisible"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label> <label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<div class="d-flex"> <div class="d-flex">
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPasswordRetype" class="text-monospace form-control" id="masterPasswordRetype"
[(ngModel)]="masterPasswordRetype" required appInputVerbatim> type="{{ showPassword ? 'text' : 'password' }}"
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}" name="MasterPasswordRetype"
(click)="togglePassword(true)"> class="text-monospace form-control"
<i class="fa fa-lg" aria-hidden="true" [(ngModel)]="masterPasswordRetype"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> required
appInputVerbatim
/>
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button> </button>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="hint">{{'masterPassHint' | i18n}}</label> <label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint"> <input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint" />
<small class="form-text text-muted">{{'masterPassHintDesc' | i18n}}</small> <small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
</div> </div>
<hr> <hr />
<div class="d-flex"> <div class="d-flex">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading"> <button
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> type="submit"
<span>{{'submit' | i18n}}</span> class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "submit" | i18n }}</span>
</button> </button>
<button type="button" class="btn btn-outline-secondary btn-block ml-2 mt-0" (click)="logOut()"> <button
{{'logOut' | i18n}} type="button"
class="btn btn-outline-secondary btn-block ml-2 mt-0"
(click)="logOut()"
>
{{ "logOut" | i18n }}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,29 +1,46 @@
import { Component } from '@angular/core'; import { Component } 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";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; 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 { 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 { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from "jslib-common/abstractions/sync.service";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from 'jslib-angular/components/update-temp-password.component'; import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "jslib-angular/components/update-temp-password.component";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
@Component({ @Component({
selector: 'app-update-temp-password', selector: "app-update-temp-password",
templateUrl: 'update-temp-password.component.html', templateUrl: "update-temp-password.component.html",
}) })
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent { export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, constructor(
passwordGenerationService: PasswordGenerationService, policyService: PolicyService, i18nService: I18nService,
cryptoService: CryptoService, messagingService: MessagingService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, logService: LogService, stateService: StateService, syncService: SyncService) { passwordGenerationService: PasswordGenerationService,
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService, policyService: PolicyService,
messagingService, apiService, stateService, syncService, logService); cryptoService: CryptoService,
messagingService: MessagingService,
apiService: ApiService,
logService: LogService,
stateService: StateService,
syncService: SyncService
) {
super(
i18nService,
platformUtilsService,
passwordGenerationService,
policyService,
cryptoService,
messagingService,
apiService,
stateService,
syncService,
logService
);
} }
} }

View File

@@ -1,9 +1,13 @@
<div class="mt-5 d-flex justify-content-center"> <div class="mt-5 d-flex justify-content-center">
<div> <div>
<img class="mb-4 logo logo-themed" alt="Bitwarden"> <img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center"> <p class="text-center">
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'loading' | i18n}}</span> class="fa fa-spinner fa-spin fa-2x text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p> </p>
</div> </div>
</div> </div>

View File

@@ -1,25 +1,19 @@
import { import { Component, OnInit } from "@angular/core";
Component, import { ActivatedRoute, Router } from "@angular/router";
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { first } from 'rxjs/operators'; import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.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 { VerifyEmailRequest } from 'jslib-common/models/request/verifyEmailRequest'; import { VerifyEmailRequest } from "jslib-common/models/request/verifyEmailRequest";
@Component({ @Component({
selector: 'app-verify-email-token', selector: "app-verify-email-token",
templateUrl: 'verify-email-token.component.html', templateUrl: "verify-email-token.component.html",
}) })
export class VerifyEmailTokenComponent implements OnInit { export class VerifyEmailTokenComponent implements OnInit {
constructor( constructor(
@@ -30,26 +24,27 @@ export class VerifyEmailTokenComponent implements OnInit {
private apiService: ApiService, private apiService: ApiService,
private logService: LogService, private logService: LogService,
private stateService: StateService private stateService: StateService
) { } ) {}
ngOnInit() { ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => { this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.userId != null && qParams.token != null) { if (qParams.userId != null && qParams.token != null) {
try { try {
await this.apiService.postAccountVerifyEmailToken( await this.apiService.postAccountVerifyEmailToken(
new VerifyEmailRequest(qParams.userId, qParams.token)); new VerifyEmailRequest(qParams.userId, qParams.token)
);
if (await this.stateService.getIsAuthenticated()) { if (await this.stateService.getIsAuthenticated()) {
await this.apiService.refreshIdentityToken(); await this.apiService.refreshIdentityToken();
} }
this.platformUtilsService.showToast('success', null, this.i18nService.t('emailVerified')); this.platformUtilsService.showToast("success", null, this.i18nService.t("emailVerified"));
this.router.navigate(['/']); this.router.navigate(["/"]);
return; return;
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
} }
this.platformUtilsService.showToast('error', null, this.i18nService.t('emailVerifiedFailed')); this.platformUtilsService.showToast("error", null, this.i18nService.t("emailVerifiedFailed"));
this.router.navigate(['/']); this.router.navigate(["/"]);
}); });
} }
} }

View File

@@ -1,22 +1,30 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate> <form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5"> <div class="row justify-content-md-center mt-5">
<div class="col-5"> <div class="col-5">
<p class="lead text-center mb-4">{{'deleteAccount' | i18n}}</p> <p class="lead text-center mb-4">{{ "deleteAccount" | i18n }}</p>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<app-callout type="warning">{{'deleteAccountWarning' | i18n}}</app-callout> <app-callout type="warning">{{ "deleteAccountWarning" | i18n }}</app-callout>
<p class="text-center"> <p class="text-center">
<strong>{{email}}</strong> <strong>{{ email }}</strong>
</p> </p>
<p>{{'deleteRecoverConfirmDesc' | i18n}}</p> <p>{{ "deleteRecoverConfirmDesc" | i18n }}</p>
<hr> <hr />
<div class="d-flex"> <div class="d-flex">
<button type="submit" class="btn btn-danger btn-block btn-submit" [disabled]="form.loading"> <button
<span>{{'deleteAccount' | i18n}}</span> type="submit"
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i> class="btn btn-danger btn-block btn-submit"
[disabled]="form.loading"
>
<span>{{ "deleteAccount" | i18n }}</span>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button> </button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0"> <a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}} {{ "cancel" | i18n }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,24 +1,18 @@
import { import { Component, OnInit } from "@angular/core";
Component, import { ActivatedRoute, Router } from "@angular/router";
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { first } from 'rxjs/operators'; import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { VerifyDeleteRecoverRequest } from 'jslib-common/models/request/verifyDeleteRecoverRequest'; import { VerifyDeleteRecoverRequest } from "jslib-common/models/request/verifyDeleteRecoverRequest";
@Component({ @Component({
selector: 'app-verify-recover-delete', selector: "app-verify-recover-delete",
templateUrl: 'verify-recover-delete.component.html', templateUrl: "verify-recover-delete.component.html",
}) })
export class VerifyRecoverDeleteComponent implements OnInit { export class VerifyRecoverDeleteComponent implements OnInit {
email: string; email: string;
@@ -27,19 +21,23 @@ export class VerifyRecoverDeleteComponent implements OnInit {
private userId: string; private userId: string;
private token: string; private token: string;
constructor(private router: Router, private apiService: ApiService, constructor(
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private router: Router,
private route: ActivatedRoute, private logService: LogService) { private apiService: ApiService,
} private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private route: ActivatedRoute,
private logService: LogService
) {}
ngOnInit() { ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => { this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.userId != null && qParams.token != null && qParams.email != null) { if (qParams.userId != null && qParams.token != null && qParams.email != null) {
this.userId = qParams.userId; this.userId = qParams.userId;
this.token = qParams.token; this.token = qParams.token;
this.email = qParams.email; this.email = qParams.email;
} else { } else {
this.router.navigate(['/']); this.router.navigate(["/"]);
} }
}); });
} }
@@ -49,9 +47,12 @@ export class VerifyRecoverDeleteComponent implements OnInit {
const request = new VerifyDeleteRecoverRequest(this.userId, this.token); const request = new VerifyDeleteRecoverRequest(this.userId, this.token);
this.formPromise = this.apiService.postAccountRecoverDeleteToken(request); this.formPromise = this.apiService.postAccountRecoverDeleteToken(request);
await this.formPromise; await this.formPromise;
this.platformUtilsService.showToast('success', this.i18nService.t('accountDeleted'), this.platformUtilsService.showToast(
this.i18nService.t('accountDeletedDesc')); "success",
this.router.navigate(['/']); this.i18nService.t("accountDeleted"),
this.i18nService.t("accountDeletedDesc")
);
this.router.navigate(["/"]);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }

View File

@@ -1,62 +1,52 @@
import { import { Component, NgZone, OnDestroy, OnInit, SecurityContext } from "@angular/core";
Component, import { DomSanitizer } from "@angular/platform-browser";
NgZone, import { NavigationEnd, Router } from "@angular/router";
OnDestroy, import * as jq from "jquery";
OnInit, import { IndividualConfig, ToastrService } from "ngx-toastr";
SecurityContext, import Swal from "sweetalert2";
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import {
NavigationEnd,
Router,
} from '@angular/router';
import * as jq from 'jquery';
import { IndividualConfig, ToastrService } from 'ngx-toastr';
import Swal from 'sweetalert2';
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from "jslib-common/abstractions/collection.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EventService } from 'jslib-common/abstractions/event.service'; import { EventService } from "jslib-common/abstractions/event.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 { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { NotificationsService } from 'jslib-common/abstractions/notifications.service'; import { NotificationsService } from "jslib-common/abstractions/notifications.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 { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from "jslib-common/abstractions/search.service";
import { SettingsService } from 'jslib-common/abstractions/settings.service'; import { SettingsService } from "jslib-common/abstractions/settings.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { StorageService } from 'jslib-common/abstractions/storage.service'; import { StorageService } from "jslib-common/abstractions/storage.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 { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { PolicyListService } from './services/policy-list.service'; import { PolicyListService } from "./services/policy-list.service";
import { RouterService } from './services/router.service'; import { RouterService } from "./services/router.service";
import { DisableSendPolicy } from './organizations/policies/disable-send.component'; import { DisableSendPolicy } from "./organizations/policies/disable-send.component";
import { MasterPasswordPolicy } from './organizations/policies/master-password.component'; import { MasterPasswordPolicy } from "./organizations/policies/master-password.component";
import { PasswordGeneratorPolicy } from './organizations/policies/password-generator.component'; import { PasswordGeneratorPolicy } from "./organizations/policies/password-generator.component";
import { PersonalOwnershipPolicy } from './organizations/policies/personal-ownership.component'; import { PersonalOwnershipPolicy } from "./organizations/policies/personal-ownership.component";
import { RequireSsoPolicy } from './organizations/policies/require-sso.component'; import { RequireSsoPolicy } from "./organizations/policies/require-sso.component";
import { ResetPasswordPolicy } from './organizations/policies/reset-password.component'; import { ResetPasswordPolicy } from "./organizations/policies/reset-password.component";
import { SendOptionsPolicy } from './organizations/policies/send-options.component'; import { SendOptionsPolicy } from "./organizations/policies/send-options.component";
import { SingleOrgPolicy } from './organizations/policies/single-org.component'; import { SingleOrgPolicy } from "./organizations/policies/single-org.component";
import { TwoFactorAuthenticationPolicy } from './organizations/policies/two-factor-authentication.component'; import { TwoFactorAuthenticationPolicy } from "./organizations/policies/two-factor-authentication.component";
const BroadcasterSubscriptionId = 'AppComponent'; const BroadcasterSubscriptionId = "AppComponent";
const IdleTimeout = 60000 * 10; // 10 minutes const IdleTimeout = 60000 * 10; // 10 minutes
@Component({ @Component({
selector: 'app-root', selector: "app-root",
templateUrl: 'app.component.html', templateUrl: "app.component.html",
}) })
export class AppComponent implements OnDestroy, OnInit { export class AppComponent implements OnDestroy, OnInit {
private lastActivity: number = null; private lastActivity: number = null;
private idleTimer: number = null; private idleTimer: number = null;
private isIdle = false; private isIdle = false;
@@ -87,7 +77,7 @@ export class AppComponent implements OnDestroy, OnInit {
private policyService: PolicyService, private policyService: PolicyService,
protected policyListService: PolicyListService, protected policyListService: PolicyListService,
private keyConnectorService: KeyConnectorService private keyConnectorService: KeyConnectorService
) { } ) {}
ngOnInit() { ngOnInit() {
this.ngZone.runOutsideAngular(() => { this.ngZone.runOutsideAngular(() => {
@@ -102,65 +92,80 @@ export class AppComponent implements OnDestroy, OnInit {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => { this.ngZone.run(async () => {
switch (message.command) { switch (message.command) {
case 'loggedIn': case "loggedIn":
case 'loggedOut': case "loggedOut":
case 'unlocked': case "unlocked":
this.notificationsService.updateConnection(false); this.notificationsService.updateConnection(false);
break; break;
case 'authBlocked': case "authBlocked":
this.router.navigate(['/']); this.router.navigate(["/"]);
break; break;
case 'logout': case "logout":
this.logOut(!!message.expired); this.logOut(!!message.expired);
break; break;
case 'lockVault': case "lockVault":
await this.vaultTimeoutService.lock(); await this.vaultTimeoutService.lock();
break; break;
case 'locked': case "locked":
this.notificationsService.updateConnection(false); this.notificationsService.updateConnection(false);
this.router.navigate(['lock']); this.router.navigate(["lock"]);
break; break;
case 'lockedUrl': case "lockedUrl":
window.setTimeout(() => this.routerService.setPreviousUrl(message.url), 500); window.setTimeout(() => this.routerService.setPreviousUrl(message.url), 500);
break; break;
case 'syncStarted': case "syncStarted":
break; break;
case 'syncCompleted': case "syncCompleted":
break; break;
case 'upgradeOrganization': case "upgradeOrganization":
const upgradeConfirmed = await this.platformUtilsService.showDialog( const upgradeConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('upgradeOrganizationDesc'), this.i18nService.t('upgradeOrganization'), this.i18nService.t("upgradeOrganizationDesc"),
this.i18nService.t('upgradeOrganization'), this.i18nService.t('cancel')); this.i18nService.t("upgradeOrganization"),
this.i18nService.t("upgradeOrganization"),
this.i18nService.t("cancel")
);
if (upgradeConfirmed) { if (upgradeConfirmed) {
this.router.navigate(['organizations', message.organizationId, 'settings', 'billing']); this.router.navigate([
"organizations",
message.organizationId,
"settings",
"billing",
]);
} }
break; break;
case 'premiumRequired': case "premiumRequired":
const premiumConfirmed = await this.platformUtilsService.showDialog( const premiumConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('premiumRequiredDesc'), this.i18nService.t('premiumRequired'), this.i18nService.t("premiumRequiredDesc"),
this.i18nService.t('learnMore'), this.i18nService.t('cancel')); this.i18nService.t("premiumRequired"),
this.i18nService.t("learnMore"),
this.i18nService.t("cancel")
);
if (premiumConfirmed) { if (premiumConfirmed) {
this.router.navigate(['settings/premium']); this.router.navigate(["settings/premium"]);
} }
break; break;
case 'emailVerificationRequired': case "emailVerificationRequired":
const emailVerificationConfirmed = await this.platformUtilsService.showDialog( const emailVerificationConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('emailVerificationRequiredDesc'), this.i18nService.t("emailVerificationRequiredDesc"),
this.i18nService.t('emailVerificationRequired'), this.i18nService.t("emailVerificationRequired"),
this.i18nService.t('learnMore'), this.i18nService.t('cancel')); this.i18nService.t("learnMore"),
this.i18nService.t("cancel")
);
if (emailVerificationConfirmed) { if (emailVerificationConfirmed) {
this.platformUtilsService.launchUri('https://bitwarden.com/help/article/create-bitwarden-account/'); this.platformUtilsService.launchUri(
"https://bitwarden.com/help/article/create-bitwarden-account/"
);
} }
break; break;
case 'showToast': case "showToast":
this.showToast(message); this.showToast(message);
break; break;
case 'setFullWidth': case "setFullWidth":
this.setFullWidth(); this.setFullWidth();
break; break;
case 'convertAccountToKeyConnector': case "convertAccountToKeyConnector":
this.keyConnectorService.setConvertAccountRequired(true); this.keyConnectorService.setConvertAccountRequired(true);
this.router.navigate(['/remove-password']); this.router.navigate(["/remove-password"]);
break; break;
default: default:
break; break;
@@ -168,14 +173,14 @@ export class AppComponent implements OnDestroy, OnInit {
}); });
}); });
this.router.events.subscribe(event => { this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) { if (event instanceof NavigationEnd) {
const modals = Array.from(document.querySelectorAll('.modal')); const modals = Array.from(document.querySelectorAll(".modal"));
for (const modal of modals) { for (const modal of modals) {
(jq(modal) as any).modal('hide'); (jq(modal) as any).modal("hide");
} }
if (document.querySelector('.swal-modal') != null) { if (document.querySelector(".swal-modal") != null) {
Swal.close(undefined); Swal.close(undefined);
} }
} }
@@ -220,18 +225,21 @@ export class AppComponent implements OnDestroy, OnInit {
this.searchService.clearIndex(); this.searchService.clearIndex();
this.authService.logOut(async () => { this.authService.logOut(async () => {
if (expired) { if (expired) {
this.platformUtilsService.showToast('warning', this.i18nService.t('loggedOut'), this.platformUtilsService.showToast(
this.i18nService.t('loginExpired')); "warning",
this.i18nService.t("loggedOut"),
this.i18nService.t("loginExpired")
);
} }
await this.stateService.clean({ userId: userId }); await this.stateService.clean({ userId: userId });
Swal.close(); Swal.close();
this.router.navigate(['/']); this.router.navigate(["/"]);
}); });
} }
private async recordActivity() { private async recordActivity() {
const now = (new Date()).getTime(); const now = new Date().getTime();
if (this.lastActivity != null && now - this.lastActivity < 250) { if (this.lastActivity != null && now - this.lastActivity < 250) {
return; return;
} }
@@ -256,17 +264,19 @@ export class AppComponent implements OnDestroy, OnInit {
} }
private showToast(msg: any) { private showToast(msg: any) {
let message = ''; let message = "";
const options: Partial<IndividualConfig> = {}; const options: Partial<IndividualConfig> = {};
if (typeof (msg.text) === 'string') { if (typeof msg.text === "string") {
message = msg.text; message = msg.text;
} else if (msg.text.length === 1) { } else if (msg.text.length === 1) {
message = msg.text[0]; message = msg.text[0];
} else { } else {
msg.text.forEach((t: string) => msg.text.forEach(
message += ('<p>' + this.sanitizer.sanitize(SecurityContext.HTML, t) + '</p>')); (t: string) =>
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>")
);
options.enableHtml = true; options.enableHtml = true;
} }
if (msg.options != null) { if (msg.options != null) {
@@ -278,7 +288,7 @@ export class AppComponent implements OnDestroy, OnInit {
} }
} }
this.toastrService.show(message, msg.title, options, 'toast-' + msg.type); this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
} }
private idleStateChanged() { private idleStateChanged() {
@@ -292,9 +302,9 @@ export class AppComponent implements OnDestroy, OnInit {
private async setFullWidth() { private async setFullWidth() {
const enableFullWidth = await this.stateService.getEnableFullWidth(); const enableFullWidth = await this.stateService.getEnableFullWidth();
if (enableFullWidth) { if (enableFullWidth) {
document.body.classList.add('full-width'); document.body.classList.add("full-width");
} else { } else {
document.body.classList.remove('full-width'); document.body.classList.remove("full-width");
} }
} }
} }

View File

@@ -1,17 +1,17 @@
import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { DragDropModule } from '@angular/cdk/drag-drop'; import { DragDropModule } from "@angular/cdk/drag-drop";
import { NgModule } from '@angular/core'; import { NgModule } from "@angular/core";
import { FormsModule } from '@angular/forms'; import { FormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { BitwardenToastModule } from 'jslib-angular/components/toastr.component'; 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";
import { ServicesModule } from './services/services.module'; import { ServicesModule } from "./services/services.module";
import { WildcardRoutingModule } from './wildcard-routing.module'; import { WildcardRoutingModule } from "./wildcard-routing.module";
@NgModule({ @NgModule({
imports: [ imports: [
@@ -29,9 +29,7 @@ import { WildcardRoutingModule } from './wildcard-routing.module';
OssRoutingModule, OssRoutingModule,
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,
],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })
export class AppModule { } export class AppModule {}

View File

@@ -1,17 +1,11 @@
import { import { Directive, OnInit } from "@angular/core";
Directive, import { ActivatedRoute, Router } from "@angular/router";
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { first } from 'rxjs/operators'; import { first } from "rxjs/operators";
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 { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
@Directive() @Directive()
export abstract class BaseAcceptComponent implements OnInit { export abstract class BaseAcceptComponent implements OnInit {
@@ -21,20 +15,24 @@ export abstract class BaseAcceptComponent implements OnInit {
actionPromise: Promise<any>; actionPromise: Promise<any>;
protected requiredParameters: string[] = []; protected requiredParameters: string[] = [];
protected failedShortMessage = 'inviteAcceptFailedShort'; protected failedShortMessage = "inviteAcceptFailedShort";
protected failedMessage = 'inviteAcceptFailed'; protected failedMessage = "inviteAcceptFailed";
constructor(protected router: Router, protected platformUtilService: PlatformUtilsService, constructor(
protected i18nService: I18nService, protected route: ActivatedRoute, protected router: Router,
protected stateService: StateService) { } protected platformUtilService: PlatformUtilsService,
protected i18nService: I18nService,
protected route: ActivatedRoute,
protected stateService: StateService
) {}
abstract authedHandler(qParams: any): Promise<void>; abstract authedHandler(qParams: any): Promise<void>;
abstract unauthedHandler(qParams: any): Promise<void>; abstract unauthedHandler(qParams: any): Promise<void>;
ngOnInit() { ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => { this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
await this.stateService.setLoginRedirect(null); await this.stateService.setLoginRedirect(null);
let error = this.requiredParameters.some(e => qParams?.[e] == null || qParams[e] === ''); let error = this.requiredParameters.some((e) => qParams?.[e] == null || qParams[e] === "");
let errorMessage: string = null; let errorMessage: string = null;
if (!error) { if (!error) {
this.authed = await this.stateService.getIsAuthenticated(); this.authed = await this.stateService.getIsAuthenticated();
@@ -58,10 +56,14 @@ export abstract class BaseAcceptComponent implements OnInit {
} }
if (error) { if (error) {
const message = errorMessage != null ? this.i18nService.t(this.failedShortMessage, errorMessage) : const message =
this.i18nService.t(this.failedMessage); errorMessage != null
this.platformUtilService.showToast('error', null, message, {timeout: 10000}); ? this.i18nService.t(this.failedShortMessage, errorMessage)
this.router.navigate(['/']); : this.i18nService.t(this.failedMessage);
this.platformUtilService.showToast("error", null, message, {
timeout: 10000,
});
this.router.navigate(["/"]);
} }
this.loading = false; this.loading = false;

View File

@@ -1,16 +1,16 @@
import { Directive } from '@angular/core'; import { Directive } from "@angular/core";
import { ExportService } from 'jslib-common/abstractions/export.service'; import { ExportService } from "jslib-common/abstractions/export.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { EventView } from 'jslib-common/models/view/eventView'; import { EventView } from "jslib-common/models/view/eventView";
import { EventResponse } from 'jslib-common/models/response/eventResponse'; import { EventResponse } from "jslib-common/models/response/eventResponse";
import { ListResponse } from 'jslib-common/models/response/listResponse'; import { ListResponse } from "jslib-common/models/response/listResponse";
import { EventService } from 'src/app/services/event.service'; import { EventService } from "src/app/services/event.service";
@Directive() @Directive()
export abstract class BaseEventsComponent { export abstract class BaseEventsComponent {
@@ -27,9 +27,13 @@ export abstract class BaseEventsComponent {
abstract readonly exportFileName: string; abstract readonly exportFileName: string;
constructor(protected eventService: EventService, protected i18nService: I18nService, constructor(
protected exportService: ExportService, protected platformUtilsService: PlatformUtilsService, protected eventService: EventService,
protected logService: LogService) { protected i18nService: I18nService,
protected exportService: ExportService,
protected platformUtilsService: PlatformUtilsService,
protected logService: LogService
) {
const defaultDates = this.eventService.getDefaultDateFilters(); const defaultDates = this.eventService.getDefaultDateFilters();
this.start = defaultDates[0]; this.start = defaultDates[0];
this.end = defaultDates[1]; this.end = defaultDates[1];
@@ -72,7 +76,11 @@ export abstract class BaseEventsComponent {
this.loading = true; this.loading = true;
let events: EventView[] = []; let events: EventView[] = [];
try { try {
const promise = this.loadAndParseEvents(dates[0], dates[1], clearExisting ? null : this.continuationToken); const promise = this.loadAndParseEvents(
dates[0],
dates[1],
clearExisting ? null : this.continuationToken
);
if (clearExisting) { if (clearExisting) {
this.refreshPromise = promise; this.refreshPromise = promise;
} else { } else {
@@ -97,13 +105,28 @@ export abstract class BaseEventsComponent {
this.refreshPromise = null; this.refreshPromise = null;
} }
protected abstract requestEvents(startDate: string, endDate: string, continuationToken: string): Promise<ListResponse<EventResponse>>; protected abstract requestEvents(
protected abstract getUserName(r: EventResponse, userId: string): { name: string, email: string }; startDate: string,
endDate: string,
continuationToken: string
): Promise<ListResponse<EventResponse>>;
protected abstract getUserName(
r: EventResponse,
userId: string
): {
name: string;
email: string;
};
protected async loadAndParseEvents(startDate: string, endDate: string, continuationToken: string) { protected async loadAndParseEvents(
startDate: string,
endDate: string,
continuationToken: string
) {
const response = await this.requestEvents(startDate, endDate, continuationToken); const response = await this.requestEvents(startDate, endDate, continuationToken);
const events = await Promise.all(response.data.map(async r => { const events = await Promise.all(
response.data.map(async (r) => {
const userId = r.actingUserId == null ? r.userId : r.actingUserId; const userId = r.actingUserId == null ? r.userId : r.actingUserId;
const eventInfo = await this.eventService.getEventInfo(r); const eventInfo = await this.eventService.getEventInfo(r);
const user = this.getUserName(r, userId); const user = this.getUserName(r, userId);
@@ -113,14 +136,18 @@ export abstract class BaseEventsComponent {
appIcon: eventInfo.appIcon, appIcon: eventInfo.appIcon,
appName: eventInfo.appName, appName: eventInfo.appName,
userId: userId, userId: userId,
userName: user != null ? user.name : this.i18nService.t('unknown'), userName: user != null ? user.name : this.i18nService.t("unknown"),
userEmail: user != null ? user.email : '', userEmail: user != null ? user.email : "",
date: r.date, date: r.date,
ip: r.ipAddress, ip: r.ipAddress,
type: r.type, type: r.type,
}); });
})); })
return { continuationToken: response.continuationToken, events: events }; );
return {
continuationToken: response.continuationToken,
events: events,
};
} }
protected parseDates() { protected parseDates() {
@@ -128,8 +155,11 @@ export abstract class BaseEventsComponent {
try { try {
dates = this.eventService.formatDateFilters(this.start, this.end); dates = this.eventService.formatDateFilters(this.start, this.end);
} catch (e) { } catch (e) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.platformUtilsService.showToast(
this.i18nService.t('invalidDateRange')); "error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidDateRange")
);
return null; return null;
} }
return dates; return dates;
@@ -150,7 +180,14 @@ export abstract class BaseEventsComponent {
} }
const data = await this.exportService.getEventExport(events); const data = await this.exportService.getEventExport(events);
const fileName = this.exportService.getFileName(this.exportFileName, 'csv'); const fileName = this.exportService.getFileName(this.exportFileName, "csv");
this.platformUtilsService.saveFile(window, data, { type: 'text/plain' }, fileName); this.platformUtilsService.saveFile(
window,
data,
{
type: "text/plain",
},
fileName
);
} }
} }

View File

@@ -1,67 +1,77 @@
import { import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
Directive,
ViewChild,
ViewContainerRef
} 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";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from "jslib-common/abstractions/search.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { ModalService } from 'jslib-angular/services/modal.service'; import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from 'jslib-angular/services/validation.service'; import { ValidationService } from "jslib-angular/services/validation.service";
import { SearchPipe } from 'jslib-angular/pipes/search.pipe'; import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { UserNamePipe } from 'jslib-angular/pipes/user-name.pipe'; import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType'; import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { OrganizationUserType } from 'jslib-common/enums/organizationUserType'; import { OrganizationUserType } from "jslib-common/enums/organizationUserType";
import { ProviderUserStatusType } from 'jslib-common/enums/providerUserStatusType'; import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
import { ProviderUserType } from 'jslib-common/enums/providerUserType'; import { ProviderUserType } from "jslib-common/enums/providerUserType";
import { ListResponse } from 'jslib-common/models/response/listResponse'; import { ListResponse } from "jslib-common/models/response/listResponse";
import { OrganizationUserUserDetailsResponse } from 'jslib-common/models/response/organizationUserResponse'; import { OrganizationUserUserDetailsResponse } from "jslib-common/models/response/organizationUserResponse";
import { ProviderUserUserDetailsResponse } from 'jslib-common/models/response/provider/providerUserResponse'; import { ProviderUserUserDetailsResponse } from "jslib-common/models/response/provider/providerUserResponse";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
import { UserConfirmComponent } from '../organizations/manage/user-confirm.component'; import { UserConfirmComponent } from "../organizations/manage/user-confirm.component";
type StatusType = OrganizationUserStatusType | ProviderUserStatusType; type StatusType = OrganizationUserStatusType | ProviderUserStatusType;
const MaxCheckedCount = 500; const MaxCheckedCount = 500;
@Directive() @Directive()
export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetailsResponse | OrganizationUserUserDetailsResponse> { export abstract class BasePeopleComponent<
UserType extends ProviderUserUserDetailsResponse | OrganizationUserUserDetailsResponse
@ViewChild('confirmTemplate', { read: ViewContainerRef, static: true }) confirmModalRef: ViewContainerRef; > {
@ViewChild("confirmTemplate", {
read: ViewContainerRef,
static: true,
})
confirmModalRef: ViewContainerRef;
get allCount() { get allCount() {
return this.allUsers != null ? this.allUsers.length : 0; return this.allUsers != null ? this.allUsers.length : 0;
} }
get invitedCount() { get invitedCount() {
return this.statusMap.has(this.userStatusType.Invited) ? return this.statusMap.has(this.userStatusType.Invited)
this.statusMap.get(this.userStatusType.Invited).length : 0; ? this.statusMap.get(this.userStatusType.Invited).length
: 0;
} }
get acceptedCount() { get acceptedCount() {
return this.statusMap.has(this.userStatusType.Accepted) ? return this.statusMap.has(this.userStatusType.Accepted)
this.statusMap.get(this.userStatusType.Accepted).length : 0; ? this.statusMap.get(this.userStatusType.Accepted).length
: 0;
} }
get confirmedCount() { get confirmedCount() {
return this.statusMap.has(this.userStatusType.Confirmed) ? return this.statusMap.has(this.userStatusType.Confirmed)
this.statusMap.get(this.userStatusType.Confirmed).length : 0; ? this.statusMap.get(this.userStatusType.Confirmed).length
: 0;
} }
get showConfirmUsers(): boolean { get showConfirmUsers(): boolean {
return this.allUsers != null && this.statusMap != null && this.allUsers.length > 1 && return (
this.confirmedCount > 0 && this.confirmedCount < 3 && this.acceptedCount > 0; this.allUsers != null &&
this.statusMap != null &&
this.allUsers.length > 1 &&
this.confirmedCount > 0 &&
this.confirmedCount < 3 &&
this.acceptedCount > 0
);
} }
get showBulkConfirmUsers(): boolean { get showBulkConfirmUsers(): boolean {
@@ -97,8 +107,8 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
private logService: LogService, private logService: LogService,
private searchPipe: SearchPipe, private searchPipe: SearchPipe,
protected userNamePipe: UserNamePipe, protected userNamePipe: UserNamePipe,
protected stateService: StateService, protected stateService: StateService
) { } ) {}
abstract edit(user: UserType): void; abstract edit(user: UserType): void;
abstract getUsers(): Promise<ListResponse<UserType>>; abstract getUsers(): Promise<ListResponse<UserType>>;
@@ -114,8 +124,8 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
} }
this.allUsers = response.data != null && response.data.length > 0 ? response.data : []; this.allUsers = response.data != null && response.data.length > 0 ? response.data : [];
this.allUsers.sort(Utils.getSortFunction(this.i18nService, 'email')); this.allUsers.sort(Utils.getSortFunction(this.i18nService, "email"));
this.allUsers.forEach(u => { this.allUsers.forEach((u) => {
if (!this.statusMap.has(u.status)) { if (!this.statusMap.has(u.status)) {
this.statusMap.set(u.status, [u]); this.statusMap.set(u.status, [u]);
} else { } else {
@@ -148,7 +158,9 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
pagedSize = this.pagedUsersCount; pagedSize = this.pagedUsersCount;
} }
if (this.users.length > pagedLength) { if (this.users.length > pagedLength) {
this.pagedUsers = this.pagedUsers.concat(this.users.slice(pagedLength, pagedLength + pagedSize)); this.pagedUsers = this.pagedUsers.concat(
this.users.slice(pagedLength, pagedLength + pagedSize)
);
} }
this.pagedUsersCount = this.pagedUsers.length; this.pagedUsersCount = this.pagedUsers.length;
this.didScroll = this.pagedUsers.length > this.pageSize; this.didScroll = this.pagedUsers.length > this.pageSize;
@@ -163,11 +175,16 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
this.selectAll(false); this.selectAll(false);
} }
const filteredUsers = this.searchPipe.transform(this.users, this.searchText, 'name', 'email', 'id'); const filteredUsers = this.searchPipe.transform(
this.users,
this.searchText,
"name",
"email",
"id"
);
const selectCount = select && filteredUsers.length > MaxCheckedCount const selectCount =
? MaxCheckedCount select && filteredUsers.length > MaxCheckedCount ? MaxCheckedCount : filteredUsers.length;
: filteredUsers.length;
for (let i = 0; i < selectCount; i++) { for (let i = 0; i < selectCount; i++) {
this.checkUser(filteredUsers[i], select); this.checkUser(filteredUsers[i], select);
} }
@@ -184,8 +201,12 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
async remove(user: UserType) { async remove(user: UserType) {
const confirmed = await this.platformUtilsService.showDialog( const confirmed = await this.platformUtilsService.showDialog(
this.deleteWarningMessage(user), this.userNamePipe.transform(user), this.deleteWarningMessage(user),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); this.userNamePipe.transform(user),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) { if (!confirmed) {
return false; return false;
@@ -194,8 +215,11 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
this.actionPromise = this.deleteUser(user.id); this.actionPromise = this.deleteUser(user.id);
try { try {
await this.actionPromise; await this.actionPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedUserId', this.platformUtilsService.showToast(
this.userNamePipe.transform(user))); "success",
null,
this.i18nService.t("removedUserId", this.userNamePipe.transform(user))
);
this.removeUser(user); this.removeUser(user);
} catch (e) { } catch (e) {
this.validationService.showError(e); this.validationService.showError(e);
@@ -211,8 +235,11 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
this.actionPromise = this.reinviteUser(user.id); this.actionPromise = this.reinviteUser(user.id);
try { try {
await this.actionPromise; await this.actionPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('hasBeenReinvited', this.platformUtilsService.showToast(
this.userNamePipe.transform(user))); "success",
null,
this.i18nService.t("hasBeenReinvited", this.userNamePipe.transform(user))
);
} catch (e) { } catch (e) {
this.validationService.showError(e); this.validationService.showError(e);
} }
@@ -234,8 +261,11 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
this.actionPromise = this.confirmUser(user, publicKey); this.actionPromise = this.confirmUser(user, publicKey);
await this.actionPromise; await this.actionPromise;
updateUser(this); updateUser(this);
this.platformUtilsService.showToast('success', null, this.i18nService.t('hasBeenConfirmed', this.platformUtilsService.showToast(
this.userNamePipe.transform(user))); "success",
null,
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(user))
);
} catch (e) { } catch (e) {
this.validationService.showError(e); this.validationService.showError(e);
throw e; throw e;
@@ -254,7 +284,10 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints(); const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
if (autoConfirm == null || !autoConfirm) { if (autoConfirm == null || !autoConfirm) {
const [modal] = await this.modalService.openViewRef(UserConfirmComponent, this.confirmModalRef, comp => { const [modal] = await this.modalService.openViewRef(
UserConfirmComponent,
this.confirmModalRef,
(comp) => {
comp.name = this.userNamePipe.transform(user); comp.name = this.userNamePipe.transform(user);
comp.userId = user != null ? user.userId : null; comp.userId = user != null ? user.userId : null;
comp.publicKey = publicKey; comp.publicKey = publicKey;
@@ -267,13 +300,14 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
this.logService.error(e); this.logService.error(e);
} }
}); });
}); }
);
return; return;
} }
try { try {
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey.buffer); const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey.buffer);
this.logService.info(`User's fingerprint: ${fingerprint.join('-')}`); this.logService.info(`User's fingerprint: ${fingerprint.join("-")}`);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
@@ -296,11 +330,11 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
} }
protected deleteWarningMessage(user: UserType): string { protected deleteWarningMessage(user: UserType): string {
return this.i18nService.t('removeUserConfirmation'); return this.i18nService.t("removeUserConfirmation");
} }
protected getCheckedUsers() { protected getCheckedUsers() {
return this.users.filter(u => (u as any).checked); return this.users.filter((u) => (u as any).checked);
} }
protected removeUser(user: UserType) { protected removeUser(user: UserType) {
@@ -316,5 +350,4 @@ export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetai
} }
} }
} }
} }

View File

@@ -1,17 +1,29 @@
<div class="form-group mb-0"> <div class="form-group mb-0">
<div class="form-check mt-1 form-check-block"> <div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" [name]="pascalize(parentId)" [id]="parentId" <input
[(ngModel)]="parentChecked" [indeterminate]="parentIndeterminate"> class="form-check-input"
type="checkbox"
[name]="pascalize(parentId)"
[id]="parentId"
[(ngModel)]="parentChecked"
[indeterminate]="parentIndeterminate"
/>
<label class="form-check-label font-weight-normal" [for]="parentId"> <label class="form-check-label font-weight-normal" [for]="parentId">
{{parentId | i18n}} {{ parentId | i18n }}
</label> </label>
</div> </div>
<div class="form-group form-group-child-check mb-0"> <div class="form-group form-group-child-check mb-0">
<div class="form-check mt-1" *ngFor="let c of checkboxes"> <div class="form-check mt-1" *ngFor="let c of checkboxes">
<input class="form-check-input" type="checkbox" [name]="pascalize(c.id)" [id]="c.id" [ngModel]="c.get()" <input
(ngModelChange)="c.set($event)"> class="form-check-input"
type="checkbox"
[name]="pascalize(c.id)"
[id]="c.id"
[ngModel]="c.get()"
(ngModelChange)="c.set($event)"
/>
<label class="form-check-label font-weight-normal" [for]="c.id"> <label class="form-check-label font-weight-normal" [for]="c.id">
{{c.id | i18n}} {{ c.id | i18n }}
</label> </label>
</div> </div>
</div> </div>

View File

@@ -1,32 +1,34 @@
import { import { Component, EventEmitter, Input, Output } from "@angular/core";
Component, import { Utils } from "jslib-common/misc/utils";
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Utils } from 'jslib-common/misc/utils';
@Component({ @Component({
selector: 'app-nested-checkbox', selector: "app-nested-checkbox",
templateUrl: 'nested-checkbox.component.html', templateUrl: "nested-checkbox.component.html",
}) })
export class NestedCheckboxComponent { export class NestedCheckboxComponent {
@Input() parentId: string; @Input()
@Input() checkboxes: { id: string, get: () => boolean, set: (v: boolean) => void; }[]; parentId: string;
@Output() onSavedUser = new EventEmitter(); @Input()
@Output() onDeletedUser = new EventEmitter(); checkboxes: {
id: string;
get: () => boolean;
set: (v: boolean) => void;
}[];
@Output()
onSavedUser = new EventEmitter();
@Output()
onDeletedUser = new EventEmitter();
get parentIndeterminate() { get parentIndeterminate() {
return !this.parentChecked && return !this.parentChecked && this.checkboxes.some((c) => c.get());
this.checkboxes.some(c => c.get());
} }
get parentChecked() { get parentChecked() {
return this.checkboxes.every(c => c.get()); return this.checkboxes.every((c) => c.get());
} }
set parentChecked(value: boolean) { set parentChecked(value: boolean) {
this.checkboxes.forEach(c => { this.checkboxes.forEach((c) => {
c.set(value); c.set(value);
}); });
} }

View File

@@ -3,35 +3,52 @@
<form class="modal-content" #form (ngSubmit)="submit()"> <form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-header"> <div class="modal-header">
<h2 class="modal-title" id="confirmUserTitle"> <h2 class="modal-title" id="confirmUserTitle">
{{'passwordConfirmation' | i18n}} {{ "passwordConfirmation" | i18n }}
</h2> </h2>
<button type="button" class="close" data-dismiss="modal"> <button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
{{'passwordConfirmationDesc' | i18n}} {{ "passwordConfirmationDesc" | i18n }}
<div class="form-group"> <div class="form-group">
<label for="masterPassword">{{'masterPass' | i18n}}</label> <label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex"> <div class="d-flex">
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" <input
name="MasterPassword" class="text-monospace form-control" [(ngModel)]="masterPassword" id="masterPassword"
required appAutofocus appInputVerbatim> type="{{ showPassword ? 'text' : 'password' }}"
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}" name="MasterPassword"
(click)="togglePassword()"> class="text-monospace form-control"
<i class="fa fa-lg" aria-hidden="true" [(ngModel)]="masterPassword"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> required
appAutofocus
appInputVerbatim
/>
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{
'fa-eye': !showPassword,
'fa-eye-slash': showPassword
}"
></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick> <button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{'ok' | i18n}}</span> <span>{{ "ok" | i18n }}</span>
</button> </button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal"> <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{'cancel' | i18n}} {{ "cancel" | i18n }}
</button> </button>
</div> </div>
</form> </form>

View File

@@ -1,8 +1,8 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { PasswordRepromptComponent as BasePasswordRepromptComponent } from 'jslib-angular/components/password-reprompt.component'; import { PasswordRepromptComponent as BasePasswordRepromptComponent } from "jslib-angular/components/password-reprompt.component";
@Component({ @Component({
templateUrl: 'password-reprompt.component.html', templateUrl: "password-reprompt.component.html",
}) })
export class PasswordRepromptComponent extends BasePasswordRepromptComponent {} export class PasswordRepromptComponent extends BasePasswordRepromptComponent {}

View File

@@ -1,8 +1,16 @@
<div class="progress"> <div class="progress">
<div class="progress-bar {{color}}" role="progressbar" [ngStyle]="{width: (scoreWidth + '%')}" <div
attr.aria-valuenow="{{scoreWidth}}" aria-valuemin="0" aria-valuemax="100"> class="progress-bar {{ color }}"
role="progressbar"
[ngStyle]="{
width: scoreWidth + '%'
}"
attr.aria-valuenow="{{ scoreWidth }}"
aria-valuemin="0"
aria-valuemax="100"
>
<ng-container *ngIf="showText && text"> <ng-container *ngIf="showText && text">
{{text}} {{ text }}
</ng-container> </ng-container>
</div> </div>
</div> </div>

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