mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Apply Prettier (#1347)
This commit is contained in:
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1,3 +1 @@
|
|||||||
*.sh eol=lf
|
* text=auto eol=lf
|
||||||
.dockerignore eol=lf
|
|
||||||
dockerfile eol=lf
|
|
||||||
|
|||||||
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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)
|
||||||
|
|||||||
60
.github/workflows/build.yml
vendored
60
.github/workflows/build.yml
vendored
@@ -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
|
||||||
@@ -84,13 +82,12 @@ jobs:
|
|||||||
zip -r web-$_VERSION-selfhosted-open-source.zip build
|
zip -r web-$_VERSION-selfhosted-open-source.zip build
|
||||||
|
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
||||||
with:
|
with:
|
||||||
name: web-${{ env._VERSION }}-selfhosted-open-source.zip
|
name: web-${{ env._VERSION }}-selfhosted-open-source.zip
|
||||||
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
|
||||||
@@ -132,13 +129,12 @@ jobs:
|
|||||||
zip -r web-$_VERSION-cloud-COMMERCIAL.zip build
|
zip -r web-$_VERSION-cloud-COMMERCIAL.zip build
|
||||||
|
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
||||||
with:
|
with:
|
||||||
name: web-${{ env._VERSION }}-cloud-COMMERCIAL.zip
|
name: web-${{ env._VERSION }}-cloud-COMMERCIAL.zip
|
||||||
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
|
||||||
@@ -195,7 +191,7 @@ jobs:
|
|||||||
zip -r web-$_VERSION-selfhosted-COMMERCIAL.zip build
|
zip -r web-$_VERSION-selfhosted-COMMERCIAL.zip build
|
||||||
|
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
||||||
with:
|
with:
|
||||||
name: web-${{ env._VERSION }}-selfhosted-COMMERCIAL.zip
|
name: web-${{ env._VERSION }}-selfhosted-COMMERCIAL.zip
|
||||||
path: ./web-${{ env._VERSION }}-selfhosted-COMMERCIAL.zip
|
path: ./web-${{ env._VERSION }}-selfhosted-COMMERCIAL.zip
|
||||||
@@ -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,22 +346,22 @@ 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
|
||||||
|
|
||||||
- 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: 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'
|
||||||
@@ -408,7 +401,7 @@ jobs:
|
|||||||
_CROWDIN_PROJECT_ID: "308189"
|
_CROWDIN_PROJECT_ID: "308189"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||||
|
|
||||||
- name: Login to Azure
|
- name: Login to Azure
|
||||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||||
@@ -423,7 +416,7 @@ jobs:
|
|||||||
secrets: "crowdin-api-token"
|
secrets: "crowdin-api-token"
|
||||||
|
|
||||||
- name: Upload Sources
|
- name: Upload Sources
|
||||||
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2
|
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||||
@@ -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()
|
||||||
@@ -493,7 +485,7 @@ jobs:
|
|||||||
secrets: "devops-alerts-slack-webhook-url"
|
secrets: "devops-alerts-slack-webhook-url"
|
||||||
|
|
||||||
- name: Notify Slack on failure
|
- name: Notify Slack on failure
|
||||||
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
|
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
|
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
|
||||||
|
|||||||
6
.github/workflows/crowdin-pull.yml
vendored
6
.github/workflows/crowdin-pull.yml
vendored
@@ -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:
|
||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
_CROWDIN_PROJECT_ID: "308189"
|
_CROWDIN_PROJECT_ID: "308189"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||||
|
|
||||||
- name: Login to Azure
|
- name: Login to Azure
|
||||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
secrets: "crowdin-api-token"
|
secrets: "crowdin-api-token"
|
||||||
|
|
||||||
- name: Download translations
|
- name: Download translations
|
||||||
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2
|
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||||
|
|||||||
5
.github/workflows/qa-deploy.yml
vendored
5
.github/workflows/qa-deploy.yml
vendored
@@ -20,11 +20,10 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
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
|
||||||
|
|||||||
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # 2.3.4
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # 2.3.4
|
||||||
|
|
||||||
- name: Check Release Version
|
- name: Check Release Version
|
||||||
id: version
|
id: version
|
||||||
@@ -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
|
||||||
@@ -108,7 +106,7 @@ jobs:
|
|||||||
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
|
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||||
with:
|
with:
|
||||||
ref: gh-pages
|
ref: gh-pages
|
||||||
|
|
||||||
@@ -118,7 +116,7 @@ jobs:
|
|||||||
git push -u origin deploy-$_TAG_VERSION
|
git push -u origin deploy-$_TAG_VERSION
|
||||||
|
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||||
|
|
||||||
- name: Setup git config
|
- name: Setup git config
|
||||||
run: |
|
run: |
|
||||||
@@ -140,7 +138,7 @@ jobs:
|
|||||||
run: unzip web-*-cloud-COMMERCIAL.zip
|
run: unzip web-*-cloud-COMMERCIAL.zip
|
||||||
|
|
||||||
- name: Deploy GitHub Pages
|
- name: Deploy GitHub Pages
|
||||||
uses: crazy-max/ghaction-github-pages@db4476a01402e1a7ce05f41832040eef16d14925 # v2.5.0
|
uses: crazy-max/ghaction-github-pages@db4476a01402e1a7ce05f41832040eef16d14925 # v2.5.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
@@ -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
|
||||||
@@ -175,7 +172,7 @@ jobs:
|
|||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
branch: ${{ needs.setup.outputs.branch-name }}
|
branch: ${{ needs.setup.outputs.branch-name }}
|
||||||
artifacts: "web-*-selfhosted-COMMERCIAL.zip,
|
artifacts: "web-*-selfhosted-COMMERCIAL.zip,
|
||||||
web-*-selfhosted-open-source.zip"
|
web-*-selfhosted-open-source.zip"
|
||||||
|
|
||||||
- name: Rename assets
|
- name: Rename assets
|
||||||
run: |
|
run: |
|
||||||
@@ -190,6 +187,6 @@ jobs:
|
|||||||
tag: "${{ needs.setup.outputs.tag_version }}"
|
tag: "${{ needs.setup.outputs.tag_version }}"
|
||||||
body: "<insert release notes here>"
|
body: "<insert release notes here>"
|
||||||
artifacts: "web-${{ needs.setup.outputs.release_version }}-selfhosted-COMMERCIAL.zip,
|
artifacts: "web-${{ needs.setup.outputs.release_version }}-selfhosted-COMMERCIAL.zip,
|
||||||
web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip"
|
web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
draft: true
|
draft: true
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -48,16 +48,14 @@ You can also manually adjusting your API endpoint settings by adding `config/loc
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dev": {
|
"dev": {
|
||||||
"proxyApi": "http://your-api-url",
|
"proxyApi": "http://your-api-url",
|
||||||
"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": {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
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,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class AppRoutingModule { }
|
export class AppRoutingModule {}
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
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() {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
ngOnInit() {
|
this.policyListService.addPolicies([
|
||||||
super.ngOnInit();
|
new MaximumVaultTimeoutPolicy(),
|
||||||
|
new DisablePersonalVaultExportPolicy(),
|
||||||
this.policyListService.addPolicies([
|
]);
|
||||||
new MaximumVaultTimeoutPolicy(),
|
}
|
||||||
new DisablePersonalVaultExportPolicy(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,48 @@
|
|||||||
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: [
|
||||||
OssModule,
|
OssModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
ServicesModule,
|
ServicesModule,
|
||||||
BitwardenToastModule.forRoot({
|
BitwardenToastModule.forRoot({
|
||||||
maxOpened: 5,
|
maxOpened: 5,
|
||||||
autoDismiss: true,
|
autoDismiss: true,
|
||||||
closeButton: true,
|
closeButton: true,
|
||||||
}),
|
}),
|
||||||
InfiniteScrollModule,
|
InfiniteScrollModule,
|
||||||
DragDropModule,
|
DragDropModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
OssRoutingModule,
|
OssRoutingModule,
|
||||||
OrganizationsModule,
|
OrganizationsModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
WildcardRoutingModule, // Needs to be last to catch all non-existing routes
|
WildcardRoutingModule, // Needs to be last to catch all non-existing routes
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
MaximumVaultTimeoutPolicyComponent,
|
MaximumVaultTimeoutPolicyComponent,
|
||||||
DisablePersonalVaultExportPolicyComponent,
|
DisablePersonalVaultExportPolicyComponent,
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule {}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });
|
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });
|
||||||
|
|||||||
@@ -1,363 +1,488 @@
|
|||||||
<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
|
||||||
<p>
|
#form
|
||||||
{{'ssoPolicyHelpStart' | i18n}}
|
(ngSubmit)="submit()"
|
||||||
<a routerLink="../policies">{{'ssoPolicyHelpLink' | i18n}}</a>
|
[formGroup]="data"
|
||||||
{{'ssoPolicyHelpEnd' | i18n}}
|
[appApiAction]="formPromise"
|
||||||
<br>
|
*ngIf="!loading"
|
||||||
{{'ssoPolicyHelpKeyConnector' | i18n}}
|
ngNativeValidate
|
||||||
</p>
|
>
|
||||||
|
<p>
|
||||||
|
{{ "ssoPolicyHelpStart" | i18n }}
|
||||||
|
<a routerLink="../policies">{{ "ssoPolicyHelpLink" | i18n }}</a>
|
||||||
|
{{ "ssoPolicyHelpEnd" | i18n }}
|
||||||
|
<br />
|
||||||
|
{{ "ssoPolicyHelpKeyConnector" | i18n }}
|
||||||
|
</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"
|
||||||
</div>
|
type="checkbox"
|
||||||
<small class="form-text text-muted">{{'allowSsoDesc' | i18n}}</small>
|
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>
|
||||||
|
</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
|
||||||
<label class="form-check-label" for="memberDecryptionPass">
|
class="form-check-input"
|
||||||
{{'masterPass' | i18n}}
|
type="radio"
|
||||||
<small>{{'memberDecryptionPassDesc' | i18n}}</small>
|
id="memberDecryptionPass"
|
||||||
</label>
|
[value]="false"
|
||||||
</div>
|
formControlName="keyConnectorEnabled"
|
||||||
<div class="form-check mt-2 form-check-block">
|
/>
|
||||||
<input class="form-check-input" type="radio" id="memberDecryptionKey" [value]="true" formControlName="keyConnectorEnabled"
|
<label class="form-check-label" for="memberDecryptionPass">
|
||||||
[attr.disabled]="!organization.useKeyConnector || null">
|
{{ "masterPass" | i18n }}
|
||||||
<label class="form-check-label" for="memberDecryptionKey">
|
<small>{{ "memberDecryptionPassDesc" | i18n }}</small>
|
||||||
{{'keyConnector' | i18n}}
|
</label>
|
||||||
<a 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>
|
|
||||||
</a>
|
|
||||||
<small>{{'memberDecryptionKeyConnectorDesc' | i18n}}</small>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check mt-2 form-check-block">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="radio"
|
||||||
|
id="memberDecryptionKey"
|
||||||
|
[value]="true"
|
||||||
|
formControlName="keyConnectorEnabled"
|
||||||
|
[attr.disabled]="!organization.useKeyConnector || null"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="memberDecryptionKey">
|
||||||
|
{{ "keyConnector" | i18n }}
|
||||||
|
<a
|
||||||
|
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>
|
||||||
|
</a>
|
||||||
|
<small>{{ "memberDecryptionKeyConnectorDesc" | i18n }}</small>
|
||||||
|
</label>
|
||||||
|
</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">
|
|
||||||
<label for="keyConnectorUrl">{{'keyConnectorUrl' | i18n}}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input class="form-control" formControlName="keyConnectorUrl" id="keyConnectorUrl" required>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" (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">
|
|
||||||
{{'keyConnectorTest' | i18n}}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ng-container *ngIf="keyConnectorUrl.pristine && !keyConnectorUrl.pending">
|
|
||||||
<div class="text-danger" *ngIf="keyConnectorUrl.hasError('invalidUrl')" role="alert">
|
|
||||||
<i class="fa fa-exclamation-circle" aria-hidden="true"></i>
|
|
||||||
{{'keyConnectorTestFail' | i18n}}
|
|
||||||
</div>
|
|
||||||
<div class="text-success" *ngIf="!keyConnectorUrl.hasError('invalidUrl')" role="alert">
|
|
||||||
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
|
|
||||||
{{'keyConnectorTestSuccess' | i18n}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="type">{{'type' | i18n}}</label>
|
<label for="keyConnectorUrl">{{ "keyConnectorUrl" | i18n }}</label>
|
||||||
<select class="form-control" id="type" formControlName="configType">
|
<div class="input-group">
|
||||||
<option [ngValue]="0" disabled>{{'selectType' | i18n}}</option>
|
<input
|
||||||
<option [ngValue]="1">OpenID Connect</option>
|
class="form-control"
|
||||||
<option [ngValue]="2">SAML 2.0</option>
|
formControlName="keyConnectorUrl"
|
||||||
|
id="keyConnectorUrl"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
(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">
|
||||||
|
{{ "keyConnectorTest" | i18n }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngIf="keyConnectorUrl.pristine && !keyConnectorUrl.pending">
|
||||||
|
<div class="text-danger" *ngIf="keyConnectorUrl.hasError('invalidUrl')" role="alert">
|
||||||
|
<i class="fa fa-exclamation-circle" aria-hidden="true"></i>
|
||||||
|
{{ "keyConnectorTestFail" | i18n }}
|
||||||
|
</div>
|
||||||
|
<div class="text-success" *ngIf="!keyConnectorUrl.hasError('invalidUrl')" role="alert">
|
||||||
|
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
|
||||||
|
{{ "keyConnectorTestSuccess" | i18n }}
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="type">{{ "type" | i18n }}</label>
|
||||||
|
<select class="form-control" id="type" formControlName="configType">
|
||||||
|
<option [ngValue]="0" disabled>{{ "selectType" | i18n }}</option>
|
||||||
|
<option [ngValue]="1">OpenID Connect</option>
|
||||||
|
<option [ngValue]="2">SAML 2.0</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- OIDC -->
|
||||||
|
<div *ngIf="data.value.configType == 1">
|
||||||
|
<div class="config-section">
|
||||||
|
<h2>{{ "openIdConnectConfig" | i18n }}</h2>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ "callbackPath" | i18n }}</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control" readonly [value]="callbackPath" />
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||||
|
(click)="copy(callbackPath)"
|
||||||
|
>
|
||||||
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ "signedOutCallbackPath" | i18n }}</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control" readonly [value]="signedOutCallbackPath" />
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||||
|
(click)="copy(signedOutCallbackPath)"
|
||||||
|
>
|
||||||
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="authority">{{ "authority" | i18n }}</label>
|
||||||
|
<input class="form-control" formControlName="authority" id="authority" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="clientId">{{ "clientId" | i18n }}</label>
|
||||||
|
<input class="form-control" formControlName="clientId" id="clientId" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="clientSecret">{{ "clientSecret" | i18n }}</label>
|
||||||
|
<input class="form-control" formControlName="clientSecret" id="clientSecret" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="metadataAddress">{{ "metadataAddress" | i18n }}</label>
|
||||||
|
<input class="form-control" formControlName="metadataAddress" id="metadataAddress" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="redirectBehavior">{{ "oidcRedirectBehavior" | i18n }}</label>
|
||||||
|
<select class="form-control" formControlName="redirectBehavior" id="redirectBehavior">
|
||||||
|
<option [ngValue]="0">Redirect GET</option>
|
||||||
|
<option [ngValue]="1">Form POST</option>
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="getClaimsFromUserInfoEndpoint"
|
||||||
|
formControlName="getClaimsFromUserInfoEndpoint"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="getClaimsFromUserInfoEndpoint">
|
||||||
|
{{ "getClaimsFromUserInfoEndpoint" | i18n }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="additionalScopes">{{ "additionalScopes" | i18n }}</label>
|
||||||
|
<input class="form-control" formControlName="additionalScopes" id="additionalScopes" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="additionalUserIdClaimTypes">{{ "additionalUserIdClaimTypes" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
formControlName="additionalUserIdClaimTypes"
|
||||||
|
id="additionalUserIdClaimTypes"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="additionalEmailClaimTypes">{{ "additionalEmailClaimTypes" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
formControlName="additionalEmailClaimTypes"
|
||||||
|
id="additionalEmailClaimTypes"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="additionalNameClaimTypes">{{ "additionalNameClaimTypes" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
formControlName="additionalNameClaimTypes"
|
||||||
|
id="additionalNameClaimTypes"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="acrValues">{{ "acrValues" | i18n }}</label>
|
||||||
|
<input class="form-control" formControlName="acrValues" id="acrValues" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="expectedReturnAcrValue">{{ "expectedReturnAcrValue" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
formControlName="expectedReturnAcrValue"
|
||||||
|
id="expectedReturnAcrValue"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="data.value.configType == 2">
|
||||||
|
<!-- SAML2 SP -->
|
||||||
|
<div class="config-section">
|
||||||
|
<h2>{{ "samlSpConfig" | i18n }}</h2>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ "spEntityId" | i18n }}</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control" readonly [value]="spEntityId" />
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||||
|
(click)="copy(spEntityId)"
|
||||||
|
>
|
||||||
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ "spMetadataUrl" | i18n }}</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control" readonly [value]="spMetadataUrl" />
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{ 'launch' | i18n }}"
|
||||||
|
(click)="launchUri(spMetadataUrl)"
|
||||||
|
>
|
||||||
|
<i class="fa fa-lg fa-external-link" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||||
|
(click)="copy(spMetadataUrl)"
|
||||||
|
>
|
||||||
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ "spAcsUrl" | i18n }}</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control" readonly [value]="spAcsUrl" />
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||||
|
(click)="copy(spAcsUrl)"
|
||||||
|
>
|
||||||
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="spNameIdFormat">{{ "spNameIdFormat" | i18n }}</label>
|
||||||
|
<select class="form-control" formControlName="spNameIdFormat" id="spNameIdFormat">
|
||||||
|
<option value="0">Not Configured</option>
|
||||||
|
<option value="1">Unspecified</option>
|
||||||
|
<option value="2">Email Address</option>
|
||||||
|
<option value="3">X.509 Subject Name</option>
|
||||||
|
<option value="4">Windows Domain Qualified Name</option>
|
||||||
|
<option value="5">Kerberos Principal Name</option>
|
||||||
|
<option value="6">Entity Identifier</option>
|
||||||
|
<option value="7">Persistent</option>
|
||||||
|
<option value="8">Transient</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="spOutboundSigningAlgorithm">{{ "spOutboundSigningAlgorithm" | i18n }}</label>
|
||||||
|
<select
|
||||||
|
class="form-control"
|
||||||
|
formControlName="spOutboundSigningAlgorithm"
|
||||||
|
id="spOutboundSigningAlgorithm"
|
||||||
|
>
|
||||||
|
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="spSigningBehavior">{{ "spSigningBehavior" | i18n }}</label>
|
||||||
|
<select class="form-control" formControlName="spSigningBehavior" id="spSigningBehavior">
|
||||||
|
<option value="0">If IdP Wants Authn Requests Signed</option>
|
||||||
|
<option value="1">Always</option>
|
||||||
|
<option value="3">Never</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="spMinIncomingSigningAlgorithm">{{
|
||||||
|
"spMinIncomingSigningAlgorithm" | i18n
|
||||||
|
}}</label>
|
||||||
|
<select
|
||||||
|
class="form-control"
|
||||||
|
formControlName="spMinIncomingSigningAlgorithm"
|
||||||
|
id="spMinIncomingSigningAlgorithm"
|
||||||
|
>
|
||||||
|
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="spWantAssertionsSigned"
|
||||||
|
formControlName="spWantAssertionsSigned"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="spWantAssertionsSigned">
|
||||||
|
{{ "spWantAssertionsSigned" | i18n }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="spValidateCertificates"
|
||||||
|
formControlName="spValidateCertificates"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="spValidateCertificates">
|
||||||
|
{{ "spValidateCertificates" | i18n }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- OIDC -->
|
<!-- SAML2 IDP -->
|
||||||
<div *ngIf="data.value.configType == 1">
|
<div class="config-section">
|
||||||
<div class="config-section">
|
<h2>{{ "samlIdpConfig" | i18n }}</h2>
|
||||||
<h2>{{'openIdConnectConfig' | i18n}}</h2>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{'callbackPath' | i18n}}</label>
|
<label for="idpEntityId">{{ "idpEntityId" | i18n }}</label>
|
||||||
<div class="input-group">
|
<input class="form-control" formControlName="idpEntityId" id="idpEntityId" />
|
||||||
<input class="form-control" readonly [value]="callbackPath">
|
</div>
|
||||||
<div class="input-group-append">
|
<div class="form-group">
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
<label for="idpBindingType">{{ "idpBindingType" | i18n }}</label>
|
||||||
appA11yTitle="{{'copyValue' | i18n}}"
|
<select class="form-control" formControlName="idpBindingType" id="idpBindingType">
|
||||||
(click)="copy(callbackPath)">
|
<option value="1">Redirect</option>
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
<option value="2">HTTP POST</option>
|
||||||
</button>
|
<option value="4">Artifact</option>
|
||||||
</div>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label for="idpSingleSignOnServiceUrl">{{ "idpSingleSignOnServiceUrl" | i18n }}</label>
|
||||||
<label>{{'signedOutCallbackPath' | i18n}}</label>
|
<input
|
||||||
<div class="input-group">
|
class="form-control"
|
||||||
<input class="form-control" readonly [value]="signedOutCallbackPath">
|
formControlName="idpSingleSignOnServiceUrl"
|
||||||
<div class="input-group-append">
|
id="idpSingleSignOnServiceUrl"
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
/>
|
||||||
appA11yTitle="{{'copyValue' | i18n}}"
|
</div>
|
||||||
(click)="copy(signedOutCallbackPath)">
|
<div class="form-group">
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
<label for="idpSingleLogoutServiceUrl">{{ "idpSingleLogoutServiceUrl" | i18n }}</label>
|
||||||
</button>
|
<input
|
||||||
</div>
|
class="form-control"
|
||||||
</div>
|
formControlName="idpSingleLogoutServiceUrl"
|
||||||
</div>
|
id="idpSingleLogoutServiceUrl"
|
||||||
<div class="form-group">
|
/>
|
||||||
<label for="authority">{{'authority' | i18n}}</label>
|
</div>
|
||||||
<input class="form-control" formControlName="authority" id="authority">
|
<div class="form-group">
|
||||||
</div>
|
<label for="idpArtifactResolutionServiceUrl">{{
|
||||||
<div class="form-group">
|
"idpArtifactResolutionServiceUrl" | i18n
|
||||||
<label for="clientId">{{'clientId' | i18n}}</label>
|
}}</label>
|
||||||
<input class="form-control" formControlName="clientId" id="clientId">
|
<input
|
||||||
</div>
|
class="form-control"
|
||||||
<div class="form-group">
|
formControlName="idpArtifactResolutionServiceUrl"
|
||||||
<label for="clientSecret">{{'clientSecret' | i18n}}</label>
|
id="idpArtifactResolutionServiceUrl"
|
||||||
<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="idpX509PublicCert">{{ "idpX509PublicCert" | i18n }}</label>
|
||||||
<input class="form-control" formControlName="metadataAddress" id="metadataAddress">
|
<textarea
|
||||||
</div>
|
formControlName="idpX509PublicCert"
|
||||||
<div class="form-group">
|
class="form-control form-control-sm text-monospace"
|
||||||
<label for="redirectBehavior">{{'oidcRedirectBehavior' | i18n}}</label>
|
rows="6"
|
||||||
<select class="form-control" formControlName="redirectBehavior" id="redirectBehavior">
|
id="idpX509PublicCert"
|
||||||
<option [ngValue]="0">Redirect GET</option>
|
></textarea>
|
||||||
<option [ngValue]="1">Form POST</option>
|
</div>
|
||||||
</select>
|
<div class="form-group">
|
||||||
</div>
|
<label for="idpOutboundSigningAlgorithm">{{ "idpOutboundSigningAlgorithm" | i18n }}</label>
|
||||||
<div class="form-group">
|
<select
|
||||||
<div class="form-check">
|
class="form-control"
|
||||||
<input class="form-check-input" type="checkbox" id="getClaimsFromUserInfoEndpoint"
|
formControlName="idpOutboundSigningAlgorithm"
|
||||||
formControlName="getClaimsFromUserInfoEndpoint">
|
id="idpOutboundSigningAlgorithm"
|
||||||
<label class="form-check-label" for="getClaimsFromUserInfoEndpoint">
|
>
|
||||||
{{'getClaimsFromUserInfoEndpoint' | i18n}}
|
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
|
||||||
</label>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<div class="form-check">
|
||||||
<label for="additionalScopes">{{'additionalScopes' | i18n}}</label>
|
<input
|
||||||
<input class="form-control" formControlName="additionalScopes" id="additionalScopes">
|
class="form-check-input"
|
||||||
</div>
|
type="checkbox"
|
||||||
<div class="form-group">
|
id="idpAllowUnsolicitedAuthnResponse"
|
||||||
<label for="additionalUserIdClaimTypes">{{'additionalUserIdClaimTypes' | i18n}}</label>
|
formControlName="idpAllowUnsolicitedAuthnResponse"
|
||||||
<input class="form-control" formControlName="additionalUserIdClaimTypes"
|
/>
|
||||||
id="additionalUserIdClaimTypes">
|
<label class="form-check-label" for="idpAllowUnsolicitedAuthnResponse">
|
||||||
</div>
|
{{ "idpAllowUnsolicitedAuthnResponse" | i18n }}
|
||||||
<div class="form-group">
|
</label>
|
||||||
<label for="additionalEmailClaimTypes">{{'additionalEmailClaimTypes' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="additionalEmailClaimTypes"
|
|
||||||
id="additionalEmailClaimTypes">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="additionalNameClaimTypes">{{'additionalNameClaimTypes' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="additionalNameClaimTypes"
|
|
||||||
id="additionalNameClaimTypes">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="acrValues">{{'acrValues' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="acrValues" id="acrValues">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="expectedReturnAcrValue">{{'expectedReturnAcrValue' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="expectedReturnAcrValue" id="expectedReturnAcrValue">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="idpDisableOutboundLogoutRequests"
|
||||||
|
formControlName="idpDisableOutboundLogoutRequests"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="idpDisableOutboundLogoutRequests">
|
||||||
|
{{ "idpDisableOutboundLogoutRequests" | i18n }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="idpWantAuthnRequestsSigned"
|
||||||
|
formControlName="idpWantAuthnRequestsSigned"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="idpWantAuthnRequestsSigned">
|
||||||
|
{{ "idpWantAuthnRequestsSigned" | i18n }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div *ngIf="data.value.configType == 2">
|
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||||
<!-- SAML2 SP -->
|
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||||
<div class="config-section">
|
<span>{{ "save" | i18n }}</span>
|
||||||
<h2>{{'samlSpConfig' | i18n}}</h2>
|
</button>
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'spEntityId' | i18n}}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input class="form-control" readonly [value]="spEntityId" >
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{'copyValue' | i18n}}"
|
|
||||||
(click)="copy(spEntityId)">
|
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'spMetadataUrl' | i18n}}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input class="form-control" readonly [value]="spMetadataUrl">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{'launch' | i18n}}"
|
|
||||||
(click)="launchUri(spMetadataUrl)">
|
|
||||||
<i class="fa fa-lg fa-external-link" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{'copyValue' | i18n}}"
|
|
||||||
(click)="copy(spMetadataUrl)">
|
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{'spAcsUrl' | i18n}}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input class="form-control" readonly [value]="spAcsUrl">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{'copyValue' | i18n}}"
|
|
||||||
(click)="copy(spAcsUrl)">
|
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="spNameIdFormat">{{'spNameIdFormat' | i18n}}</label>
|
|
||||||
<select class="form-control" formControlName="spNameIdFormat" id="spNameIdFormat">
|
|
||||||
<option value="0">Not Configured</option>
|
|
||||||
<option value="1">Unspecified</option>
|
|
||||||
<option value="2">Email Address</option>
|
|
||||||
<option value="3">X.509 Subject Name</option>
|
|
||||||
<option value="4">Windows Domain Qualified Name</option>
|
|
||||||
<option value="5">Kerberos Principal Name</option>
|
|
||||||
<option value="6">Entity Identifier</option>
|
|
||||||
<option value="7">Persistent</option>
|
|
||||||
<option value="8">Transient</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="spOutboundSigningAlgorithm">{{'spOutboundSigningAlgorithm' | i18n}}</label>
|
|
||||||
<select class="form-control" formControlName="spOutboundSigningAlgorithm"
|
|
||||||
id="spOutboundSigningAlgorithm">
|
|
||||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{o}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="spSigningBehavior">{{'spSigningBehavior' | i18n}}</label>
|
|
||||||
<select class="form-control" formControlName="spSigningBehavior" id="spSigningBehavior">
|
|
||||||
<option value="0">If IdP Wants Authn Requests Signed</option>
|
|
||||||
<option value="1">Always</option>
|
|
||||||
<option value="3">Never</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="spMinIncomingSigningAlgorithm">{{'spMinIncomingSigningAlgorithm' | i18n}}</label>
|
|
||||||
<select class="form-control" formControlName="spMinIncomingSigningAlgorithm"
|
|
||||||
id="spMinIncomingSigningAlgorithm">
|
|
||||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{o}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="spWantAssertionsSigned"
|
|
||||||
formControlName="spWantAssertionsSigned">
|
|
||||||
<label class="form-check-label" for="spWantAssertionsSigned">
|
|
||||||
{{'spWantAssertionsSigned' | i18n}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="spValidateCertificates"
|
|
||||||
formControlName="spValidateCertificates">
|
|
||||||
<label class="form-check-label" for="spValidateCertificates">
|
|
||||||
{{'spValidateCertificates' | i18n}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- SAML2 IDP -->
|
|
||||||
<div class="config-section">
|
|
||||||
<h2>{{'samlIdpConfig' | i18n}}</h2>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="idpEntityId">{{'idpEntityId' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="idpEntityId" id="idpEntityId">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="idpBindingType">{{'idpBindingType' | i18n}}</label>
|
|
||||||
<select class="form-control" formControlName="idpBindingType" id="idpBindingType">
|
|
||||||
<option value="1">Redirect</option>
|
|
||||||
<option value="2">HTTP POST</option>
|
|
||||||
<option value="4">Artifact</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="idpSingleSignOnServiceUrl">{{'idpSingleSignOnServiceUrl' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="idpSingleSignOnServiceUrl" id="idpSingleSignOnServiceUrl">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="idpSingleLogoutServiceUrl">{{'idpSingleLogoutServiceUrl' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="idpSingleLogoutServiceUrl" id="idpSingleLogoutServiceUrl">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="idpArtifactResolutionServiceUrl">{{'idpArtifactResolutionServiceUrl' | i18n}}</label>
|
|
||||||
<input class="form-control" formControlName="idpArtifactResolutionServiceUrl"
|
|
||||||
id="idpArtifactResolutionServiceUrl">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="idpX509PublicCert">{{'idpX509PublicCert' | i18n}}</label>
|
|
||||||
<textarea formControlName="idpX509PublicCert" class="form-control form-control-sm text-monospace"
|
|
||||||
rows="6" id="idpX509PublicCert"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="idpOutboundSigningAlgorithm">{{'idpOutboundSigningAlgorithm' | i18n}}</label>
|
|
||||||
<select class="form-control" formControlName="idpOutboundSigningAlgorithm"
|
|
||||||
id="idpOutboundSigningAlgorithm">
|
|
||||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{o}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="idpAllowUnsolicitedAuthnResponse"
|
|
||||||
formControlName="idpAllowUnsolicitedAuthnResponse">
|
|
||||||
<label class="form-check-label" for="idpAllowUnsolicitedAuthnResponse">
|
|
||||||
{{'idpAllowUnsolicitedAuthnResponse' | i18n}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="idpDisableOutboundLogoutRequests"
|
|
||||||
formControlName="idpDisableOutboundLogoutRequests">
|
|
||||||
<label class="form-check-label" for="idpDisableOutboundLogoutRequests">
|
|
||||||
{{'idpDisableOutboundLogoutRequests' | i18n}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="idpWantAuthnRequestsSigned"
|
|
||||||
formControlName="idpWantAuthnRequestsSigned">
|
|
||||||
<label class="form-check-label" for="idpWantAuthnRequestsSigned">
|
|
||||||
{{'idpWantAuthnRequestsSigned' | i18n}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
<span>{{'save' | i18n}}</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,180 +1,183 @@
|
|||||||
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 = [
|
||||||
|
"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-sha512",
|
||||||
|
"http://www.w3.org/2000/09/xmldsig#rsa-sha1",
|
||||||
|
];
|
||||||
|
|
||||||
samlSigningAlgorithms = [
|
loading = true;
|
||||||
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
|
organizationId: string;
|
||||||
'http://www.w3.org/2000/09/xmldsig#rsa-sha384',
|
organization: Organization;
|
||||||
'http://www.w3.org/2000/09/xmldsig#rsa-sha512',
|
formPromise: Promise<any>;
|
||||||
'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
|
|
||||||
];
|
|
||||||
|
|
||||||
loading = true;
|
callbackPath: string;
|
||||||
organizationId: string;
|
signedOutCallbackPath: string;
|
||||||
organization: Organization;
|
spEntityId: string;
|
||||||
formPromise: Promise<any>;
|
spMetadataUrl: string;
|
||||||
|
spAcsUrl: string;
|
||||||
|
|
||||||
callbackPath: string;
|
enabled = this.fb.control(false);
|
||||||
signedOutCallbackPath: string;
|
data = this.fb.group({
|
||||||
spEntityId: string;
|
configType: [],
|
||||||
spMetadataUrl: string;
|
|
||||||
spAcsUrl: string;
|
|
||||||
|
|
||||||
enabled = this.fb.control(false);
|
keyConnectorEnabled: [],
|
||||||
data = this.fb.group({
|
keyConnectorUrl: [],
|
||||||
configType: [],
|
|
||||||
|
|
||||||
keyConnectorEnabled: [],
|
// OpenId
|
||||||
keyConnectorUrl: [],
|
authority: [],
|
||||||
|
clientId: [],
|
||||||
|
clientSecret: [],
|
||||||
|
metadataAddress: [],
|
||||||
|
redirectBehavior: [],
|
||||||
|
getClaimsFromUserInfoEndpoint: [],
|
||||||
|
additionalScopes: [],
|
||||||
|
additionalUserIdClaimTypes: [],
|
||||||
|
additionalEmailClaimTypes: [],
|
||||||
|
additionalNameClaimTypes: [],
|
||||||
|
acrValues: [],
|
||||||
|
expectedReturnAcrValue: [],
|
||||||
|
|
||||||
// OpenId
|
// SAML
|
||||||
authority: [],
|
spNameIdFormat: [],
|
||||||
clientId: [],
|
spOutboundSigningAlgorithm: [],
|
||||||
clientSecret: [],
|
spSigningBehavior: [],
|
||||||
metadataAddress: [],
|
spMinIncomingSigningAlgorithm: [],
|
||||||
redirectBehavior: [],
|
spWantAssertionsSigned: [],
|
||||||
getClaimsFromUserInfoEndpoint: [],
|
spValidateCertificates: [],
|
||||||
additionalScopes: [],
|
|
||||||
additionalUserIdClaimTypes: [],
|
|
||||||
additionalEmailClaimTypes: [],
|
|
||||||
additionalNameClaimTypes: [],
|
|
||||||
acrValues: [],
|
|
||||||
expectedReturnAcrValue: [],
|
|
||||||
|
|
||||||
// SAML
|
idpEntityId: [],
|
||||||
spNameIdFormat: [],
|
idpBindingType: [],
|
||||||
spOutboundSigningAlgorithm: [],
|
idpSingleSignOnServiceUrl: [],
|
||||||
spSigningBehavior: [],
|
idpSingleLogoutServiceUrl: [],
|
||||||
spMinIncomingSigningAlgorithm: [],
|
idpArtifactResolutionServiceUrl: [],
|
||||||
spWantAssertionsSigned: [],
|
idpX509PublicCert: [],
|
||||||
spValidateCertificates: [],
|
idpOutboundSigningAlgorithm: [],
|
||||||
|
idpAllowUnsolicitedAuthnResponse: [],
|
||||||
|
idpDisableOutboundLogoutRequests: [],
|
||||||
|
idpWantAuthnRequestsSigned: [],
|
||||||
|
});
|
||||||
|
|
||||||
idpEntityId: [],
|
constructor(
|
||||||
idpBindingType: [],
|
private fb: FormBuilder,
|
||||||
idpSingleSignOnServiceUrl: [],
|
private route: ActivatedRoute,
|
||||||
idpSingleLogoutServiceUrl: [],
|
private apiService: ApiService,
|
||||||
idpArtifactResolutionServiceUrl: [],
|
private platformUtilsService: PlatformUtilsService,
|
||||||
idpX509PublicCert: [],
|
private i18nService: I18nService,
|
||||||
idpOutboundSigningAlgorithm: [],
|
private organizationService: OrganizationService
|
||||||
idpAllowUnsolicitedAuthnResponse: [],
|
) {}
|
||||||
idpDisableOutboundLogoutRequests: [],
|
|
||||||
idpWantAuthnRequestsSigned: [],
|
async ngOnInit() {
|
||||||
|
this.route.parent.parent.params.subscribe(async (params) => {
|
||||||
|
this.organizationId = params.organizationId;
|
||||||
|
await this.load();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private fb: FormBuilder, private route: ActivatedRoute, private apiService: ApiService,
|
async load() {
|
||||||
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
|
this.organization = await this.organizationService.get(this.organizationId);
|
||||||
private organizationService: OrganizationService) { }
|
const ssoSettings = await this.apiService.getOrganizationSso(this.organizationId);
|
||||||
|
|
||||||
async ngOnInit() {
|
this.data.patchValue(ssoSettings.data);
|
||||||
this.route.parent.parent.params.subscribe(async params => {
|
this.enabled.setValue(ssoSettings.enabled);
|
||||||
this.organizationId = params.organizationId;
|
|
||||||
await this.load();
|
this.callbackPath = ssoSettings.urls.callbackPath;
|
||||||
});
|
this.signedOutCallbackPath = ssoSettings.urls.signedOutCallbackPath;
|
||||||
|
this.spEntityId = ssoSettings.urls.spEntityId;
|
||||||
|
this.spMetadataUrl = ssoSettings.urls.spMetadataUrl;
|
||||||
|
this.spAcsUrl = ssoSettings.urls.spAcsUrl;
|
||||||
|
|
||||||
|
this.keyConnectorUrl.markAsDirty();
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(value: string) {
|
||||||
|
this.platformUtilsService.copyToClipboard(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
launchUri(url: string) {
|
||||||
|
this.platformUtilsService.launchUri(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
this.formPromise = this.postData();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.formPromise;
|
||||||
|
|
||||||
|
this.data.patchValue(response.data);
|
||||||
|
this.enabled.setValue(response.enabled);
|
||||||
|
|
||||||
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("ssoSettingsSaved"));
|
||||||
|
} catch {
|
||||||
|
// Logged by appApiAction, do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
this.formPromise = null;
|
||||||
this.organization = await this.organizationService.get(this.organizationId);
|
}
|
||||||
const ssoSettings = await this.apiService.getOrganizationSso(this.organizationId);
|
|
||||||
|
|
||||||
this.data.patchValue(ssoSettings.data);
|
async postData() {
|
||||||
this.enabled.setValue(ssoSettings.enabled);
|
if (this.data.get("keyConnectorEnabled").value) {
|
||||||
|
await this.validateKeyConnectorUrl();
|
||||||
|
|
||||||
this.callbackPath = ssoSettings.urls.callbackPath;
|
if (this.keyConnectorUrl.hasError("invalidUrl")) {
|
||||||
this.signedOutCallbackPath = ssoSettings.urls.signedOutCallbackPath;
|
throw new Error(this.i18nService.t("keyConnectorTestFail"));
|
||||||
this.spEntityId = ssoSettings.urls.spEntityId;
|
}
|
||||||
this.spMetadataUrl = ssoSettings.urls.spMetadataUrl;
|
|
||||||
this.spAcsUrl = ssoSettings.urls.spAcsUrl;
|
|
||||||
|
|
||||||
this.keyConnectorUrl.markAsDirty();
|
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(value: string) {
|
const request = new OrganizationSsoRequest();
|
||||||
this.platformUtilsService.copyToClipboard(value);
|
request.enabled = this.enabled.value;
|
||||||
|
request.data = this.data.value;
|
||||||
|
|
||||||
|
return this.apiService.postOrganizationSso(this.organizationId, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateKeyConnectorUrl() {
|
||||||
|
if (this.keyConnectorUrl.pristine) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
launchUri(url: string) {
|
this.keyConnectorUrl.markAsPending();
|
||||||
this.platformUtilsService.launchUri(url);
|
|
||||||
|
try {
|
||||||
|
await this.apiService.getKeyConnectorAlive(this.keyConnectorUrl.value);
|
||||||
|
this.keyConnectorUrl.updateValueAndValidity();
|
||||||
|
} catch {
|
||||||
|
this.keyConnectorUrl.setErrors({
|
||||||
|
invalidUrl: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
this.keyConnectorUrl.markAsPristine();
|
||||||
this.formPromise = this.postData();
|
}
|
||||||
|
|
||||||
try {
|
get enableTestKeyConnector() {
|
||||||
const response = await this.formPromise;
|
return (
|
||||||
|
this.data.get("keyConnectorEnabled").value &&
|
||||||
|
this.keyConnectorUrl != null &&
|
||||||
|
this.keyConnectorUrl.value !== ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.data.patchValue(response.data);
|
get keyConnectorUrl() {
|
||||||
this.enabled.setValue(response.enabled);
|
return this.data.get("keyConnectorUrl");
|
||||||
|
}
|
||||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('ssoSettingsSaved'));
|
|
||||||
} catch {
|
|
||||||
// Logged by appApiAction, do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
this.formPromise = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async postData() {
|
|
||||||
if (this.data.get('keyConnectorEnabled').value) {
|
|
||||||
await this.validateKeyConnectorUrl();
|
|
||||||
|
|
||||||
if (this.keyConnectorUrl.hasError('invalidUrl')) {
|
|
||||||
throw new Error(this.i18nService.t('keyConnectorTestFail'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = new OrganizationSsoRequest();
|
|
||||||
request.enabled = this.enabled.value;
|
|
||||||
request.data = this.data.value;
|
|
||||||
|
|
||||||
return this.apiService.postOrganizationSso(this.organizationId, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
async validateKeyConnectorUrl() {
|
|
||||||
if (this.keyConnectorUrl.pristine) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.keyConnectorUrl.markAsPending();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.apiService.getKeyConnectorAlive(this.keyConnectorUrl.value);
|
|
||||||
this.keyConnectorUrl.updateValueAndValidity();
|
|
||||||
} catch {
|
|
||||||
this.keyConnectorUrl.setErrors({
|
|
||||||
invalidUrl: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.keyConnectorUrl.markAsPristine();
|
|
||||||
}
|
|
||||||
|
|
||||||
get enableTestKeyConnector() {
|
|
||||||
return this.data.get('keyConnectorEnabled').value &&
|
|
||||||
this.keyConnectorUrl != null &&
|
|
||||||
this.keyConnectorUrl.value !== '';
|
|
||||||
}
|
|
||||||
|
|
||||||
get keyConnectorUrl() {
|
|
||||||
return this.data.get('keyConnectorUrl');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,54 @@
|
|||||||
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: [
|
||||||
|
{
|
||||||
|
path: "manage",
|
||||||
|
component: ManageComponent,
|
||||||
|
canActivate: [OrganizationTypeGuardService],
|
||||||
|
data: {
|
||||||
|
permissions: [
|
||||||
|
Permissions.CreateNewCollections,
|
||||||
|
Permissions.EditAnyCollection,
|
||||||
|
Permissions.DeleteAnyCollection,
|
||||||
|
Permissions.EditAssignedCollections,
|
||||||
|
Permissions.DeleteAssignedCollections,
|
||||||
|
Permissions.AccessEventLogs,
|
||||||
|
Permissions.ManageGroups,
|
||||||
|
Permissions.ManageUsers,
|
||||||
|
Permissions.ManagePolicies,
|
||||||
|
Permissions.ManageSso,
|
||||||
|
],
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'manage',
|
path: "sso",
|
||||||
component: ManageComponent,
|
component: SsoComponent,
|
||||||
canActivate: [OrganizationTypeGuardService],
|
},
|
||||||
data: {
|
|
||||||
permissions: [
|
|
||||||
Permissions.CreateNewCollections,
|
|
||||||
Permissions.EditAnyCollection,
|
|
||||||
Permissions.DeleteAnyCollection,
|
|
||||||
Permissions.EditAssignedCollections,
|
|
||||||
Permissions.DeleteAssignedCollections,
|
|
||||||
Permissions.AccessEventLogs,
|
|
||||||
Permissions.ManageGroups,
|
|
||||||
Permissions.ManageUsers,
|
|
||||||
Permissions.ManagePolicies,
|
|
||||||
Permissions.ManageSso,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'sso',
|
|
||||||
component: SsoComponent,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class OrganizationsRoutingModule { }
|
export class OrganizationsRoutingModule {}
|
||||||
|
|||||||
@@ -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 {}
|
||||||
|
|||||||
@@ -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"
|
||||||
</div>
|
type="checkbox"
|
||||||
|
id="enabled"
|
||||||
|
[formControl]="enabled"
|
||||||
|
name="Enabled"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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 {}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,27 +1,47 @@
|
|||||||
<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"
|
||||||
</div>
|
type="checkbox"
|
||||||
|
id="enabled"
|
||||||
|
[formControl]="enabled"
|
||||||
|
name="Enabled"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
|
||||||
|
</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"
|
||||||
</div>
|
class="form-control"
|
||||||
<div class="col-6">
|
type="number"
|
||||||
<input id="minutes" class="form-control" type="number" min="0" max="59" name="minutes"
|
min="0"
|
||||||
formControlName="minutes">
|
name="hours"
|
||||||
<small>{{'minutes' | i18n }}</small>
|
formControlName="hours"
|
||||||
</div>
|
/>
|
||||||
</div>
|
<small>{{ "hours" | i18n }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<input
|
||||||
|
id="minutes"
|
||||||
|
class="form-control"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="59"
|
||||||
|
name="minutes"
|
||||||
|
formControlName="minutes"
|
||||||
|
/>
|
||||||
|
<small>{{ "minutes" | i18n }}</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,70 +1,72 @@
|
|||||||
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({
|
||||||
|
hours: [null],
|
||||||
|
minutes: [null],
|
||||||
|
});
|
||||||
|
|
||||||
data = this.fb.group({
|
constructor(private fb: FormBuilder, private i18nService: I18nService) {
|
||||||
hours: [null],
|
super();
|
||||||
minutes: [null],
|
}
|
||||||
|
|
||||||
|
loadData() {
|
||||||
|
const minutes = this.policyResponse.data?.minutes;
|
||||||
|
|
||||||
|
if (minutes == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data.patchValue({
|
||||||
|
hours: Math.floor(minutes / 60),
|
||||||
|
minutes: minutes % 60,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private fb: FormBuilder, private i18nService: I18nService) {
|
buildRequestData() {
|
||||||
super();
|
if (this.data.value.hours == null && this.data.value.minutes == null) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadData() {
|
return {
|
||||||
const minutes = this.policyResponse.data?.minutes;
|
minutes: this.data.value.hours * 60 + this.data.value.minutes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (minutes == null) {
|
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
|
||||||
return;
|
const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false;
|
||||||
}
|
if (this.enabled.value && !singleOrgEnabled) {
|
||||||
|
throw new Error(this.i18nService.t("requireSsoPolicyReqError"));
|
||||||
this.data.patchValue({
|
|
||||||
hours: Math.floor(minutes / 60),
|
|
||||||
minutes: minutes % 60,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildRequestData() {
|
const data = this.buildRequestData();
|
||||||
if (this.data.value.hours == null && this.data.value.minutes == null) {
|
if (data?.minutes == null || data?.minutes <= 0) {
|
||||||
return null;
|
throw new Error(this.i18nService.t("invalidMaximumVaultTimeout"));
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
minutes: this.data.value.hours * 60 + this.data.value.minutes,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
|
return super.buildRequest(policiesEnabledMap);
|
||||||
const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false;
|
}
|
||||||
if (this.enabled.value && !singleOrgEnabled) {
|
|
||||||
throw new Error(this.i18nService.t('requireSsoPolicyReqError'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = this.buildRequestData();
|
|
||||||
if (data?.minutes == null || data?.minutes <= 0) {
|
|
||||||
throw new Error(this.i18nService.t('invalidMaximumVaultTimeout'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.buildRequest(policiesEnabledMap);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,46 @@
|
|||||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="addTitle">
|
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="addTitle">
|
||||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||||
<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
|
||||||
<span aria-hidden="true">×</span>
|
type="button"
|
||||||
</button>
|
class="close"
|
||||||
</div>
|
data-dismiss="modal"
|
||||||
<div class="modal-body">
|
appA11yTitle="{{ 'close' | i18n }}"
|
||||||
<div class="card-body text-center" *ngIf="loading">
|
>
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
<span aria-hidden="true">×</span>
|
||||||
{{'loading' | i18n}}
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="!loading">
|
<div class="modal-body">
|
||||||
<table class="table table-hover table-list">
|
<div class="card-body text-center" *ngIf="loading">
|
||||||
<tr *ngFor="let o of organizations">
|
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||||
<td width="30">
|
{{ "loading" | i18n }}
|
||||||
<app-avatar [data]="o.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{o.name}}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button class="btn btn-outline-secondary pull-right" (click)="add(o)" [disabled]="formPromise">Add</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ng-container *ngIf="!loading">
|
||||||
|
<table class="table table-hover table-list">
|
||||||
|
<tr *ngFor="let o of organizations">
|
||||||
|
<td width="30">
|
||||||
|
<app-avatar [data]="o.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ o.name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-secondary pull-right"
|
||||||
|
(click)="add(o)"
|
||||||
|
[disabled]="formPromise"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,82 +1,86 @@
|
|||||||
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() organizations: Organization[];
|
||||||
|
@Output() onAddedOrganization = new EventEmitter();
|
||||||
|
|
||||||
@Input() providerId: string;
|
provider: Provider;
|
||||||
@Input() organizations: Organization[];
|
formPromise: Promise<any>;
|
||||||
@Output() onAddedOrganization = new EventEmitter();
|
loading = true;
|
||||||
|
|
||||||
provider: Provider;
|
constructor(
|
||||||
formPromise: Promise<any>;
|
private providerService: ProviderService,
|
||||||
loading = true;
|
private webProviderService: WebProviderService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private validationService: ValidationService
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor(
|
async ngOnInit() {
|
||||||
private providerService: ProviderService,
|
await this.load();
|
||||||
private webProviderService: WebProviderService,
|
}
|
||||||
private i18nService: I18nService,
|
|
||||||
private platformUtilsService: PlatformUtilsService,
|
|
||||||
private validationService: ValidationService
|
|
||||||
) { }
|
|
||||||
|
|
||||||
async ngOnInit() {
|
async load() {
|
||||||
await this.load();
|
if (this.providerId == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
this.provider = await this.providerService.get(this.providerId);
|
||||||
if (this.providerId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.provider = await this.providerService.get(this.providerId);
|
this.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
this.loading = false;
|
async add(organization: Organization) {
|
||||||
|
if (this.formPromise) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async add(organization: Organization) {
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
if (this.formPromise) {
|
this.i18nService.t("addOrganizationConfirmation", organization.name, this.provider.name),
|
||||||
return;
|
organization.name,
|
||||||
}
|
this.i18nService.t("yes"),
|
||||||
|
this.i18nService.t("no"),
|
||||||
|
"warning"
|
||||||
|
);
|
||||||
|
|
||||||
const confirmed = await this.platformUtilsService.showDialog(
|
if (!confirmed) {
|
||||||
this.i18nService.t('addOrganizationConfirmation', organization.name, this.provider.name), organization.name,
|
return false;
|
||||||
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.formPromise = this.webProviderService.addOrganizationToProvider(this.providerId, organization.id);
|
|
||||||
await this.formPromise;
|
|
||||||
} catch (e) {
|
|
||||||
this.validationService.showError(e);
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
this.formPromise = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('organizationJoinedProvider'));
|
|
||||||
this.onAddedOrganization.emit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.formPromise = this.webProviderService.addOrganizationToProvider(
|
||||||
|
this.providerId,
|
||||||
|
organization.id
|
||||||
|
);
|
||||||
|
await this.formPromise;
|
||||||
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
this.formPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("organizationJoinedProvider")
|
||||||
|
);
|
||||||
|
this.onAddedOrganization.emit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +1,86 @@
|
|||||||
<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"
|
||||||
</div>
|
class="form-control form-control-sm"
|
||||||
<a class="btn btn-sm btn-outline-primary ml-3" routerLink="create" *ngIf="manageOrganizations">
|
id="search"
|
||||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
|
placeholder="{{ 'search' | i18n }}"
|
||||||
{{'newClientOrganization' | i18n}}
|
[(ngModel)]="searchText"
|
||||||
</a>
|
/>
|
||||||
<button 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>
|
|
||||||
{{'addExistingOrganization' | i18n}}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<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>
|
||||||
|
{{ "newClientOrganization" | i18n }}
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
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>
|
||||||
|
{{ "addExistingOrganization" | i18n }}
|
||||||
|
</button>
|
||||||
|
</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>
|
>
|
||||||
<ng-container *ngIf="searchedClients.length">
|
<p *ngIf="!searchedClients.length">{{ "noClientsInList" | i18n }}</p>
|
||||||
<table class="table table-hover table-list" infiniteScroll [infiniteScrollDistance]="1"
|
<ng-container *ngIf="searchedClients.length">
|
||||||
[infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
|
<table
|
||||||
<tbody>
|
class="table table-hover table-list"
|
||||||
<tr *ngFor="let o of searchedClients">
|
infiniteScroll
|
||||||
<td width="30">
|
[infiniteScrollDistance]="1"
|
||||||
<app-avatar [data]="o.organizationName" size="25" [circle]="true" [fontSize]="14"></app-avatar>
|
[infiniteScrollDisabled]="!isPaging()"
|
||||||
</td>
|
(scrolled)="loadMore()"
|
||||||
<td>
|
>
|
||||||
<a [routerLink]="['/organizations', o.organizationId]">{{o.organizationName}}</a>
|
<tbody>
|
||||||
</td>
|
<tr *ngFor="let o of searchedClients">
|
||||||
<td class="table-list-options" *ngIf="manageOrganizations">
|
<td width="30">
|
||||||
<div class="dropdown" appListDropdown>
|
<app-avatar
|
||||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
|
[data]="o.organizationName"
|
||||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
size="25"
|
||||||
appA11yTitle="{{'options' | i18n}}">
|
[circle]="true"
|
||||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
[fontSize]="14"
|
||||||
</button>
|
></app-avatar>
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
</td>
|
||||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(o)">
|
<td>
|
||||||
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
|
<a [routerLink]="['/organizations', o.organizationId]">{{ o.organizationName }}</a>
|
||||||
{{'remove' | i18n}}
|
</td>
|
||||||
</a>
|
<td class="table-list-options" *ngIf="manageOrganizations">
|
||||||
</div>
|
<div class="dropdown" appListDropdown>
|
||||||
</div>
|
<button
|
||||||
</td>
|
class="btn btn-outline-secondary dropdown-toggle"
|
||||||
</tr>
|
type="button"
|
||||||
</tbody>
|
data-toggle="dropdown"
|
||||||
</table>
|
aria-haspopup="true"
|
||||||
</ng-container>
|
aria-expanded="false"
|
||||||
|
appA11yTitle="{{ 'options' | i18n }}"
|
||||||
|
>
|
||||||
|
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(o)">
|
||||||
|
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
|
||||||
|
{{ "remove" | i18n }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-template #add></ng-template>
|
<ng-template #add></ng-template>
|
||||||
|
|||||||
@@ -1,167 +1,183 @@
|
|||||||
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;
|
||||||
|
searchText: string;
|
||||||
|
addableOrganizations: Organization[];
|
||||||
|
loading = true;
|
||||||
|
manageOrganizations = false;
|
||||||
|
showAddExisting = false;
|
||||||
|
|
||||||
providerId: any;
|
clients: ProviderOrganizationOrganizationDetailsResponse[];
|
||||||
searchText: string;
|
pagedClients: ProviderOrganizationOrganizationDetailsResponse[];
|
||||||
addableOrganizations: Organization[];
|
|
||||||
loading = true;
|
|
||||||
manageOrganizations = false;
|
|
||||||
showAddExisting = false;
|
|
||||||
|
|
||||||
clients: ProviderOrganizationOrganizationDetailsResponse[];
|
protected didScroll = false;
|
||||||
pagedClients: ProviderOrganizationOrganizationDetailsResponse[];
|
protected pageSize = 100;
|
||||||
|
protected actionPromise: Promise<any>;
|
||||||
|
private pagedClientsCount = 0;
|
||||||
|
|
||||||
protected didScroll = false;
|
constructor(
|
||||||
protected pageSize = 100;
|
private route: ActivatedRoute,
|
||||||
protected actionPromise: Promise<any>;
|
private providerService: ProviderService,
|
||||||
private pagedClientsCount = 0;
|
private apiService: ApiService,
|
||||||
|
private searchService: SearchService,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private validationService: ValidationService,
|
||||||
|
private webProviderService: WebProviderService,
|
||||||
|
private logService: LogService,
|
||||||
|
private modalService: ModalService,
|
||||||
|
private organizationService: OrganizationService
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor(
|
async ngOnInit() {
|
||||||
private route: ActivatedRoute,
|
this.route.parent.params.subscribe(async (params) => {
|
||||||
private providerService: ProviderService,
|
this.providerId = params.providerId;
|
||||||
private apiService: ApiService,
|
|
||||||
private searchService: SearchService,
|
|
||||||
private platformUtilsService: PlatformUtilsService,
|
|
||||||
private i18nService: I18nService,
|
|
||||||
private validationService: ValidationService,
|
|
||||||
private webProviderService: WebProviderService,
|
|
||||||
private logService: LogService,
|
|
||||||
private modalService: ModalService,
|
|
||||||
private organizationService: OrganizationService
|
|
||||||
) { }
|
|
||||||
|
|
||||||
async ngOnInit() {
|
await this.load();
|
||||||
this.route.parent.params.subscribe(async params => {
|
|
||||||
this.providerId = params.providerId;
|
|
||||||
|
|
||||||
|
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||||
|
this.searchText = qParams.search;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
const response = await this.apiService.getProviderClients(this.providerId);
|
||||||
|
this.clients = response.data != null && response.data.length > 0 ? response.data : [];
|
||||||
|
this.manageOrganizations =
|
||||||
|
(await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin;
|
||||||
|
const candidateOrgs = (await this.organizationService.getAll()).filter(
|
||||||
|
(o) => o.isOwner && o.providerId == null
|
||||||
|
);
|
||||||
|
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.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPaging() {
|
||||||
|
const searching = this.isSearching();
|
||||||
|
if (searching && this.didScroll) {
|
||||||
|
this.resetPaging();
|
||||||
|
}
|
||||||
|
return !searching && this.clients && this.clients.length > this.pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSearching() {
|
||||||
|
return this.searchService.isSearchable(this.searchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
async resetPaging() {
|
||||||
|
this.pagedClients = [];
|
||||||
|
this.loadMore();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMore() {
|
||||||
|
if (!this.clients || this.clients.length <= this.pageSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pagedLength = this.pagedClients.length;
|
||||||
|
let pagedSize = this.pageSize;
|
||||||
|
if (pagedLength === 0 && this.pagedClientsCount > this.pageSize) {
|
||||||
|
pagedSize = this.pagedClientsCount;
|
||||||
|
}
|
||||||
|
if (this.clients.length > pagedLength) {
|
||||||
|
this.pagedClients = this.pagedClients.concat(
|
||||||
|
this.clients.slice(pagedLength, pagedLength + pagedSize)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.pagedClientsCount = this.pagedClients.length;
|
||||||
|
this.didScroll = this.pagedClients.length > this.pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
async addExistingOrganization() {
|
||||||
|
const [modal] = await this.modalService.openViewRef(
|
||||||
|
AddOrganizationComponent,
|
||||||
|
this.addModalRef,
|
||||||
|
(comp) => {
|
||||||
|
comp.providerId = this.providerId;
|
||||||
|
comp.organizations = this.addableOrganizations;
|
||||||
|
comp.onAddedOrganization.subscribe(async () => {
|
||||||
|
try {
|
||||||
await this.load();
|
await this.load();
|
||||||
|
modal.close();
|
||||||
this.route.queryParams.pipe(first()).subscribe(async qParams => {
|
} catch (e) {
|
||||||
this.searchText = qParams.search;
|
this.logService.error(`Handled exception: ${e}`);
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(organization: ProviderOrganizationOrganizationDetailsResponse) {
|
||||||
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
|
this.i18nService.t("detachOrganizationConfirmation"),
|
||||||
|
organization.organizationName,
|
||||||
|
this.i18nService.t("yes"),
|
||||||
|
this.i18nService.t("no"),
|
||||||
|
"warning"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
this.actionPromise = this.webProviderService.detachOrganizastion(
|
||||||
const response = await this.apiService.getProviderClients(this.providerId);
|
this.providerId,
|
||||||
this.clients = response.data != null && response.data.length > 0 ? response.data : [];
|
organization.id
|
||||||
this.manageOrganizations = (await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin;
|
);
|
||||||
const candidateOrgs = (await this.organizationService.getAll()).filter(o => o.isOwner && o.providerId == null);
|
try {
|
||||||
const allowedOrgsIds = await Promise.all(candidateOrgs.map(o => this.apiService.getOrganization(o.id))).then(orgs =>
|
await this.actionPromise;
|
||||||
orgs.filter(o => !DisallowedPlanTypes.includes(o.planType))
|
this.platformUtilsService.showToast(
|
||||||
.map(o => o.id));
|
"success",
|
||||||
this.addableOrganizations = candidateOrgs.filter(o => allowedOrgsIds.includes(o.id));
|
null,
|
||||||
|
this.i18nService.t("detachedOrganization", organization.organizationName)
|
||||||
this.showAddExisting = this.addableOrganizations.length !== 0;
|
);
|
||||||
this.loading = false;
|
await this.load();
|
||||||
}
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
isPaging() {
|
|
||||||
const searching = this.isSearching();
|
|
||||||
if (searching && this.didScroll) {
|
|
||||||
this.resetPaging();
|
|
||||||
}
|
|
||||||
return !searching && this.clients && this.clients.length > this.pageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSearching() {
|
|
||||||
return this.searchService.isSearchable(this.searchText);
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetPaging() {
|
|
||||||
this.pagedClients = [];
|
|
||||||
this.loadMore();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
loadMore() {
|
|
||||||
if (!this.clients || this.clients.length <= this.pageSize) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pagedLength = this.pagedClients.length;
|
|
||||||
let pagedSize = this.pageSize;
|
|
||||||
if (pagedLength === 0 && this.pagedClientsCount > this.pageSize) {
|
|
||||||
pagedSize = this.pagedClientsCount;
|
|
||||||
}
|
|
||||||
if (this.clients.length > pagedLength) {
|
|
||||||
this.pagedClients = this.pagedClients.concat(this.clients.slice(pagedLength, pagedLength + pagedSize));
|
|
||||||
}
|
|
||||||
this.pagedClientsCount = this.pagedClients.length;
|
|
||||||
this.didScroll = this.pagedClients.length > this.pageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
async addExistingOrganization() {
|
|
||||||
const [modal] = await this.modalService.openViewRef(AddOrganizationComponent, this.addModalRef, comp => {
|
|
||||||
comp.providerId = this.providerId;
|
|
||||||
comp.organizations = this.addableOrganizations;
|
|
||||||
comp.onAddedOrganization.subscribe(async () => {
|
|
||||||
try {
|
|
||||||
await this.load();
|
|
||||||
modal.close();
|
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(`Handled exception: ${e}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(organization: ProviderOrganizationOrganizationDetailsResponse) {
|
|
||||||
const confirmed = await this.platformUtilsService.showDialog(
|
|
||||||
this.i18nService.t('detachOrganizationConfirmation'), organization.organizationName,
|
|
||||||
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.actionPromise = this.webProviderService.detachOrganizastion(this.providerId, organization.id);
|
|
||||||
try {
|
|
||||||
await this.actionPromise;
|
|
||||||
this.platformUtilsService.showToast('success', null,
|
|
||||||
this.i18nService.t('detachedOrganization', organization.organizationName));
|
|
||||||
await this.load();
|
|
||||||
} catch (e) {
|
|
||||||
this.validationService.showError(e);
|
|
||||||
}
|
|
||||||
this.actionPromise = null;
|
|
||||||
}
|
}
|
||||||
|
this.actionPromise = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,26 +1,23 @@
|
|||||||
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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,42 @@
|
|||||||
<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"
|
||||||
</p>
|
title="{{ 'loading' | i18n }}"
|
||||||
</div>
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</p>
|
||||||
|
</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 }"
|
||||||
</a>
|
class="btn btn-primary btn-block ml-2 mt-0"
|
||||||
</div>
|
>
|
||||||
</div>
|
{{ "createAccount" | i18n }}
|
||||||
</div>
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,48 +1,56 @@
|
|||||||
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,
|
||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
async authedHandler(qParams: any) {
|
async authedHandler(qParams: any) {
|
||||||
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) {
|
||||||
this.providerName = qParams.providerName;
|
this.providerName = qParams.providerName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,34 @@
|
|||||||
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) {
|
||||||
|
return user.status === ProviderUserStatusType.Accepted;
|
||||||
|
}
|
||||||
|
|
||||||
protected isAccepted(user: BulkUserDetails) {
|
protected async getPublicKeys() {
|
||||||
return user.status === ProviderUserStatusType.Accepted;
|
const request = new ProviderUserBulkRequest(this.filteredUsers.map((user) => user.id));
|
||||||
}
|
return await this.apiService.postProviderUsersPublicKey(this.providerId, request);
|
||||||
|
}
|
||||||
|
|
||||||
protected async getPublicKeys() {
|
protected getCryptoKey() {
|
||||||
const request = new ProviderUserBulkRequest(this.filteredUsers.map(user => user.id));
|
return this.cryptoService.getProviderKey(this.providerId);
|
||||||
return await this.apiService.postProviderUsersPublicKey(this.providerId, request);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected getCryptoKey() {
|
protected async postConfirmRequest(userIdsWithKeys: any[]) {
|
||||||
return this.cryptoService.getProviderKey(this.providerId);
|
const request = new ProviderUserBulkConfirmRequest(userIdsWithKeys);
|
||||||
}
|
return await this.apiService.postProviderUserBulkConfirm(this.providerId, request);
|
||||||
|
}
|
||||||
protected async postConfirmRequest(userIdsWithKeys: any[]) {
|
|
||||||
const request = new ProviderUserBulkConfirmRequest(userIdsWithKeys);
|
|
||||||
return await this.apiService.postProviderUserBulkConfirm(this.providerId, request);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
const request = new ProviderUserBulkRequest(this.users.map((user) => user.id));
|
||||||
async deleteUsers() {
|
return await this.apiService.deleteManyProviderUsers(this.providerId, request);
|
||||||
const request = new ProviderUserBulkRequest(this.users.map(user => user.id));
|
}
|
||||||
return await this.apiService.deleteManyProviderUsers(this.providerId, request);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
<span class="mx-2">-</span>
|
id="start"
|
||||||
<label class="sr-only" for="end">{{'endDate' | i18n}}</label>
|
placeholder="{{ 'startDate' | i18n }}"
|
||||||
<input type="datetime-local" class="form-control form-control-sm" id="end"
|
[(ngModel)]="start"
|
||||||
placeholder="{{'endDate' | i18n}}" [(ngModel)]="end" placeholder="YYYY-MM-DDTHH:MM"
|
placeholder="YYYY-MM-DDTHH:MM"
|
||||||
(change)="dirtyDates = true">
|
(change)="dirtyDates = true"
|
||||||
</div>
|
/>
|
||||||
<form #refreshForm [appApiAction]="refreshPromise" class="d-inline">
|
<span class="mx-2">-</span>
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="loadEvents(true)"
|
<label class="sr-only" for="end">{{ "endDate" | i18n }}</label>
|
||||||
[disabled]="loaded && refreshForm.loading">
|
<input
|
||||||
<i class="fa fa-refresh fa-fw" aria-hidden="true" [ngClass]="{'fa-spin': loaded && refreshForm.loading}"></i>
|
type="datetime-local"
|
||||||
{{'refresh' | i18n}}
|
class="form-control form-control-sm"
|
||||||
</button>
|
id="end"
|
||||||
</form>
|
placeholder="{{ 'endDate' | i18n }}"
|
||||||
<form #exportForm [appApiAction]="exportPromise" class="d-inline">
|
[(ngModel)]="end"
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary btn-submit manual ml-3"
|
placeholder="YYYY-MM-DDTHH:MM"
|
||||||
[ngClass]="{loading:exportForm.loading}" (click)="exportEvents()"
|
(change)="dirtyDates = true"
|
||||||
[disabled]="loaded && exportForm.loading || dirtyDates">
|
/>
|
||||||
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
|
|
||||||
<span>{{'export' | i18n}}</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
<form #refreshForm [appApiAction]="refreshPromise" class="d-inline">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm btn-outline-primary ml-3"
|
||||||
|
(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>
|
||||||
|
</form>
|
||||||
|
<form #exportForm [appApiAction]="exportPromise" class="d-inline">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
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>
|
||||||
|
<span>{{ "export" | i18n }}</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</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 }}"
|
||||||
</td>
|
title="{{ e.appName }}, {{ e.ip }}"
|
||||||
<td>
|
aria-hidden="true"
|
||||||
<span title="{{e.userEmail}}">{{e.userName}}</span>
|
></i>
|
||||||
</td>
|
<span class="sr-only">{{ e.appName }}, {{ e.ip }}</span>
|
||||||
<td [innerHTML]="e.message"></td>
|
</td>
|
||||||
</tr>
|
<td>
|
||||||
</tbody>
|
<span title="{{ e.userEmail }}">{{ e.userName }}</span>
|
||||||
</table>
|
</td>
|
||||||
<button #moreBtn [appApiAction]="morePromise" type="button" class="btn btn-block btn-link btn-submit"
|
<td [innerHTML]="e.message"></td>
|
||||||
(click)="loadEvents(false)" [disabled]="loaded && moreBtn.loading" *ngIf="continuationToken">
|
</tr>
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
</tbody>
|
||||||
<span>{{'loadMore' | i18n}}</span>
|
</table>
|
||||||
</button>
|
<button
|
||||||
|
#moreBtn
|
||||||
|
[appApiAction]="morePromise"
|
||||||
|
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>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -1,84 +1,82 @@
|
|||||||
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>();
|
||||||
private providerUsersIdMap = new Map<string, any>();
|
private providerUsersIdMap = new Map<string, any>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
eventService: EventService,
|
eventService: EventService,
|
||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
private providerService: ProviderService,
|
private providerService: ProviderService,
|
||||||
exportService: ExportService,
|
exportService: ExportService,
|
||||||
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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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 });
|
||||||
});
|
});
|
||||||
await this.loadEvents(true);
|
await this.loadEvents(true);
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,30 @@
|
|||||||
<div class="container page-content">
|
<div class="container page-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
<div class="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"
|
||||||
</a>
|
routerLinkActive="active"
|
||||||
<a routerLink="events" class="list-group-item" routerLinkActive="active"
|
*ngIf="provider.canManageUsers"
|
||||||
*ngIf="provider.canAccessEventLogs && accessEvents">
|
>
|
||||||
{{'eventLogs' | i18n}}
|
{{ "people" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
<a
|
||||||
</div>
|
routerLink="events"
|
||||||
</div>
|
class="list-group-item"
|
||||||
<div class="col-9">
|
routerLinkActive="active"
|
||||||
<router-outlet></router-outlet>
|
*ngIf="provider.canAccessEventLogs && accessEvents"
|
||||||
|
>
|
||||||
|
{{ "eventLogs" | i18n }}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-9">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,27 +1,24 @@
|
|||||||
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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,145 +1,220 @@
|
|||||||
<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 }"
|
||||||
</button>
|
(click)="filter(null)"
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
>
|
||||||
[ngClass]="{active: status == userStatusType.Invited}"
|
{{ "all" | i18n }}
|
||||||
(click)="filter(userStatusType.Invited)">
|
<span class="badge badge-pill badge-info" *ngIf="allCount">{{ allCount }}</span>
|
||||||
{{'invited' | i18n}}
|
</button>
|
||||||
<span class="badge badge-pill badge-info" *ngIf="invitedCount">{{invitedCount}}</span>
|
<button
|
||||||
</button>
|
type="button"
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
class="btn btn-outline-secondary"
|
||||||
[ngClass]="{active: status == userStatusType.Accepted}"
|
[ngClass]="{ active: status == userStatusType.Invited }"
|
||||||
(click)="filter(userStatusType.Accepted)">
|
(click)="filter(userStatusType.Invited)"
|
||||||
{{'accepted' | i18n}}
|
>
|
||||||
<span class="badge badge-pill badge-warning" *ngIf="acceptedCount">{{acceptedCount}}</span>
|
{{ "invited" | i18n }}
|
||||||
</button>
|
<span class="badge badge-pill badge-info" *ngIf="invitedCount">{{ invitedCount }}</span>
|
||||||
</div>
|
</button>
|
||||||
<div class="ml-3">
|
<button
|
||||||
<label class="sr-only" for="search">{{'search' | i18n}}</label>
|
type="button"
|
||||||
<input type="search" class="form-control form-control-sm" id="search" placeholder="{{'search' | i18n}}"
|
class="btn btn-outline-secondary"
|
||||||
[(ngModel)]="searchText">
|
[ngClass]="{ active: status == userStatusType.Accepted }"
|
||||||
</div>
|
(click)="filter(userStatusType.Accepted)"
|
||||||
<div class="dropdown ml-3" appListDropdown>
|
>
|
||||||
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" id="bulkActionsButton"
|
{{ "accepted" | i18n }}
|
||||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" appA11yTitle="{{'options' | i18n}}">
|
<span class="badge badge-pill badge-warning" *ngIf="acceptedCount">{{
|
||||||
<i class="fa fa-cog" aria-hidden="true"></i>
|
acceptedCount
|
||||||
</button>
|
}}</span>
|
||||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
|
</button>
|
||||||
<button class="dropdown-item" appStopClick (click)="bulkReinvite()">
|
|
||||||
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i>
|
|
||||||
{{'reinviteSelected' | i18n}}
|
|
||||||
</button>
|
|
||||||
<button class="dropdown-item text-success" appStopClick (click)="bulkConfirm()"
|
|
||||||
*ngIf="showBulkConfirmUsers">
|
|
||||||
<i class="fa fa-fw fa-check" aria-hidden="true"></i>
|
|
||||||
{{'confirmSelected' | i18n}}
|
|
||||||
</button>
|
|
||||||
<button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()">
|
|
||||||
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
|
|
||||||
{{'remove' | i18n}}
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<button class="dropdown-item" appStopClick (click)="selectAll(true)">
|
|
||||||
<i class="fa fa-fw fa-check-square-o" aria-hidden="true"></i>
|
|
||||||
{{'selectAll' | i18n}}
|
|
||||||
</button>
|
|
||||||
<button class="dropdown-item" appStopClick (click)="selectAll(false)">
|
|
||||||
<i class="fa fa-fw fa-minus-square-o" aria-hidden="true"></i>
|
|
||||||
{{'unselectAll' | i18n}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
{{'inviteUser' | i18n}}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
class="form-control form-control-sm"
|
||||||
|
id="search"
|
||||||
|
placeholder="{{ 'search' | i18n }}"
|
||||||
|
[(ngModel)]="searchText"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown ml-3" appListDropdown>
|
||||||
|
<button
|
||||||
|
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>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
|
||||||
|
<button class="dropdown-item" appStopClick (click)="bulkReinvite()">
|
||||||
|
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i>
|
||||||
|
{{ "reinviteSelected" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="dropdown-item text-success"
|
||||||
|
appStopClick
|
||||||
|
(click)="bulkConfirm()"
|
||||||
|
*ngIf="showBulkConfirmUsers"
|
||||||
|
>
|
||||||
|
<i class="fa fa-fw fa-check" aria-hidden="true"></i>
|
||||||
|
{{ "confirmSelected" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()">
|
||||||
|
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
|
||||||
|
{{ "remove" | i18n }}
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<button class="dropdown-item" appStopClick (click)="selectAll(true)">
|
||||||
|
<i class="fa fa-fw fa-check-square-o" aria-hidden="true"></i>
|
||||||
|
{{ "selectAll" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button class="dropdown-item" appStopClick (click)="selectAll(false)">
|
||||||
|
<i class="fa fa-fw fa-minus-square-o" aria-hidden="true"></i>
|
||||||
|
{{ "unselectAll" | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
{{ "inviteUser" | i18n }}
|
||||||
|
</button>
|
||||||
|
</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 &&
|
||||||
<ng-container *ngIf="searchedUsers.length">
|
(isPaging() ? pagedUsers : (users | search: searchText:'name':'email':'id')) as searchedUsers
|
||||||
<app-callout type="info" title="{{'confirmUsers' | i18n}}" icon="fa-check-circle" *ngIf="showConfirmUsers">
|
"
|
||||||
{{'providerUsersNeedConfirmed' | i18n}}
|
>
|
||||||
</app-callout>
|
<p *ngIf="!searchedUsers.length">{{ "noUsersInList" | i18n }}</p>
|
||||||
<table class="table table-hover table-list" infiniteScroll [infiniteScrollDistance]="1"
|
<ng-container *ngIf="searchedUsers.length">
|
||||||
[infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
|
<app-callout
|
||||||
<tbody>
|
type="info"
|
||||||
<tr *ngFor="let u of searchedUsers">
|
title="{{ 'confirmUsers' | i18n }}"
|
||||||
<td (click)="checkUser(u)" class="table-list-checkbox">
|
icon="fa-check-circle"
|
||||||
<input type="checkbox" [(ngModel)]="u.checked" appStopProp>
|
*ngIf="showConfirmUsers"
|
||||||
</td>
|
>
|
||||||
<td width="30">
|
{{ "providerUsersNeedConfirmed" | i18n }}
|
||||||
<app-avatar [data]="u | userName" [email]="u.email" size="25" [circle]="true"
|
</app-callout>
|
||||||
[fontSize]="14"></app-avatar>
|
<table
|
||||||
</td>
|
class="table table-hover table-list"
|
||||||
<td>
|
infiniteScroll
|
||||||
<a href="#" appStopClick (click)="edit(u)">{{u.email}}</a>
|
[infiniteScrollDistance]="1"
|
||||||
<span class="badge badge-secondary"
|
[infiniteScrollDisabled]="!isPaging()"
|
||||||
*ngIf="u.status === userStatusType.Invited">{{'invited' | i18n}}</span>
|
(scrolled)="loadMore()"
|
||||||
<span class="badge badge-warning"
|
>
|
||||||
*ngIf="u.status === userStatusType.Accepted">{{'accepted' | i18n}}</span>
|
<tbody>
|
||||||
<small class="text-muted d-block" *ngIf="u.name">{{u.name}}</small>
|
<tr *ngFor="let u of searchedUsers">
|
||||||
</td>
|
<td (click)="checkUser(u)" class="table-list-checkbox">
|
||||||
<td>
|
<input type="checkbox" [(ngModel)]="u.checked" appStopProp />
|
||||||
<ng-container *ngIf="u.twoFactorEnabled">
|
</td>
|
||||||
<i class="fa fa-lock" title="{{'userUsingTwoStep' | i18n}}" aria-hidden="true"></i>
|
<td width="30">
|
||||||
<span class="sr-only">{{'userUsingTwoStep' | i18n}}</span>
|
<app-avatar
|
||||||
</ng-container>
|
[data]="u | userName"
|
||||||
</td>
|
[email]="u.email"
|
||||||
<td>
|
size="25"
|
||||||
<span *ngIf="u.type === userType.ProviderAdmin">{{'providerAdmin' | i18n}}</span>
|
[circle]="true"
|
||||||
<span *ngIf="u.type === userType.ServiceUser">{{'serviceUser' | i18n}}</span>
|
[fontSize]="14"
|
||||||
<span *ngIf="u.type === userType.Custom">{{'custom' | i18n}}</span>
|
></app-avatar>
|
||||||
</td>
|
</td>
|
||||||
<td class="table-list-options">
|
<td>
|
||||||
<div class="dropdown" appListDropdown>
|
<a href="#" appStopClick (click)="edit(u)">{{ u.email }}</a>
|
||||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
|
<span class="badge badge-secondary" *ngIf="u.status === userStatusType.Invited">{{
|
||||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
"invited" | i18n
|
||||||
appA11yTitle="{{'options' | i18n}}">
|
}}</span>
|
||||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
<span class="badge badge-warning" *ngIf="u.status === userStatusType.Accepted">{{
|
||||||
</button>
|
"accepted" | i18n
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
}}</span>
|
||||||
<a class="dropdown-item" href="#" appStopClick (click)="reinvite(u)"
|
<small class="text-muted d-block" *ngIf="u.name">{{ u.name }}</small>
|
||||||
*ngIf="u.status === userStatusType.Invited">
|
</td>
|
||||||
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i>
|
<td>
|
||||||
{{'resendInvitation' | i18n}}
|
<ng-container *ngIf="u.twoFactorEnabled">
|
||||||
</a>
|
<i class="fa fa-lock" title="{{ 'userUsingTwoStep' | i18n }}" aria-hidden="true"></i>
|
||||||
<a class="dropdown-item text-success" href="#" appStopClick (click)="confirm(u)"
|
<span class="sr-only">{{ "userUsingTwoStep" | i18n }}</span>
|
||||||
*ngIf="u.status === userStatusType.Accepted">
|
</ng-container>
|
||||||
<i class="fa fa-fw fa-check" aria-hidden="true"></i>
|
</td>
|
||||||
{{'confirm' | i18n}}
|
<td>
|
||||||
</a>
|
<span *ngIf="u.type === userType.ProviderAdmin">{{ "providerAdmin" | i18n }}</span>
|
||||||
<a class="dropdown-item" href="#" appStopClick (click)="groups(u)" *ngIf="accessGroups">
|
<span *ngIf="u.type === userType.ServiceUser">{{ "serviceUser" | i18n }}</span>
|
||||||
<i class="fa fa-fw fa-sitemap" aria-hidden="true"></i>
|
<span *ngIf="u.type === userType.Custom">{{ "custom" | i18n }}</span>
|
||||||
{{'groups' | i18n}}
|
</td>
|
||||||
</a>
|
<td class="table-list-options">
|
||||||
<a class="dropdown-item" href="#" appStopClick (click)="events(u)"
|
<div class="dropdown" appListDropdown>
|
||||||
*ngIf="accessEvents && u.status === userStatusType.Confirmed">
|
<button
|
||||||
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>
|
class="btn btn-outline-secondary dropdown-toggle"
|
||||||
{{'eventLogs' | i18n}}
|
type="button"
|
||||||
</a>
|
data-toggle="dropdown"
|
||||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
|
aria-haspopup="true"
|
||||||
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
|
aria-expanded="false"
|
||||||
{{'remove' | i18n}}
|
appA11yTitle="{{ 'options' | i18n }}"
|
||||||
</a>
|
>
|
||||||
</div>
|
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
||||||
</div>
|
</button>
|
||||||
</td>
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
</tr>
|
<a
|
||||||
</tbody>
|
class="dropdown-item"
|
||||||
</table>
|
href="#"
|
||||||
</ng-container>
|
appStopClick
|
||||||
|
(click)="reinvite(u)"
|
||||||
|
*ngIf="u.status === userStatusType.Invited"
|
||||||
|
>
|
||||||
|
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i>
|
||||||
|
{{ "resendInvitation" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
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>
|
||||||
|
{{ "confirm" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
appStopClick
|
||||||
|
(click)="groups(u)"
|
||||||
|
*ngIf="accessGroups"
|
||||||
|
>
|
||||||
|
<i class="fa fa-fw fa-sitemap" aria-hidden="true"></i>
|
||||||
|
{{ "groups" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
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>
|
||||||
|
{{ "eventLogs" | i18n }}
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
|
||||||
|
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
|
||||||
|
{{ "remove" | i18n }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #addEdit></ng-template>
|
<ng-template #addEdit></ng-template>
|
||||||
<ng-template #eventsTemplate></ng-template>
|
<ng-template #eventsTemplate></ng-template>
|
||||||
|
|||||||
@@ -1,257 +1,292 @@
|
|||||||
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>
|
||||||
|
implements OnInit
|
||||||
|
{
|
||||||
|
@ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
|
||||||
|
@ViewChild("groupsTemplate", { read: ViewContainerRef, static: true })
|
||||||
|
groupsModalRef: 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;
|
||||||
|
|
||||||
@ViewChild('addEdit', { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
|
userType = ProviderUserType;
|
||||||
@ViewChild('groupsTemplate', { read: ViewContainerRef, static: true }) groupsModalRef: ViewContainerRef;
|
userStatusType = ProviderUserStatusType;
|
||||||
@ViewChild('eventsTemplate', { read: ViewContainerRef, static: true }) eventsModalRef: ViewContainerRef;
|
providerId: string;
|
||||||
@ViewChild('bulkStatusTemplate', { read: ViewContainerRef, static: true }) bulkStatusModalRef: ViewContainerRef;
|
accessEvents = false;
|
||||||
@ViewChild('bulkConfirmTemplate', { read: ViewContainerRef, static: true }) bulkConfirmModalRef: ViewContainerRef;
|
|
||||||
@ViewChild('bulkRemoveTemplate', { read: ViewContainerRef, static: true }) bulkRemoveModalRef: ViewContainerRef;
|
|
||||||
|
|
||||||
userType = ProviderUserType;
|
constructor(
|
||||||
userStatusType = ProviderUserStatusType;
|
apiService: ApiService,
|
||||||
providerId: string;
|
private route: ActivatedRoute,
|
||||||
accessEvents = false;
|
i18nService: I18nService,
|
||||||
|
modalService: ModalService,
|
||||||
|
platformUtilsService: PlatformUtilsService,
|
||||||
|
cryptoService: CryptoService,
|
||||||
|
private router: Router,
|
||||||
|
searchService: SearchService,
|
||||||
|
validationService: ValidationService,
|
||||||
|
logService: LogService,
|
||||||
|
searchPipe: SearchPipe,
|
||||||
|
userNamePipe: UserNamePipe,
|
||||||
|
stateService: StateService,
|
||||||
|
private providerService: ProviderService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
apiService,
|
||||||
|
searchService,
|
||||||
|
i18nService,
|
||||||
|
platformUtilsService,
|
||||||
|
cryptoService,
|
||||||
|
validationService,
|
||||||
|
modalService,
|
||||||
|
logService,
|
||||||
|
searchPipe,
|
||||||
|
userNamePipe,
|
||||||
|
stateService
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
ngOnInit() {
|
||||||
apiService: ApiService,
|
this.route.parent.params.subscribe(async (params) => {
|
||||||
private route: ActivatedRoute,
|
this.providerId = params.providerId;
|
||||||
i18nService: I18nService,
|
const provider = await this.providerService.get(this.providerId);
|
||||||
modalService: ModalService,
|
|
||||||
platformUtilsService: PlatformUtilsService,
|
|
||||||
cryptoService: CryptoService,
|
|
||||||
private router: Router,
|
|
||||||
searchService: SearchService,
|
|
||||||
validationService: ValidationService,
|
|
||||||
logService: LogService,
|
|
||||||
searchPipe: SearchPipe,
|
|
||||||
userNamePipe: UserNamePipe,
|
|
||||||
stateService: StateService,
|
|
||||||
private providerService: ProviderService,
|
|
||||||
) {
|
|
||||||
super(
|
|
||||||
apiService,
|
|
||||||
searchService,
|
|
||||||
i18nService,
|
|
||||||
platformUtilsService,
|
|
||||||
cryptoService,
|
|
||||||
validationService,
|
|
||||||
modalService,
|
|
||||||
logService,
|
|
||||||
searchPipe,
|
|
||||||
userNamePipe,
|
|
||||||
stateService,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
if (!provider.canManageUsers) {
|
||||||
this.route.parent.params.subscribe(async params => {
|
this.router.navigate(["../"], { relativeTo: this.route });
|
||||||
this.providerId = params.providerId;
|
return;
|
||||||
const provider = await this.providerService.get(this.providerId);
|
}
|
||||||
|
|
||||||
if (!provider.canManageUsers) {
|
this.accessEvents = provider.useEvents;
|
||||||
this.router.navigate(['../'], { relativeTo: this.route });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.accessEvents = provider.useEvents;
|
await this.load();
|
||||||
|
|
||||||
await this.load();
|
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||||
|
this.searchText = qParams.search;
|
||||||
this.route.queryParams.pipe(first()).subscribe(async qParams => {
|
if (qParams.viewEvents != null) {
|
||||||
this.searchText = qParams.search;
|
const user = this.users.filter((u) => u.id === qParams.viewEvents);
|
||||||
if (qParams.viewEvents != null) {
|
if (user.length > 0 && user[0].status === ProviderUserStatusType.Confirmed) {
|
||||||
const user = this.users.filter(u => u.id === qParams.viewEvents);
|
this.events(user[0]);
|
||||||
if (user.length > 0 && user[0].status === ProviderUserStatusType.Confirmed) {
|
}
|
||||||
this.events(user[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getUsers(): Promise<ListResponse<ProviderUserUserDetailsResponse>> {
|
|
||||||
return this.apiService.getProviderUsers(this.providerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteUser(id: string): Promise<any> {
|
|
||||||
return this.apiService.deleteProviderUser(this.providerId, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
reinviteUser(id: string): Promise<any> {
|
|
||||||
return this.apiService.postProviderUserReinvite(this.providerId, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
async confirmUser(user: ProviderUserUserDetailsResponse, publicKey: Uint8Array): Promise<any> {
|
|
||||||
const providerKey = await this.cryptoService.getProviderKey(this.providerId);
|
|
||||||
const key = await this.cryptoService.rsaEncrypt(providerKey.key, publicKey.buffer);
|
|
||||||
const request = new ProviderUserConfirmRequest();
|
|
||||||
request.key = key.encryptedString;
|
|
||||||
await this.apiService.postProviderUserConfirm(this.providerId, user.id, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
async edit(user: ProviderUserUserDetailsResponse) {
|
|
||||||
const [modal] = await this.modalService.openViewRef(UserAddEditComponent, this.addEditModalRef, comp => {
|
|
||||||
comp.name = this.userNamePipe.transform(user);
|
|
||||||
comp.providerId = this.providerId;
|
|
||||||
comp.providerUserId = user != null ? user.id : null;
|
|
||||||
comp.onSavedUser.subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
this.load();
|
|
||||||
});
|
|
||||||
comp.onDeletedUser.subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
this.removeUser(user);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async events(user: ProviderUserUserDetailsResponse) {
|
|
||||||
const [modal] = await this.modalService.openViewRef(EntityEventsComponent, this.eventsModalRef, comp => {
|
|
||||||
comp.name = this.userNamePipe.transform(user);
|
|
||||||
comp.providerId = this.providerId;
|
|
||||||
comp.entityId = user.id;
|
|
||||||
comp.showUser = false;
|
|
||||||
comp.entity = 'user';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async bulkRemove() {
|
|
||||||
if (this.actionPromise != null) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const [modal] = await this.modalService.openViewRef(BulkRemoveComponent, this.bulkRemoveModalRef, comp => {
|
getUsers(): Promise<ListResponse<ProviderUserUserDetailsResponse>> {
|
||||||
comp.providerId = this.providerId;
|
return this.apiService.getProviderUsers(this.providerId);
|
||||||
comp.users = this.getCheckedUsers();
|
}
|
||||||
|
|
||||||
|
deleteUser(id: string): Promise<any> {
|
||||||
|
return this.apiService.deleteProviderUser(this.providerId, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
reinviteUser(id: string): Promise<any> {
|
||||||
|
return this.apiService.postProviderUserReinvite(this.providerId, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async confirmUser(user: ProviderUserUserDetailsResponse, publicKey: Uint8Array): Promise<any> {
|
||||||
|
const providerKey = await this.cryptoService.getProviderKey(this.providerId);
|
||||||
|
const key = await this.cryptoService.rsaEncrypt(providerKey.key, publicKey.buffer);
|
||||||
|
const request = new ProviderUserConfirmRequest();
|
||||||
|
request.key = key.encryptedString;
|
||||||
|
await this.apiService.postProviderUserConfirm(this.providerId, user.id, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
async edit(user: ProviderUserUserDetailsResponse) {
|
||||||
|
const [modal] = await this.modalService.openViewRef(
|
||||||
|
UserAddEditComponent,
|
||||||
|
this.addEditModalRef,
|
||||||
|
(comp) => {
|
||||||
|
comp.name = this.userNamePipe.transform(user);
|
||||||
|
comp.providerId = this.providerId;
|
||||||
|
comp.providerUserId = user != null ? user.id : null;
|
||||||
|
comp.onSavedUser.subscribe(() => {
|
||||||
|
modal.close();
|
||||||
|
this.load();
|
||||||
});
|
});
|
||||||
|
comp.onDeletedUser.subscribe(() => {
|
||||||
|
modal.close();
|
||||||
|
this.removeUser(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await modal.onClosedPromise();
|
async events(user: ProviderUserUserDetailsResponse) {
|
||||||
await this.load();
|
const [modal] = await this.modalService.openViewRef(
|
||||||
|
EntityEventsComponent,
|
||||||
|
this.eventsModalRef,
|
||||||
|
(comp) => {
|
||||||
|
comp.name = this.userNamePipe.transform(user);
|
||||||
|
comp.providerId = this.providerId;
|
||||||
|
comp.entityId = user.id;
|
||||||
|
comp.showUser = false;
|
||||||
|
comp.entity = "user";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async bulkRemove() {
|
||||||
|
if (this.actionPromise != null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async bulkReinvite() {
|
const [modal] = await this.modalService.openViewRef(
|
||||||
if (this.actionPromise != null) {
|
BulkRemoveComponent,
|
||||||
return;
|
this.bulkRemoveModalRef,
|
||||||
}
|
(comp) => {
|
||||||
|
comp.providerId = this.providerId;
|
||||||
|
comp.users = this.getCheckedUsers();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const users = this.getCheckedUsers();
|
await modal.onClosedPromise();
|
||||||
const filteredUsers = users.filter(u => u.status === ProviderUserStatusType.Invited);
|
await this.load();
|
||||||
|
}
|
||||||
|
|
||||||
if (filteredUsers.length <= 0) {
|
async bulkReinvite() {
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
if (this.actionPromise != null) {
|
||||||
this.i18nService.t('noSelectedUsersApplicable'));
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const request = new ProviderUserBulkRequest(filteredUsers.map(user => user.id));
|
|
||||||
const response = this.apiService.postManyProviderUserReinvite(this.providerId, request);
|
|
||||||
this.showBulkStatus(users, filteredUsers, response, this.i18nService.t('bulkReinviteMessage'));
|
|
||||||
} catch (e) {
|
|
||||||
this.validationService.showError(e);
|
|
||||||
}
|
|
||||||
this.actionPromise = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async bulkConfirm() {
|
const users = this.getCheckedUsers();
|
||||||
if (this.actionPromise != null) {
|
const filteredUsers = users.filter((u) => u.status === ProviderUserStatusType.Invited);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [modal] = await this.modalService.openViewRef(BulkConfirmComponent, this.bulkConfirmModalRef, comp => {
|
if (filteredUsers.length <= 0) {
|
||||||
comp.providerId = this.providerId;
|
this.platformUtilsService.showToast(
|
||||||
comp.users = this.getCheckedUsers();
|
"error",
|
||||||
});
|
this.i18nService.t("errorOccurred"),
|
||||||
|
this.i18nService.t("noSelectedUsersApplicable")
|
||||||
await modal.onClosedPromise();
|
);
|
||||||
await this.load();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async showBulkStatus(users: ProviderUserUserDetailsResponse[], filteredUsers: ProviderUserUserDetailsResponse[],
|
try {
|
||||||
request: Promise<ListResponse<ProviderUserBulkResponse>>, successfullMessage: string) {
|
const request = new ProviderUserBulkRequest(filteredUsers.map((user) => user.id));
|
||||||
|
const response = this.apiService.postManyProviderUserReinvite(this.providerId, request);
|
||||||
const [modal, childComponent] = await this.modalService.openViewRef(BulkStatusComponent, this.bulkStatusModalRef, comp => {
|
this.showBulkStatus(
|
||||||
comp.loading = true;
|
users,
|
||||||
});
|
filteredUsers,
|
||||||
|
response,
|
||||||
// Workaround to handle closing the modal shortly after it has been opened
|
this.i18nService.t("bulkReinviteMessage")
|
||||||
let close = false;
|
);
|
||||||
modal.onShown.subscribe(() => {
|
} catch (e) {
|
||||||
if (close) {
|
this.validationService.showError(e);
|
||||||
modal.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await request;
|
|
||||||
|
|
||||||
if (modal) {
|
|
||||||
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 }), {});
|
|
||||||
|
|
||||||
childComponent.users = users.map(user => {
|
|
||||||
let message = keyedErrors[user.id] ?? successfullMessage;
|
|
||||||
if (!keyedFilteredUsers.hasOwnProperty(user.id)) {
|
|
||||||
message = this.i18nService.t('bulkFilteredMessage');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
user: user,
|
|
||||||
error: keyedErrors.hasOwnProperty(user.id),
|
|
||||||
message: message,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
childComponent.loading = false;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
close = true;
|
|
||||||
modal.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this.actionPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async bulkConfirm() {
|
||||||
|
if (this.actionPromise != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [modal] = await this.modalService.openViewRef(
|
||||||
|
BulkConfirmComponent,
|
||||||
|
this.bulkConfirmModalRef,
|
||||||
|
(comp) => {
|
||||||
|
comp.providerId = this.providerId;
|
||||||
|
comp.users = this.getCheckedUsers();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await modal.onClosedPromise();
|
||||||
|
await this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async showBulkStatus(
|
||||||
|
users: ProviderUserUserDetailsResponse[],
|
||||||
|
filteredUsers: ProviderUserUserDetailsResponse[],
|
||||||
|
request: Promise<ListResponse<ProviderUserBulkResponse>>,
|
||||||
|
successfullMessage: string
|
||||||
|
) {
|
||||||
|
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||||
|
BulkStatusComponent,
|
||||||
|
this.bulkStatusModalRef,
|
||||||
|
(comp) => {
|
||||||
|
comp.loading = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Workaround to handle closing the modal shortly after it has been opened
|
||||||
|
let close = false;
|
||||||
|
modal.onShown.subscribe(() => {
|
||||||
|
if (close) {
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await request;
|
||||||
|
|
||||||
|
if (modal) {
|
||||||
|
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 }), {});
|
||||||
|
|
||||||
|
childComponent.users = users.map((user) => {
|
||||||
|
let message = keyedErrors[user.id] ?? successfullMessage;
|
||||||
|
if (!keyedFilteredUsers.hasOwnProperty(user.id)) {
|
||||||
|
message = this.i18nService.t("bulkFilteredMessage");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: user,
|
||||||
|
error: keyedErrors.hasOwnProperty(user.id),
|
||||||
|
message: message,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
childComponent.loading = false;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
close = true;
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,71 +1,124 @@
|
|||||||
<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
|
||||||
<div class="modal-header">
|
class="modal-content"
|
||||||
<h2 class="modal-title" id="userAddEditTitle">
|
#form
|
||||||
{{title}}
|
(ngSubmit)="submit()"
|
||||||
<small class="text-muted" *ngIf="name">{{name}}</small>
|
[appApiAction]="formPromise"
|
||||||
</h2>
|
ngNativeValidate
|
||||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
>
|
||||||
<span aria-hidden="true">×</span>
|
<div class="modal-header">
|
||||||
</button>
|
<h2 class="modal-title" id="userAddEditTitle">
|
||||||
</div>
|
{{ title }}
|
||||||
<div class="modal-body" *ngIf="loading">
|
<small class="text-muted" *ngIf="name">{{ name }}</small>
|
||||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
</h2>
|
||||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
<button
|
||||||
</div>
|
type="button"
|
||||||
<div class="modal-body" *ngIf="!loading">
|
class="close"
|
||||||
<ng-container *ngIf="!editMode">
|
data-dismiss="modal"
|
||||||
<p>{{'providerInviteUserDesc' | i18n}}</p>
|
appA11yTitle="{{ 'close' | i18n }}"
|
||||||
<div class="form-group mb-4">
|
>
|
||||||
<label for="emails">{{'email' | i18n}}</label>
|
<span aria-hidden="true">×</span>
|
||||||
<input id="emails" class="form-control" type="text" name="Emails" [(ngModel)]="emails" required
|
</button>
|
||||||
appAutoFocus>
|
</div>
|
||||||
<small class="text-muted">{{'inviteMultipleEmailDesc' | i18n : '20'}}</small>
|
<div class="modal-body" *ngIf="loading">
|
||||||
</div>
|
<i
|
||||||
</ng-container>
|
class="fa fa-spinner fa-spin text-muted"
|
||||||
<h3>
|
title="{{ 'loading' | i18n }}"
|
||||||
{{'userType' | i18n}}
|
aria-hidden="true"
|
||||||
<a target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}"
|
></i>
|
||||||
href="https://bitwarden.com/help/article/user-types-access-control/#user-types">
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
</div>
|
||||||
</a>
|
<div class="modal-body" *ngIf="!loading">
|
||||||
</h3>
|
<ng-container *ngIf="!editMode">
|
||||||
<div class="form-check mt-2 form-check-block">
|
<p>{{ "providerInviteUserDesc" | i18n }}</p>
|
||||||
<input class="form-check-input" type="radio" name="userType" id="userTypeServiceUser"
|
<div class="form-group mb-4">
|
||||||
[value]="userType.ServiceUser" [(ngModel)]="type">
|
<label for="emails">{{ "email" | i18n }}</label>
|
||||||
<label class="form-check-label" for="userTypeServiceUser">
|
<input
|
||||||
{{'serviceUser' | i18n}}
|
id="emails"
|
||||||
<small>{{'serviceUserDesc' | i18n}}</small>
|
class="form-control"
|
||||||
</label>
|
type="text"
|
||||||
</div>
|
name="Emails"
|
||||||
<div class="form-check mt-2 form-check-block">
|
[(ngModel)]="emails"
|
||||||
<input class="form-check-input" type="radio" name="userType" id="userTypeProviderAdmin"
|
required
|
||||||
[value]="userType.ProviderAdmin" [(ngModel)]="type">
|
appAutoFocus
|
||||||
<label class="form-check-label" for="userTypeProviderAdmin">
|
/>
|
||||||
{{'providerAdmin' | i18n}}
|
<small class="text-muted">{{ "inviteMultipleEmailDesc" | i18n: "20" }}</small>
|
||||||
<small>{{'providerAdminDesc' | i18n}}</small>
|
</div>
|
||||||
</label>
|
</ng-container>
|
||||||
</div>
|
<h3>
|
||||||
</div>
|
{{ "userType" | i18n }}
|
||||||
<div class="modal-footer">
|
<a
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
target="_blank"
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
rel="noopener"
|
||||||
<span>{{'save' | i18n}}</span>
|
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||||
</button>
|
href="https://bitwarden.com/help/article/user-types-access-control/#user-types"
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
>
|
||||||
{{'cancel' | i18n}}
|
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||||
</button>
|
</a>
|
||||||
<div class="ml-auto">
|
</h3>
|
||||||
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
|
<div class="form-check mt-2 form-check-block">
|
||||||
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
<input
|
||||||
[appApiAction]="deletePromise">
|
class="form-check-input"
|
||||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
|
type="radio"
|
||||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"
|
name="userType"
|
||||||
title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
id="userTypeServiceUser"
|
||||||
</button>
|
[value]="userType.ServiceUser"
|
||||||
</div>
|
[(ngModel)]="type"
|
||||||
</div>
|
/>
|
||||||
</form>
|
<label class="form-check-label" for="userTypeServiceUser">
|
||||||
</div>
|
{{ "serviceUser" | i18n }}
|
||||||
|
<small>{{ "serviceUserDesc" | i18n }}</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mt-2 form-check-block">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="radio"
|
||||||
|
name="userType"
|
||||||
|
id="userTypeProviderAdmin"
|
||||||
|
[value]="userType.ProviderAdmin"
|
||||||
|
[(ngModel)]="type"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="userTypeProviderAdmin">
|
||||||
|
{{ "providerAdmin" | i18n }}
|
||||||
|
<small>{{ "providerAdminDesc" | i18n }}</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<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>
|
||||||
|
<span>{{ "save" | i18n }}</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||||
|
{{ "cancel" | i18n }}
|
||||||
|
</button>
|
||||||
|
<div class="ml-auto">
|
||||||
|
<button
|
||||||
|
#deleteBtn
|
||||||
|
type="button"
|
||||||
|
(click)="delete()"
|
||||||
|
class="btn btn-outline-danger"
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,109 +1,121 @@
|
|||||||
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;
|
||||||
@Input() providerUserId: string;
|
@Input() providerUserId: string;
|
||||||
@Input() providerId: string;
|
@Input() providerId: string;
|
||||||
@Output() onSavedUser = new EventEmitter();
|
@Output() onSavedUser = new EventEmitter();
|
||||||
@Output() onDeletedUser = new EventEmitter();
|
@Output() onDeletedUser = new EventEmitter();
|
||||||
|
|
||||||
loading = true;
|
loading = true;
|
||||||
editMode: boolean = false;
|
editMode: boolean = false;
|
||||||
title: string;
|
title: string;
|
||||||
emails: string;
|
emails: string;
|
||||||
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;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
this.loading = false;
|
||||||
try {
|
}
|
||||||
if (this.editMode) {
|
|
||||||
const request = new ProviderUserUpdateRequest();
|
async submit() {
|
||||||
request.type = this.type;
|
try {
|
||||||
this.formPromise = this.apiService.putProviderUser(this.providerId, this.providerUserId, request);
|
if (this.editMode) {
|
||||||
} else {
|
const request = new ProviderUserUpdateRequest();
|
||||||
const request = new ProviderUserInviteRequest();
|
request.type = this.type;
|
||||||
request.emails = this.emails.trim().split(/\s*,\s*/);
|
this.formPromise = this.apiService.putProviderUser(
|
||||||
request.type = this.type;
|
this.providerId,
|
||||||
this.formPromise = this.apiService.postProviderUserInvite(this.providerId, request);
|
this.providerUserId,
|
||||||
}
|
request
|
||||||
await this.formPromise;
|
);
|
||||||
this.platformUtilsService.showToast('success', null,
|
} else {
|
||||||
this.i18nService.t(this.editMode ? 'editedUserId' : 'invitedUsers', this.name));
|
const request = new ProviderUserInviteRequest();
|
||||||
this.onSavedUser.emit();
|
request.emails = this.emails.trim().split(/\s*,\s*/);
|
||||||
} catch (e) {
|
request.type = this.type;
|
||||||
this.logService.error(e);
|
this.formPromise = this.apiService.postProviderUserInvite(this.providerId, request);
|
||||||
}
|
}
|
||||||
|
await this.formPromise;
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t(this.editMode ? "editedUserId" : "invitedUsers", this.name)
|
||||||
|
);
|
||||||
|
this.onSavedUser.emit();
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete() {
|
||||||
|
if (!this.editMode) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete() {
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
if (!this.editMode) {
|
this.i18nService.t("removeUserConfirmation"),
|
||||||
return;
|
this.name,
|
||||||
}
|
this.i18nService.t("yes"),
|
||||||
|
this.i18nService.t("no"),
|
||||||
const confirmed = await this.platformUtilsService.showDialog(
|
"warning"
|
||||||
this.i18nService.t('removeUserConfirmation'), this.name,
|
);
|
||||||
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
|
if (!confirmed) {
|
||||||
if (!confirmed) {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.deletePromise = this.apiService.deleteProviderUser(this.providerId, this.providerUserId);
|
|
||||||
await this.deletePromise;
|
|
||||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedUserId', this.name));
|
|
||||||
this.onDeletedUser.emit();
|
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.deletePromise = this.apiService.deleteProviderUser(this.providerId, this.providerUserId);
|
||||||
|
await this.deletePromise;
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("removedUserId", this.name)
|
||||||
|
);
|
||||||
|
this.onDeletedUser.emit();
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
<app-navbar></app-navbar>
|
<app-navbar></app-navbar>
|
||||||
<div class="org-nav" *ngIf="provider">
|
<div class="org-nav" *ngIf="provider">
|
||||||
<div class="container d-flex">
|
<div class="container d-flex">
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<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 class="ml-3 card border-danger text-danger bg-transparent" *ngIf="!provider.enabled">
|
|
||||||
<div class="card-body py-2">
|
|
||||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
|
||||||
{{'providerIsDisabled' | i18n}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul class="nav nav-tabs" *ngIf="showMenuBar">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" routerLink="clients" routerLinkActive="active">
|
|
||||||
<i class="fa fa-university" aria-hidden="true"></i>
|
|
||||||
{{'clients' | i18n}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" *ngIf="showManageTab">
|
|
||||||
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
|
|
||||||
<i class="fa fa-sliders" aria-hidden="true"></i>
|
|
||||||
{{'manage' | i18n}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" *ngIf="showSettingsTab">
|
|
||||||
<a class="nav-link" routerLink="settings" routerLinkActive="active">
|
|
||||||
<i class="fa fa-cogs" aria-hidden="true"></i>
|
|
||||||
{{'settings' | i18n}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ml-3 card border-danger text-danger bg-transparent" *ngIf="!provider.enabled">
|
||||||
|
<div class="card-body py-2">
|
||||||
|
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||||
|
{{ "providerIsDisabled" | i18n }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="nav nav-tabs" *ngIf="showMenuBar">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" routerLink="clients" routerLinkActive="active">
|
||||||
|
<i class="fa fa-university" aria-hidden="true"></i>
|
||||||
|
{{ "clients" | i18n }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" *ngIf="showManageTab">
|
||||||
|
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
|
||||||
|
<i class="fa fa-sliders" aria-hidden="true"></i>
|
||||||
|
{{ "manage" | i18n }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" *ngIf="showSettingsTab">
|
||||||
|
<a class="nav-link" routerLink="settings" routerLinkActive="active">
|
||||||
|
<i class="fa fa-cogs" aria-hidden="true"></i>
|
||||||
|
{{ "settings" | i18n }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container page-content">
|
<div class="container page-content">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
<app-footer></app-footer>
|
<app-footer></app-footer>
|
||||||
|
|||||||
@@ -1,51 +1,50 @@
|
|||||||
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;
|
||||||
|
private providerId: string;
|
||||||
|
|
||||||
provider: Provider;
|
constructor(private route: ActivatedRoute, private providerService: ProviderService) {}
|
||||||
private providerId: string;
|
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private providerService: ProviderService) { }
|
ngOnInit() {
|
||||||
|
document.body.classList.remove("layout_frontend");
|
||||||
|
this.route.params.subscribe(async (params) => {
|
||||||
|
this.providerId = params.providerId;
|
||||||
|
await this.load();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
async load() {
|
||||||
document.body.classList.remove('layout_frontend');
|
this.provider = await this.providerService.get(this.providerId);
|
||||||
this.route.params.subscribe(async params => {
|
}
|
||||||
this.providerId = params.providerId;
|
|
||||||
await this.load();
|
get showMenuBar() {
|
||||||
});
|
return this.showManageTab || this.showSettingsTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
get showManageTab() {
|
||||||
this.provider = await this.providerService.get(this.providerId);
|
return this.provider.canManageUsers || this.provider.canAccessEventLogs;
|
||||||
}
|
}
|
||||||
|
|
||||||
get showMenuBar() {
|
get showSettingsTab() {
|
||||||
return this.showManageTab || this.showSettingsTab;
|
return this.provider.isProviderAdmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
get showManageTab() {
|
get manageRoute(): string {
|
||||||
return this.provider.canManageUsers || this.provider.canAccessEventLogs;
|
switch (true) {
|
||||||
}
|
case this.provider.canManageUsers:
|
||||||
|
return "manage/people";
|
||||||
get showSettingsTab() {
|
case this.provider.canAccessEventLogs:
|
||||||
return this.provider.isProviderAdmin;
|
return "manage/events";
|
||||||
}
|
|
||||||
|
|
||||||
get manageRoute(): string {
|
|
||||||
switch (true) {
|
|
||||||
case this.provider.canManageUsers:
|
|
||||||
return 'manage/people';
|
|
||||||
case this.provider.canAccessEventLogs:
|
|
||||||
return 'manage/events';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,123 +1,123 @@
|
|||||||
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: [
|
||||||
|
{
|
||||||
|
path: "setup-provider",
|
||||||
|
component: SetupProviderComponent,
|
||||||
|
data: { titleId: "setupProvider" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "accept-provider",
|
||||||
|
component: AcceptProviderComponent,
|
||||||
|
data: { titleId: "acceptProvider" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
canActivate: [AuthGuardService],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "setup",
|
||||||
|
component: SetupComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ":providerId",
|
||||||
|
component: ProvidersLayoutComponent,
|
||||||
|
canActivate: [ProviderGuardService],
|
||||||
children: [
|
children: [
|
||||||
{
|
{ path: "", pathMatch: "full", redirectTo: "clients" },
|
||||||
path: 'setup-provider',
|
{ path: "clients/create", component: CreateOrganizationComponent },
|
||||||
component: SetupProviderComponent,
|
{ path: "clients", component: ClientsComponent, data: { titleId: "clients" } },
|
||||||
data: { titleId: 'setupProvider' },
|
{
|
||||||
},
|
path: "manage",
|
||||||
{
|
component: ManageComponent,
|
||||||
path: 'accept-provider',
|
children: [
|
||||||
component: AcceptProviderComponent,
|
{
|
||||||
data: { titleId: 'acceptProvider' },
|
path: "",
|
||||||
},
|
pathMatch: "full",
|
||||||
|
redirectTo: "people",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "people",
|
||||||
|
component: PeopleComponent,
|
||||||
|
canActivate: [ProviderTypeGuardService],
|
||||||
|
data: {
|
||||||
|
titleId: "people",
|
||||||
|
permissions: [Permissions.ManageUsers],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "events",
|
||||||
|
component: EventsComponent,
|
||||||
|
canActivate: [ProviderTypeGuardService],
|
||||||
|
data: {
|
||||||
|
titleId: "eventLogs",
|
||||||
|
permissions: [Permissions.AccessEventLogs],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "settings",
|
||||||
|
component: SettingsComponent,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
pathMatch: "full",
|
||||||
|
redirectTo: "account",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "account",
|
||||||
|
component: AccountComponent,
|
||||||
|
canActivate: [ProviderTypeGuardService],
|
||||||
|
data: {
|
||||||
|
titleId: "myProvider",
|
||||||
|
permissions: [Permissions.ManageProvider],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
],
|
||||||
path: '',
|
},
|
||||||
canActivate: [AuthGuardService],
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'setup',
|
|
||||||
component: SetupComponent,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: ':providerId',
|
|
||||||
component: ProvidersLayoutComponent,
|
|
||||||
canActivate: [ProviderGuardService],
|
|
||||||
children: [
|
|
||||||
{ path: '', pathMatch: 'full', redirectTo: 'clients' },
|
|
||||||
{ path: 'clients/create', component: CreateOrganizationComponent },
|
|
||||||
{ path: 'clients', component: ClientsComponent, data: { titleId: 'clients' } },
|
|
||||||
{
|
|
||||||
path: 'manage',
|
|
||||||
component: ManageComponent,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
pathMatch: 'full',
|
|
||||||
redirectTo: 'people',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'people',
|
|
||||||
component: PeopleComponent,
|
|
||||||
canActivate: [ProviderTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: 'people',
|
|
||||||
permissions: [Permissions.ManageUsers],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'events',
|
|
||||||
component: EventsComponent,
|
|
||||||
canActivate: [ProviderTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: 'eventLogs',
|
|
||||||
permissions: [Permissions.AccessEventLogs],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'settings',
|
|
||||||
component: SettingsComponent,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
pathMatch: 'full',
|
|
||||||
redirectTo: 'account',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'account',
|
|
||||||
component: AccountComponent,
|
|
||||||
canActivate: [ProviderTypeGuardService],
|
|
||||||
data: {
|
|
||||||
titleId: 'myProvider',
|
|
||||||
permissions: [Permissions.ManageProvider],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class ProvidersRoutingModule { }
|
export class ProvidersRoutingModule {}
|
||||||
|
|||||||
@@ -1,69 +1,63 @@
|
|||||||
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,
|
declarations: [
|
||||||
FormsModule,
|
AcceptProviderComponent,
|
||||||
OssModule,
|
AccountComponent,
|
||||||
ProvidersRoutingModule,
|
AddOrganizationComponent,
|
||||||
],
|
BulkConfirmComponent,
|
||||||
declarations: [
|
BulkRemoveComponent,
|
||||||
AcceptProviderComponent,
|
ClientsComponent,
|
||||||
AccountComponent,
|
CreateOrganizationComponent,
|
||||||
AddOrganizationComponent,
|
EventsComponent,
|
||||||
BulkConfirmComponent,
|
ManageComponent,
|
||||||
BulkRemoveComponent,
|
PeopleComponent,
|
||||||
ClientsComponent,
|
ProvidersLayoutComponent,
|
||||||
CreateOrganizationComponent,
|
SettingsComponent,
|
||||||
EventsComponent,
|
SetupComponent,
|
||||||
ManageComponent,
|
SetupProviderComponent,
|
||||||
PeopleComponent,
|
UserAddEditComponent,
|
||||||
ProvidersLayoutComponent,
|
],
|
||||||
SettingsComponent,
|
providers: [WebProviderService, ProviderGuardService, ProviderTypeGuardService],
|
||||||
SetupComponent,
|
|
||||||
SetupProviderComponent,
|
|
||||||
UserAddEditComponent,
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,31 @@
|
|||||||
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 {
|
||||||
constructor(
|
constructor(
|
||||||
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) {
|
|
||||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('providerIsDisabled'));
|
|
||||||
this.router.navigate(['/']);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
if (!provider.isProviderAdmin && !provider.enabled) {
|
||||||
|
this.platformUtilsService.showToast("error", null, this.i18nService.t("providerIsDisabled"));
|
||||||
|
this.router.navigate(["/"]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,27 @@
|
|||||||
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) ||
|
||||||
(permissions.indexOf(Permissions.ManageProvider) !== -1 && provider.isProviderAdmin) ||
|
(permissions.indexOf(Permissions.ManageProvider) !== -1 && provider.isProviderAdmin) ||
|
||||||
(permissions.indexOf(Permissions.ManageUsers) !== -1 && provider.canManageUsers)
|
(permissions.indexOf(Permissions.ManageUsers) !== -1 && provider.canManageUsers)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
this.router.navigate(['/providers', provider.id]);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.router.navigate(["/providers", provider.id]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,36 @@
|
|||||||
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);
|
||||||
const providerKey = await this.cryptoService.getProviderKey(providerId);
|
const providerKey = await this.cryptoService.getProviderKey(providerId);
|
||||||
|
|
||||||
const encryptedOrgKey = await this.cryptoService.encrypt(orgKey.key, providerKey);
|
const encryptedOrgKey = await this.cryptoService.encrypt(orgKey.key, providerKey);
|
||||||
|
|
||||||
const request = new ProviderAddOrganizationRequest();
|
const request = new ProviderAddOrganizationRequest();
|
||||||
request.organizationId = organizationId;
|
request.organizationId = organizationId;
|
||||||
request.key = encryptedOrgKey.encryptedString;
|
request.key = encryptedOrgKey.encryptedString;
|
||||||
|
|
||||||
const response = await this.apiService.postProviderAddOrganization(providerId, request);
|
const response = await this.apiService.postProviderAddOrganization(providerId, request);
|
||||||
await this.syncService.fullSync(true);
|
await this.syncService.fullSync(true);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async detachOrganizastion(providerId: string, organizationId: string): Promise<any> {
|
async detachOrganizastion(providerId: string, organizationId: string): Promise<any> {
|
||||||
await this.apiService.deleteProviderOrganization(providerId, organizationId);
|
await this.apiService.deleteProviderOrganization(providerId, organizationId);
|
||||||
await this.syncService.fullSync(true);
|
await this.syncService.fullSync(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
<div class="row">
|
*ngIf="provider && !loading"
|
||||||
<div class="col-6">
|
#form
|
||||||
<div class="form-group">
|
(ngSubmit)="submit()"
|
||||||
<label for="name">{{'providerName' | i18n}}</label>
|
[appApiAction]="formPromise"
|
||||||
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="provider.name"
|
ngNativeValidate
|
||||||
[disabled]="selfHosted">
|
>
|
||||||
</div>
|
<div class="row">
|
||||||
<div class="form-group">
|
<div class="col-6">
|
||||||
<label for="billingEmail">{{'billingEmail' | i18n}}</label>
|
<div class="form-group">
|
||||||
<input id="billingEmail" class="form-control" type="text" name="BillingEmail"
|
<label for="name">{{ "providerName" | i18n }}</label>
|
||||||
[(ngModel)]="provider.billingEmail" [disabled]="selfHosted">
|
<input
|
||||||
</div>
|
id="name"
|
||||||
</div>
|
class="form-control"
|
||||||
<div class="col-6">
|
type="text"
|
||||||
<app-avatar data="{{provider.name}}" dynamic="true" size="75" fontSize="35"></app-avatar>
|
name="Name"
|
||||||
</div>
|
[(ngModel)]="provider.name"
|
||||||
|
[disabled]="selfHosted"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="billingEmail">{{ "billingEmail" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
id="billingEmail"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
name="BillingEmail"
|
||||||
|
[(ngModel)]="provider.billingEmail"
|
||||||
|
[disabled]="selfHosted"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
<div class="col-6">
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
<app-avatar data="{{ provider.name }}" dynamic="true" size="75" fontSize="35"></app-avatar>
|
||||||
<span>{{'save' | i18n}}</span>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
|
<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>
|
||||||
|
<span>{{ "save" | i18n }}</span>
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,60 +1,65 @@
|
|||||||
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;
|
||||||
loading = true;
|
loading = true;
|
||||||
provider: ProviderResponse;
|
provider: ProviderResponse;
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
taxFormPromise: Promise<any>;
|
taxFormPromise: Promise<any>;
|
||||||
|
|
||||||
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);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.error(`Handled exception: ${e}`);
|
this.logService.error(`Handled exception: ${e}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
try {
|
try {
|
||||||
const request = new ProviderUpdateRequest();
|
const request = new ProviderUpdateRequest();
|
||||||
request.name = this.provider.name;
|
request.name = this.provider.name;
|
||||||
request.businessName = this.provider.businessName;
|
request.businessName = this.provider.businessName;
|
||||||
request.billingEmail = this.provider.billingEmail;
|
request.billingEmail = this.provider.billingEmail;
|
||||||
|
|
||||||
this.formPromise = this.apiService.putProvider(this.providerId, request).then(() => {
|
this.formPromise = this.apiService.putProvider(this.providerId, request).then(() => {
|
||||||
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}`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<div class="container page-content">
|
<div class="container page-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
<div class="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 class="col-9">
|
|
||||||
<router-outlet></router-outlet>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-9">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,31 @@
|
|||||||
<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"
|
||||||
</p>
|
title="{{ 'loading' | i18n }}"
|
||||||
</div>
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</p>
|
||||||
|
</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>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
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) {
|
||||||
|
this.router.navigate(["/providers/setup"], { queryParams: qParams });
|
||||||
|
}
|
||||||
|
|
||||||
async authedHandler(qParams: any) {
|
// tslint:disable-next-line
|
||||||
this.router.navigate(['/providers/setup'], {queryParams: qParams});
|
async unauthedHandler(qParams: any) {}
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable-next-line
|
|
||||||
async unauthedHandler(qParams: any) {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,39 @@
|
|||||||
<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>
|
||||||
|
<p>{{ "setupProviderDesc" | i18n }}</p>
|
||||||
|
|
||||||
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="loading">
|
||||||
|
<h2 class="mt-5">{{ "generalInformation" | i18n }}</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-group col-6">
|
||||||
|
<label for="name">{{ "providerName" | i18n }}</label>
|
||||||
|
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name" required />
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-6">
|
||||||
|
<label for="billingEmail">{{ "billingEmail" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
id="billingEmail"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
name="BillingEmail"
|
||||||
|
[(ngModel)]="billingEmail"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>{{'setupProviderDesc' | i18n}}</p>
|
|
||||||
|
|
||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="loading">
|
<div class="mt-4">
|
||||||
<h2 class="mt-5">{{'generalInformation' | i18n}}</h2>
|
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||||
<div class="row">
|
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||||
<div class="form-group col-6">
|
<span>{{ "submit" | i18n }}</span>
|
||||||
<label for="name">{{'providerName' | i18n}}</label>
|
</button>
|
||||||
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name" required>
|
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" *ngIf="showCancel">
|
||||||
</div>
|
{{ "cancel" | i18n }}
|
||||||
<div class="form-group col-6">
|
</button>
|
||||||
<label for="billingEmail">{{'billingEmail' | i18n}}</label>
|
</div>
|
||||||
<input id="billingEmail" class="form-control" type="text" name="BillingEmail" [(ngModel)]="billingEmail" required>
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4">
|
|
||||||
<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>
|
|
||||||
<span>{{'submit' | i18n}}</span>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" *ngIf="showCancel">
|
|
||||||
{{'cancel' | i18n}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
<app-footer></app-footer>
|
<app-footer></app-footer>
|
||||||
|
|||||||
@@ -1,95 +1,101 @@
|
|||||||
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;
|
||||||
authed = false;
|
authed = false;
|
||||||
email: string;
|
email: string;
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
|
|
||||||
providerId: string;
|
providerId: string;
|
||||||
token: string;
|
token: string;
|
||||||
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,
|
||||||
return;
|
this.i18nService.t("emergencyInviteAcceptFailed"),
|
||||||
}
|
{
|
||||||
|
timeout: 10000,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.router.navigate(["/"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.providerId = qParams.providerId;
|
this.providerId = qParams.providerId;
|
||||||
this.token = qParams.token;
|
this.token = qParams.token;
|
||||||
|
|
||||||
// Check if provider exists, redirect if it does
|
// Check if provider exists, redirect if it does
|
||||||
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) {
|
|
||||||
this.validationService.showError(e);
|
|
||||||
this.router.navigate(['/']);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async submit() {
|
|
||||||
this.formPromise = this.doSubmit();
|
|
||||||
await this.formPromise;
|
|
||||||
this.formPromise = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async doSubmit() {
|
|
||||||
try {
|
|
||||||
const shareKey = await this.cryptoService.makeShareKey();
|
|
||||||
const key = shareKey[0].encryptedString;
|
|
||||||
|
|
||||||
const request = new ProviderSetupRequest();
|
|
||||||
request.name = this.name;
|
|
||||||
request.billingEmail = this.billingEmail;
|
|
||||||
request.token = this.token;
|
|
||||||
request.key = key;
|
|
||||||
|
|
||||||
const provider = await this.apiService.postProviderSetup(this.providerId, request);
|
|
||||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('providerSetup'));
|
|
||||||
await this.syncService.fullSync(true);
|
|
||||||
|
|
||||||
this.router.navigate(['/providers', provider.id]);
|
|
||||||
} catch (e) {
|
|
||||||
this.validationService.showError(e);
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
|
this.router.navigate(["/"]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
this.formPromise = this.doSubmit();
|
||||||
|
await this.formPromise;
|
||||||
|
this.formPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async doSubmit() {
|
||||||
|
try {
|
||||||
|
const shareKey = await this.cryptoService.makeShareKey();
|
||||||
|
const key = shareKey[0].encryptedString;
|
||||||
|
|
||||||
|
const request = new ProviderSetupRequest();
|
||||||
|
request.name = this.name;
|
||||||
|
request.billingEmail = this.billingEmail;
|
||||||
|
request.token = this.token;
|
||||||
|
request.key = key;
|
||||||
|
|
||||||
|
const provider = await this.apiService.postProviderSetup(this.providerId, request);
|
||||||
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("providerSetup"));
|
||||||
|
await this.syncService.fullSync(true);
|
||||||
|
|
||||||
|
this.router.navigate(["/providers", provider.id]);
|
||||||
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
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,
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = webpackConfig;
|
module.exports = webpackConfig;
|
||||||
|
|||||||
49
config.js
49
config.js
@@ -1,37 +1,36 @@
|
|||||||
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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function log(configObj) {
|
function log(configObj) {
|
||||||
const repeatNum = 50;
|
const repeatNum = 50;
|
||||||
console.log(`${"=".repeat(repeatNum)}\nenvConfig`);
|
console.log(`${"=".repeat(repeatNum)}\nenvConfig`);
|
||||||
console.log(JSON.stringify(configObj, null, 2));
|
console.log(JSON.stringify(configObj, null, 2));
|
||||||
console.log(`${"=".repeat(repeatNum)}`);
|
console.log(`${"=".repeat(repeatNum)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadConfig(configName) {
|
function loadConfig(configName) {
|
||||||
try {
|
try {
|
||||||
return require(`./config/${configName}.json`);
|
return require(`./config/${configName}.json`);
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
load,
|
load,
|
||||||
log
|
log,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"urls": {},
|
"urls": {},
|
||||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
||||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
||||||
"paypal": {
|
"paypal": {
|
||||||
"businessId": "AD3LAUZSNVPJY",
|
"businessId": "AD3LAUZSNVPJY",
|
||||||
"buttonAction": "https://www.sandbox.paypal.com/cgi-bin/webscr"
|
"buttonAction": "https://www.sandbox.paypal.com/cgi-bin/webscr"
|
||||||
},
|
},
|
||||||
"dev": {
|
"dev": {
|
||||||
"allowedHosts": "auto"
|
"allowedHosts": "auto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"urls": {
|
"urls": {
|
||||||
"icons": "https://icons.bitwarden.net",
|
"icons": "https://icons.bitwarden.net",
|
||||||
"notifications": "https://notifications.bitwarden.com"
|
"notifications": "https://notifications.bitwarden.com"
|
||||||
},
|
},
|
||||||
"stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk",
|
"stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk",
|
||||||
"braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd",
|
"braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd",
|
||||||
"paypal": {
|
"paypal": {
|
||||||
"businessId": "4ZDA7DLUUJGMN",
|
"businessId": "4ZDA7DLUUJGMN",
|
||||||
"buttonAction": "https://www.paypal.com/cgi-bin/webscr"
|
"buttonAction": "https://www.paypal.com/cgi-bin/webscr"
|
||||||
},
|
},
|
||||||
"dev": {
|
"dev": {
|
||||||
"proxyApi": "https://api.bitwarden.com",
|
"proxyApi": "https://api.bitwarden.com",
|
||||||
"proxyIdentity": "https://identity.bitwarden.com",
|
"proxyIdentity": "https://identity.bitwarden.com",
|
||||||
"proxyEvents": "https://events.bitwarden.com"
|
"proxyEvents": "https://events.bitwarden.com"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"urls": {
|
"urls": {
|
||||||
"notifications": "http://localhost:61840"
|
"notifications": "http://localhost:61840"
|
||||||
},
|
},
|
||||||
"dev": {
|
"dev": {
|
||||||
"proxyApi": "http://localhost:4000",
|
"proxyApi": "http://localhost:4000",
|
||||||
"proxyIdentity": "http://localhost:33656",
|
"proxyIdentity": "http://localhost:33656",
|
||||||
"proxyEvents": "http://localhost:46273",
|
"proxyEvents": "http://localhost:46273",
|
||||||
"proxyNotifications": "http://localhost:61840"
|
"proxyNotifications": "http://localhost:61840"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"urls": {
|
"urls": {
|
||||||
"icons": "https://icons.qa.bitwarden.pw",
|
"icons": "https://icons.qa.bitwarden.pw",
|
||||||
"notifications": "https://notifications.qa.bitwarden.pw"
|
"notifications": "https://notifications.qa.bitwarden.pw"
|
||||||
},
|
},
|
||||||
"dev": {
|
"dev": {
|
||||||
"proxyApi": "https://api.qa.bitwarden.pw",
|
"proxyApi": "https://api.qa.bitwarden.pw",
|
||||||
"proxyIdentity": "https://identity.qa.bitwarden.pw",
|
"proxyIdentity": "https://identity.qa.bitwarden.pw",
|
||||||
"proxyEvents": "https://events.qa.bitwarden.pw"
|
"proxyEvents": "https://events.qa.bitwarden.pw"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
90
src/404.html
90
src/404.html
@@ -1,50 +1,56 @@
|
|||||||
<!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> <strong>bit</strong>warden</div>
|
||||||
<i class="fa fa-shield"></i>
|
|
||||||
<strong>bit</strong>warden
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="container inner content">
|
</div>
|
||||||
<h2>Page not found!</h2>
|
</div>
|
||||||
<p>Sorry, but the page you were looking for could not be found.</p>
|
<div class="container inner content">
|
||||||
<p>
|
<h2>Page not found!</h2>
|
||||||
<a href="/">
|
<p>Sorry, but the page you were looking for could not be found.</p>
|
||||||
<img src="/images/404.png" class="img-fluid" alt="404 image" width="80%"/>
|
<p>
|
||||||
</a>
|
<a href="/">
|
||||||
</p>
|
<img src="/images/404.png" class="img-fluid" alt="404 image" width="80%" />
|
||||||
<p>You can <a href="/">return to the web vault</a>, check our <a href="https://status.bitwarden.com/">status page</a>
|
</a>
|
||||||
or <a href="https://bitwarden.com/contact/">contact us</a>.</p>
|
</p>
|
||||||
</div>
|
<p>
|
||||||
<div class="container footer text-muted content">
|
You can <a href="/">return to the web vault</a>, check our
|
||||||
© Copyright 2021 Bitwarden, Inc.
|
<a href="https://status.bitwarden.com/">status page</a> or
|
||||||
</div>
|
<a href="https://bitwarden.com/contact/">contact us</a>.
|
||||||
</body>
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="container footer text-muted content">© Copyright 2021 Bitwarden, Inc.</div>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,119 +1,121 @@
|
|||||||
@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,
|
||||||
height: 100%;
|
body,
|
||||||
-webkit-font-smoothing: antialiased;
|
.row {
|
||||||
|
height: 100%;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
margin-bottom: 12.5px;
|
margin-bottom: 12.5px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand {
|
.brand {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
padding: 40px 0 40px 0;
|
padding: 40px 0 40px 0;
|
||||||
border-top: 1px solid #dee2e6;
|
border-top: 1px solid #dee2e6;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,41 @@
|
|||||||
<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"
|
||||||
</p>
|
title="{{ 'loading' | i18n }}"
|
||||||
</div>
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</p>
|
||||||
|
</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 }"
|
||||||
</a>
|
class="btn btn-primary btn-block ml-2 mt-0"
|
||||||
</div>
|
>
|
||||||
</div>
|
{{ "createAccount" | i18n }}
|
||||||
</div>
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,60 +1,54 @@
|
|||||||
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 failedShortMessage = "emergencyInviteAcceptFailedShort";
|
||||||
|
protected failedMessage = "emergencyInviteAcceptFailed";
|
||||||
|
|
||||||
protected requiredParameters: string[] = ['id', 'name', 'email', 'token'];
|
constructor(
|
||||||
protected failedShortMessage = 'emergencyInviteAcceptFailedShort';
|
router: Router,
|
||||||
protected failedMessage = 'emergencyInviteAcceptFailed';
|
platformUtilsService: PlatformUtilsService,
|
||||||
|
i18nService: I18nService,
|
||||||
|
route: ActivatedRoute,
|
||||||
|
private apiService: ApiService,
|
||||||
|
stateService: StateService
|
||||||
|
) {
|
||||||
|
super(router, platformUtilsService, i18nService, route, stateService);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
async authedHandler(qParams: any): Promise<void> {
|
||||||
router: Router,
|
const request = new EmergencyAccessAcceptRequest();
|
||||||
platformUtilsService: PlatformUtilsService,
|
request.token = qParams.token;
|
||||||
i18nService: I18nService,
|
this.actionPromise = this.apiService.postEmergencyAccessAccept(qParams.id, request);
|
||||||
route: ActivatedRoute,
|
await this.actionPromise;
|
||||||
private apiService: ApiService,
|
this.platformUtilService.showToast(
|
||||||
stateService: StateService
|
"success",
|
||||||
) {
|
this.i18nService.t("inviteAccepted"),
|
||||||
super(
|
this.i18nService.t("emergencyInviteAcceptedDesc"),
|
||||||
router,
|
{ timeout: 10000 }
|
||||||
platformUtilsService,
|
);
|
||||||
i18nService,
|
this.router.navigate(["/vault"]);
|
||||||
route,
|
}
|
||||||
stateService
|
|
||||||
);
|
async unauthedHandler(qParams: any): Promise<void> {
|
||||||
}
|
this.name = qParams.name;
|
||||||
|
if (this.name != null) {
|
||||||
async authedHandler(qParams: any): Promise<void> {
|
// Fix URL encoding of space issue with Angular
|
||||||
const request = new EmergencyAccessAcceptRequest();
|
this.name = this.name.replace(/\+/g, " ");
|
||||||
request.token = qParams.token;
|
|
||||||
this.actionPromise = this.apiService.postEmergencyAccessAccept(qParams.id, request);
|
|
||||||
await this.actionPromise;
|
|
||||||
this.platformUtilService.showToast('success', this.i18nService.t('inviteAccepted'),
|
|
||||||
this.i18nService.t('emergencyInviteAcceptedDesc'), {timeout: 10000});
|
|
||||||
this.router.navigate(['/vault']);
|
|
||||||
}
|
|
||||||
|
|
||||||
async unauthedHandler(qParams: any): Promise<void> {
|
|
||||||
this.name = qParams.name;
|
|
||||||
if (this.name != null) {
|
|
||||||
// Fix URL encoding of space issue with Angular
|
|
||||||
this.name = this.name.replace(/\+/g, ' ');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,42 @@
|
|||||||
<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"
|
||||||
</p>
|
title="{{ 'loading' | i18n }}"
|
||||||
</div>
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</p>
|
||||||
|
</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 }"
|
||||||
</a>
|
class="btn btn-primary btn-block ml-2 mt-0"
|
||||||
</div>
|
>
|
||||||
</div>
|
{{ "createAccount" | i18n }}
|
||||||
</div>
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,116 +1,127 @@
|
|||||||
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,
|
||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
route: ActivatedRoute,
|
route: ActivatedRoute,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
stateService: StateService,
|
stateService: StateService,
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private policyService: PolicyService,
|
private policyService: PolicyService,
|
||||||
private logService: LogService
|
private logService: LogService
|
||||||
) {
|
) {
|
||||||
super(
|
super(router, platformUtilsService, i18nService, route, stateService);
|
||||||
router,
|
}
|
||||||
platformUtilsService,
|
|
||||||
i18nService,
|
async authedHandler(qParams: any): Promise<void> {
|
||||||
route,
|
const request = new OrganizationUserAcceptRequest();
|
||||||
stateService
|
request.token = qParams.token;
|
||||||
);
|
if (await this.performResetPasswordAutoEnroll(qParams)) {
|
||||||
|
this.actionPromise = this.apiService
|
||||||
|
.postOrganizationUserAccept(qParams.organizationId, qParams.organizationUserId, request)
|
||||||
|
.then(() => {
|
||||||
|
// Retrieve Public Key
|
||||||
|
return this.apiService.getOrganizationKeys(qParams.organizationId);
|
||||||
|
})
|
||||||
|
.then(async (response) => {
|
||||||
|
if (response == null) {
|
||||||
|
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicKey = Utils.fromB64ToArray(response.publicKey);
|
||||||
|
|
||||||
|
// RSA Encrypt user's encKey.key with organization public key
|
||||||
|
const encKey = await this.cryptoService.getEncKey();
|
||||||
|
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
|
||||||
|
|
||||||
|
// Create request and execute enrollment
|
||||||
|
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
|
||||||
|
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
|
||||||
|
|
||||||
|
return this.apiService.putOrganizationUserResetPasswordEnrollment(
|
||||||
|
qParams.organizationId,
|
||||||
|
await this.stateService.getUserId(),
|
||||||
|
resetRequest
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.actionPromise = this.apiService.postOrganizationUserAccept(
|
||||||
|
qParams.organizationId,
|
||||||
|
qParams.organizationUserId,
|
||||||
|
request
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async authedHandler(qParams: any): Promise<void> {
|
await this.actionPromise;
|
||||||
const request = new OrganizationUserAcceptRequest();
|
this.platformUtilService.showToast(
|
||||||
request.token = qParams.token;
|
"success",
|
||||||
if (await this.performResetPasswordAutoEnroll(qParams)) {
|
this.i18nService.t("inviteAccepted"),
|
||||||
this.actionPromise = this.apiService.postOrganizationUserAccept(qParams.organizationId,
|
this.i18nService.t("inviteAcceptedDesc"),
|
||||||
qParams.organizationUserId, request).then(() => {
|
{ timeout: 10000 }
|
||||||
// Retrieve Public Key
|
);
|
||||||
return this.apiService.getOrganizationKeys(qParams.organizationId);
|
|
||||||
}).then(async response => {
|
|
||||||
if (response == null) {
|
|
||||||
throw new Error(this.i18nService.t('resetPasswordOrgKeysError'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const publicKey = Utils.fromB64ToArray(response.publicKey);
|
await this.stateService.setOrganizationInvitation(null);
|
||||||
|
this.router.navigate(["/vault"]);
|
||||||
|
}
|
||||||
|
|
||||||
// RSA Encrypt user's encKey.key with organization public key
|
async unauthedHandler(qParams: any): Promise<void> {
|
||||||
const encKey = await this.cryptoService.getEncKey();
|
this.orgName = qParams.organizationName;
|
||||||
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
|
if (this.orgName != null) {
|
||||||
|
// Fix URL encoding of space issue with Angular
|
||||||
|
this.orgName = this.orgName.replace(/\+/g, " ");
|
||||||
|
}
|
||||||
|
await this.stateService.setOrganizationInvitation(qParams);
|
||||||
|
}
|
||||||
|
|
||||||
// Create request and execute enrollment
|
private async performResetPasswordAutoEnroll(qParams: any): Promise<boolean> {
|
||||||
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
|
let policyList: Policy[] = null;
|
||||||
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
|
try {
|
||||||
|
const policies = await this.apiService.getPoliciesByToken(
|
||||||
return this.apiService.putOrganizationUserResetPasswordEnrollment(qParams.organizationId, await this.stateService.getUserId(), resetRequest);
|
qParams.organizationId,
|
||||||
});
|
qParams.token,
|
||||||
} else {
|
qParams.email,
|
||||||
this.actionPromise = this.apiService.postOrganizationUserAccept(qParams.organizationId,
|
qParams.organizationUserId
|
||||||
qParams.organizationUserId, request);
|
);
|
||||||
}
|
policyList = this.policyService.mapPoliciesFromToken(policies);
|
||||||
|
} catch (e) {
|
||||||
await this.actionPromise;
|
this.logService.error(e);
|
||||||
this.platformUtilService.showToast('success', this.i18nService.t('inviteAccepted'),
|
|
||||||
this.i18nService.t('inviteAcceptedDesc'), {timeout: 10000});
|
|
||||||
|
|
||||||
await this.stateService.setOrganizationInvitation(null);
|
|
||||||
this.router.navigate(['/vault']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async unauthedHandler(qParams: any): Promise<void> {
|
if (policyList != null) {
|
||||||
this.orgName = qParams.organizationName;
|
const result = this.policyService.getResetPasswordPolicyOptions(
|
||||||
if (this.orgName != null) {
|
policyList,
|
||||||
// Fix URL encoding of space issue with Angular
|
qParams.organizationId
|
||||||
this.orgName = this.orgName.replace(/\+/g, ' ');
|
);
|
||||||
}
|
// Return true if policy enabled and auto-enroll enabled
|
||||||
await this.stateService.setOrganizationInvitation(qParams);
|
return result[1] && result[0].autoEnrollEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async performResetPasswordAutoEnroll(qParams: any): Promise<boolean> {
|
return false;
|
||||||
let policyList: Policy[] = null;
|
}
|
||||||
try {
|
|
||||||
const policies = await this.apiService.getPoliciesByToken(qParams.organizationId, qParams.token,
|
|
||||||
qParams.email, qParams.organizationUserId);
|
|
||||||
policyList = this.policyService.mapPoliciesFromToken(policies);
|
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (policyList != null) {
|
|
||||||
const result = this.policyService.getResetPasswordPolicyOptions(policyList, qParams.organizationId);
|
|
||||||
// Return true if policy enabled and auto-enroll enabled
|
|
||||||
return result[1] && result[0].autoEnrollEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,44 @@
|
|||||||
<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"
|
||||||
</div>
|
type="text"
|
||||||
<hr>
|
name="Email"
|
||||||
<div class="d-flex">
|
[(ngModel)]="email"
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
|
required
|
||||||
<span [hidden]="form.loading">{{'submit' | i18n}}</span>
|
appAutofocus
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
inputmode="email"
|
||||||
</button>
|
appInputVerbatim="false"
|
||||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
/>
|
||||||
{{'cancel' | i18n}}
|
<small class="form-text text-muted">{{ "enterEmailToGetHint" | i18n }}</small>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
<hr />
|
||||||
</div>
|
<div class="d-flex">
|
||||||
</div>
|
<button
|
||||||
|
type="submit"
|
||||||
|
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>
|
||||||
|
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||||
|
{{ "cancel" | i18n }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -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,
|
||||||
super(router, i18nService, apiService, platformUtilsService, logService);
|
apiService: ApiService,
|
||||||
}
|
platformUtilsService: PlatformUtilsService,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(router, i18nService, apiService, platformUtilsService, logService);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,68 @@
|
|||||||
<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="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
|
||||||
</button>
|
appAutofocus
|
||||||
</div>
|
appInputVerbatim
|
||||||
<small class="text-muted form-text">
|
/>
|
||||||
{{'loggedInAsEmailOn' | i18n : email : webVaultHostname}}
|
<button
|
||||||
</small>
|
type="button"
|
||||||
</div>
|
class="ml-1 btn btn-link"
|
||||||
<hr>
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
<div class="d-flex">
|
(click)="togglePassword()"
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
|
>
|
||||||
<span>
|
<i
|
||||||
<i class="fa fa-unlock-alt" aria-hidden="true"></i> {{'unlock' | i18n}}
|
class="fa fa-lg"
|
||||||
</span>
|
aria-hidden="true"
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
|
||||||
</button>
|
></i>
|
||||||
<button type="button" class="btn btn-outline-secondary btn-block ml-2 mt-0" (click)="logOut()">
|
</button>
|
||||||
{{'logOut' | i18n}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<small class="text-muted form-text">
|
||||||
|
{{ "loggedInAsEmailOn" | i18n: email:webVaultHostname }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="d-flex">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary btn-block btn-submit"
|
||||||
|
[disabled]="form.loading"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<i class="fa fa-unlock-alt" aria-hidden="true"></i> {{ "unlock" | i18n }}
|
||||||
|
</span>
|
||||||
|
<i
|
||||||
|
class="fa fa-spinner fa-spin"
|
||||||
|
title="{{ 'loading' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary btn-block ml-2 mt-0"
|
||||||
|
(click)="logOut()"
|
||||||
|
>
|
||||||
|
{{ "logOut" | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,48 +1,65 @@
|
|||||||
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]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +1,101 @@
|
|||||||
<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 }}"
|
||||||
</app-callout>
|
*ngIf="showResetPasswordAutoEnrollWarning"
|
||||||
<div class="form-group">
|
>
|
||||||
<label for="email">{{'emailAddress' | i18n}}</label>
|
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
|
||||||
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required
|
</app-callout>
|
||||||
inputmode="email" appInputVerbatim="false">
|
<div class="form-group">
|
||||||
</div>
|
<label for="email">{{ "emailAddress" | i18n }}</label>
|
||||||
<div class="form-group">
|
<input
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
id="email"
|
||||||
<div class="d-flex">
|
class="form-control"
|
||||||
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
|
type="text"
|
||||||
name="MasterPassword" class="text-monospace form-control" [(ngModel)]="masterPassword"
|
name="Email"
|
||||||
required appInputVerbatim>
|
[(ngModel)]="email"
|
||||||
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}"
|
required
|
||||||
(click)="togglePassword()">
|
inputmode="email"
|
||||||
<i class="fa fa-lg" aria-hidden="true"
|
appInputVerbatim="false"
|
||||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
/>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
<small class="form-text">
|
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||||
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
|
<div class="d-flex">
|
||||||
</small>
|
<input
|
||||||
</div>
|
id="masterPassword"
|
||||||
<div class="form-check mb-3">
|
type="{{ showPassword ? 'text' : 'password' }}"
|
||||||
<input type="checkbox" class="form-check-input" id="rememberEmail" name="RememberEmail"
|
name="MasterPassword"
|
||||||
[(ngModel)]="rememberEmail">
|
class="text-monospace form-control"
|
||||||
<label class="form-check-label" for="rememberEmail">{{'rememberEmail' | i18n}}</label>
|
[(ngModel)]="masterPassword"
|
||||||
</div>
|
required
|
||||||
<div class="mb-n3" [hidden]="!showCaptcha()"><iframe id="hcaptcha_iframe" height="80"></iframe></div>
|
appInputVerbatim
|
||||||
<hr>
|
/>
|
||||||
<div class="d-flex">
|
<button
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
|
type="button"
|
||||||
<span>
|
class="ml-1 btn btn-link"
|
||||||
<i class="fa fa-sign-in" aria-hidden="true"></i> {{'logIn' | i18n}}
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
</span>
|
(click)="togglePassword()"
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
>
|
||||||
</button>
|
<i
|
||||||
<a routerLink="/register" [queryParams]="{email: email}"
|
class="fa fa-lg"
|
||||||
class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
aria-hidden="true"
|
||||||
<i class="fa fa-pencil-square-o" aria-hidden="true"></i> {{'createAccount' | i18n}}
|
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
|
||||||
</a>
|
></i>
|
||||||
</div>
|
</button>
|
||||||
<div class="d-flex">
|
|
||||||
<a routerLink="/sso" class="btn btn-outline-secondary btn-block mt-2">
|
|
||||||
<i class="fa fa-bank" aria-hidden="true"></i> {{'enterpriseSingleSignOn' | i18n}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<small class="form-text">
|
||||||
|
<a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="form-check-input"
|
||||||
|
id="rememberEmail"
|
||||||
|
name="RememberEmail"
|
||||||
|
[(ngModel)]="rememberEmail"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="rememberEmail">{{ "rememberEmail" | i18n }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="mb-n3" [hidden]="!showCaptcha()">
|
||||||
|
<iframe id="hcaptcha_iframe" height="80"></iframe>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="d-flex">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary btn-block btn-submit"
|
||||||
|
[disabled]="form.loading"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
<a
|
||||||
|
routerLink="/register"
|
||||||
|
[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>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex">
|
||||||
|
<a routerLink="/sso" class="btn btn-outline-secondary btn-block mt-2">
|
||||||
|
<i class="fa fa-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,100 +1,118 @@
|
|||||||
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,
|
||||||
|
i18nService: I18nService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
stateService: StateService,
|
||||||
|
platformUtilsService: PlatformUtilsService,
|
||||||
|
environmentService: EnvironmentService,
|
||||||
|
passwordGenerationService: PasswordGenerationService,
|
||||||
|
cryptoFunctionService: CryptoFunctionService,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(authService: AuthService, router: Router,
|
async ngOnInit() {
|
||||||
i18nService: I18nService, private route: ActivatedRoute, stateService: StateService,
|
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||||
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
if (qParams.email != null && qParams.email.indexOf("@") > -1) {
|
||||||
passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService,
|
this.email = qParams.email;
|
||||||
private apiService: ApiService, private policyService: PolicyService, logService: LogService,
|
}
|
||||||
ngZone: NgZone) {
|
if (qParams.premium != null) {
|
||||||
super(authService, router,
|
this.stateService.setLoginRedirect({ route: "/settings/premium" });
|
||||||
platformUtilsService, i18nService,
|
} else if (qParams.org != null) {
|
||||||
stateService, environmentService,
|
this.stateService.setLoginRedirect({
|
||||||
passwordGenerationService, cryptoFunctionService,
|
route: "/settings/create-organization",
|
||||||
logService, ngZone);
|
qParams: { plan: qParams.org },
|
||||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
|
||||||
this.route.queryParams.pipe(first()).subscribe(async qParams => {
|
|
||||||
if (qParams.email != null && qParams.email.indexOf('@') > -1) {
|
|
||||||
this.email = qParams.email;
|
|
||||||
}
|
|
||||||
if (qParams.premium != null) {
|
|
||||||
this.stateService.setLoginRedirect({ route: '/settings/premium' });
|
|
||||||
} else if (qParams.org != null) {
|
|
||||||
this.stateService.setLoginRedirect(
|
|
||||||
{ route: '/settings/create-organization', qParams: { plan: qParams.org } });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Are they coming from an email for sponsoring a families organization
|
|
||||||
if (qParams.sponsorshipToken != null) {
|
|
||||||
// After logging in redirect them to setup the families sponsorship
|
|
||||||
this.stateService.setLoginRedirect({
|
|
||||||
route: '/setup/families-for-enterprise',
|
|
||||||
qParams: { token: qParams.sponsorshipToken },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await super.ngOnInit();
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const invite = await this.stateService.getOrganizationInvitation();
|
// Are they coming from an email for sponsoring a families organization
|
||||||
if (invite != null) {
|
if (qParams.sponsorshipToken != null) {
|
||||||
let policyList: Policy[] = null;
|
// After logging in redirect them to setup the families sponsorship
|
||||||
try {
|
this.stateService.setLoginRedirect({
|
||||||
const policies = await this.apiService.getPoliciesByToken(invite.organizationId, invite.token,
|
route: "/setup/families-for-enterprise",
|
||||||
invite.email, invite.organizationUserId);
|
qParams: { token: qParams.sponsorshipToken },
|
||||||
policyList = this.policyService.mapPoliciesFromToken(policies);
|
});
|
||||||
} catch (e) {
|
}
|
||||||
this.logService.error(e);
|
await super.ngOnInit();
|
||||||
}
|
});
|
||||||
|
|
||||||
if (policyList != null) {
|
const invite = await this.stateService.getOrganizationInvitation();
|
||||||
const result = this.policyService.getResetPasswordPolicyOptions(policyList, invite.organizationId);
|
if (invite != null) {
|
||||||
// Set to true if policy enabled and auto-enroll enabled
|
let policyList: Policy[] = null;
|
||||||
this.showResetPasswordAutoEnrollWarning = result[1] && result[0].autoEnrollEnabled;
|
try {
|
||||||
}
|
const policies = await this.apiService.getPoliciesByToken(
|
||||||
}
|
invite.organizationId,
|
||||||
|
invite.token,
|
||||||
|
invite.email,
|
||||||
|
invite.organizationUserId
|
||||||
|
);
|
||||||
|
policyList = this.policyService.mapPoliciesFromToken(policies);
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (policyList != null) {
|
||||||
|
const result = this.policyService.getResetPasswordPolicyOptions(
|
||||||
|
policyList,
|
||||||
|
invite.organizationId
|
||||||
|
);
|
||||||
|
// Set to true if policy enabled and auto-enroll enabled
|
||||||
|
this.showResetPasswordAutoEnrollWarning = result[1] && result[0].autoEnrollEnabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async goAfterLogIn() {
|
async goAfterLogIn() {
|
||||||
const loginRedirect = await this.stateService.getLoginRedirect();
|
const loginRedirect = await this.stateService.getLoginRedirect();
|
||||||
if (loginRedirect != null) {
|
if (loginRedirect != null) {
|
||||||
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
|
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
|
||||||
await this.stateService.setLoginRedirect(null);
|
await this.stateService.setLoginRedirect(null);
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate([this.successRoute]);
|
this.router.navigate([this.successRoute]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,44 @@
|
|||||||
<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"
|
||||||
</div>
|
class="form-control"
|
||||||
<hr>
|
type="text"
|
||||||
<div class="d-flex">
|
name="Email"
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
|
[(ngModel)]="email"
|
||||||
<span>{{'submit' | i18n}}</span>
|
required
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
appAutofocus
|
||||||
</button>
|
inputmode="email"
|
||||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
appInputVerbatim="false"
|
||||||
{{'cancel' | i18n}}
|
/>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
<hr />
|
||||||
</div>
|
<div class="d-flex">
|
||||||
</div>
|
<button
|
||||||
|
type="submit"
|
||||||
|
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>
|
||||||
|
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||||
|
{{ "cancel" | i18n }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,36 +1,43 @@
|
|||||||
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 {
|
||||||
const request = new DeleteRecoverRequest();
|
const request = new DeleteRecoverRequest();
|
||||||
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",
|
||||||
} catch (e) {
|
null,
|
||||||
this.logService.error(e);
|
this.i18nService.t("deleteRecoverEmailSent")
|
||||||
}
|
);
|
||||||
|
this.router.navigate(["/"]);
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,76 @@
|
|||||||
<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
|
||||||
</p>
|
href="https://help.bitwarden.com/article/lost-two-step-device/"
|
||||||
<div class="form-group">
|
target="_blank"
|
||||||
<label for="email">{{'emailAddress' | i18n}}</label>
|
rel="noopener"
|
||||||
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required
|
>{{ "learnMore" | i18n }}</a
|
||||||
appAutofocus inputmode="email" appInputVerbatim="false">
|
>
|
||||||
</div>
|
</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
<label for="email">{{ "emailAddress" | i18n }}</label>
|
||||||
<input id="masterPassword" type="password" name="MasterPassword" class="form-control"
|
<input
|
||||||
[(ngModel)]="masterPassword" required appInputVerbatim>
|
id="email"
|
||||||
</div>
|
class="form-control"
|
||||||
<div class="form-group">
|
type="text"
|
||||||
<label for="recoveryCode">{{'recoveryCodeTitle' | i18n}}</label>
|
name="Email"
|
||||||
<input id="recoveryCode" class="text-monospace form-control" type="text" name="RecoveryCode"
|
[(ngModel)]="email"
|
||||||
[(ngModel)]="recoveryCode" required appInputVerbatim>
|
required
|
||||||
</div>
|
appAutofocus
|
||||||
<hr>
|
inputmode="email"
|
||||||
<div class="d-flex">
|
appInputVerbatim="false"
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
|
/>
|
||||||
<span>{{'submit' | i18n}}</span>
|
</div>
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
<div class="form-group">
|
||||||
</button>
|
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
<input
|
||||||
{{'cancel' | i18n}}
|
id="masterPassword"
|
||||||
</a>
|
type="password"
|
||||||
</div>
|
name="MasterPassword"
|
||||||
</div>
|
class="form-control"
|
||||||
</div>
|
[(ngModel)]="masterPassword"
|
||||||
|
required
|
||||||
|
appInputVerbatim
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="recoveryCode">{{ "recoveryCodeTitle" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
id="recoveryCode"
|
||||||
|
class="text-monospace form-control"
|
||||||
|
type="text"
|
||||||
|
name="RecoveryCode"
|
||||||
|
[(ngModel)]="recoveryCode"
|
||||||
|
required
|
||||||
|
appInputVerbatim
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="d-flex">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
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>
|
||||||
|
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||||
|
{{ "cancel" | i18n }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,43 +1,52 @@
|
|||||||
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;
|
||||||
masterPassword: string;
|
masterPassword: string;
|
||||||
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",
|
||||||
} catch (e) {
|
null,
|
||||||
this.logService.error(e);
|
this.i18nService.t("twoStepRecoverDisabled")
|
||||||
}
|
);
|
||||||
|
this.router.navigate(["/"]);
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,141 +1,216 @@
|
|||||||
<div class="layout" [ngClass]="['layout', layout]">
|
<div class="layout" [ngClass]="['layout', layout]">
|
||||||
<header class="header" *ngIf="layout === 'enterprise2'">
|
<header class="header" *ngIf="layout === 'enterprise2'">
|
||||||
<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
|
||||||
</div>
|
alt="Bitwarden"
|
||||||
</div>
|
class="logo mb-2"
|
||||||
|
src="../../images/register-layout/logo-horizontal-white.png"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</div>
|
||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
</div>
|
||||||
<div class="row">
|
</header>
|
||||||
<div class="col-7" *ngIf="layout">
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||||
<div class="mt-5">
|
<div class="row">
|
||||||
<div *ngIf="layout === 'enterprise2'">
|
<div class="col-7" *ngIf="layout">
|
||||||
<h2>Companies globally trust Bitwarden for password management.</h2>
|
<div class="mt-5">
|
||||||
<p>Start your 7-day free trial!</p>
|
<div *ngIf="layout === 'enterprise2'">
|
||||||
<p class="highlight">Quickly deploy your <b>organization</b></p>
|
<h2>Companies globally trust Bitwarden for password management.</h2>
|
||||||
<p>Use Bitwarden across all platforms</p>
|
<p>Start your 7-day free trial!</p>
|
||||||
<p>Collaborate and share securely</p>
|
<p class="highlight">Quickly deploy your <b>organization</b></p>
|
||||||
<figure>
|
<p>Use Bitwarden across all platforms</p>
|
||||||
<figcaption>
|
<p>Collaborate and share securely</p>
|
||||||
<cite>
|
<figure>
|
||||||
<img src="../../images/register-layout/wired-logo.png" alt="Wired">
|
<figcaption>
|
||||||
</cite>
|
<cite>
|
||||||
</figcaption>
|
<img src="../../images/register-layout/wired-logo.png" alt="Wired" />
|
||||||
<blockquote>
|
</cite>
|
||||||
"Bitwarden has become a popular choice among open-source software advocates. After using
|
</figcaption>
|
||||||
it for a few months, I can see why." - February 2020
|
<blockquote>
|
||||||
</blockquote>
|
"Bitwarden has become a popular choice among open-source software advocates. After
|
||||||
</figure>
|
using it for a few months, I can see why." - February 2020
|
||||||
</div>
|
</blockquote>
|
||||||
<div *ngIf="layout === 'enterprise3'">
|
</figure>
|
||||||
<p>Enterprise 3 layout</p>
|
</div>
|
||||||
</div>
|
<div *ngIf="layout === 'enterprise3'">
|
||||||
<div *ngIf="layout === 'enterprise4'">
|
<p>Enterprise 3 layout</p>
|
||||||
<p>Enterprise 4 layout</p>
|
</div>
|
||||||
</div>
|
<div *ngIf="layout === 'enterprise4'">
|
||||||
</div>
|
<p>Enterprise 4 layout</p>
|
||||||
</div>
|
</div>
|
||||||
<div [ngClass]="{'col-5': layout, 'col-12': !layout}">
|
|
||||||
<div class="row justify-content-md-center mt-5">
|
|
||||||
<div [ngClass]="{'col-5': !layout, 'col-12': layout}">
|
|
||||||
<p class="lead text-center mb-4" *ngIf="!layout">{{'createAccount' | i18n}}</p>
|
|
||||||
<div class="card d-block">
|
|
||||||
<div class="card-body">
|
|
||||||
<app-callout title="{{'createOrganizationStep1' | i18n}}" type="info"
|
|
||||||
icon="fa-thumb-tack" *ngIf="showCreateOrgMessage">
|
|
||||||
{{'createOrganizationCreatePersonalAccount' | i18n}}
|
|
||||||
</app-callout>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">{{'emailAddress' | i18n}}</label>
|
|
||||||
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email"
|
|
||||||
required [appAutofocus]="email === ''" inputmode="email"
|
|
||||||
appInputVerbatim="false">
|
|
||||||
<small class="form-text text-muted">{{'emailAddressDesc' | i18n}}</small>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="name">{{'yourName' | i18n}}</label>
|
|
||||||
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name"
|
|
||||||
[appAutofocus]="email !== ''">
|
|
||||||
<small class="form-text text-muted">{{'yourNameDesc' | i18n}}</small>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions"
|
|
||||||
*ngIf="enforcedPolicyOptions">
|
|
||||||
</app-callout>
|
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
|
||||||
<div class="d-flex">
|
|
||||||
<div class="w-100">
|
|
||||||
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
|
|
||||||
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>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button type="button" class="ml-1 btn btn-link"
|
|
||||||
appA11yTitle="{{'toggleVisibility' | i18n}}"
|
|
||||||
(click)="togglePassword(false)">
|
|
||||||
<i class="fa fa-lg" aria-hidden="true"
|
|
||||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
|
||||||
</button>
|
|
||||||
<div class="progress-bar invisible"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted">{{'masterPassDesc' | i18n}}</small>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
|
|
||||||
<div class="d-flex">
|
|
||||||
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
|
|
||||||
name="MasterPasswordRetype" class="text-monospace form-control"
|
|
||||||
[(ngModel)]="confirmMasterPassword" 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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="hint">{{'masterPassHint' | i18n}}</label>
|
|
||||||
<input 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 class="form-group" *ngIf="showTerms">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="acceptPolicies"
|
|
||||||
[(ngModel)]="acceptPolicies" name="AcceptPolicies">
|
|
||||||
<label class="form-check-label small text-muted" for="acceptPolicies">
|
|
||||||
{{'acceptPolicies' | i18n}}<br>
|
|
||||||
<a href="https://bitwarden.com/terms/" target="_blank"
|
|
||||||
rel="noopener">{{'termsOfService' | i18n}}</a>,
|
|
||||||
<a href="https://bitwarden.com/privacy/" target="_blank"
|
|
||||||
rel="noopener">{{'privacyPolicy' | i18n}}</a>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="d-flex mb-2">
|
|
||||||
<button type="submit" 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>
|
|
||||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
|
||||||
{{'cancel' | i18n}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
|
<div [ngClass]="{ 'col-5': layout, 'col-12': !layout }">
|
||||||
|
<div class="row justify-content-md-center mt-5">
|
||||||
|
<div [ngClass]="{ 'col-5': !layout, 'col-12': layout }">
|
||||||
|
<p class="lead text-center mb-4" *ngIf="!layout">{{ "createAccount" | i18n }}</p>
|
||||||
|
<div class="card d-block">
|
||||||
|
<div class="card-body">
|
||||||
|
<app-callout
|
||||||
|
title="{{ 'createOrganizationStep1' | i18n }}"
|
||||||
|
type="info"
|
||||||
|
icon="fa-thumb-tack"
|
||||||
|
*ngIf="showCreateOrgMessage"
|
||||||
|
>
|
||||||
|
{{ "createOrganizationCreatePersonalAccount" | i18n }}
|
||||||
|
</app-callout>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">{{ "emailAddress" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
name="Email"
|
||||||
|
[(ngModel)]="email"
|
||||||
|
required
|
||||||
|
[appAutofocus]="email === ''"
|
||||||
|
inputmode="email"
|
||||||
|
appInputVerbatim="false"
|
||||||
|
/>
|
||||||
|
<small class="form-text text-muted">{{ "emailAddressDesc" | i18n }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">{{ "yourName" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
name="Name"
|
||||||
|
[(ngModel)]="name"
|
||||||
|
[appAutofocus]="email !== ''"
|
||||||
|
/>
|
||||||
|
<small class="form-text text-muted">{{ "yourNameDesc" | i18n }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<app-callout
|
||||||
|
type="info"
|
||||||
|
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||||
|
*ngIf="enforcedPolicyOptions"
|
||||||
|
>
|
||||||
|
</app-callout>
|
||||||
|
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="w-100">
|
||||||
|
<input
|
||||||
|
id="masterPassword"
|
||||||
|
type="{{ showPassword ? 'text' : 'password' }}"
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ml-1 btn btn-link"
|
||||||
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
|
(click)="togglePassword(false)"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-lg"
|
||||||
|
aria-hidden="true"
|
||||||
|
[ngClass]="{
|
||||||
|
'fa-eye': !showPassword,
|
||||||
|
'fa-eye-slash': showPassword
|
||||||
|
}"
|
||||||
|
></i>
|
||||||
|
</button>
|
||||||
|
<div class="progress-bar invisible"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">{{ "masterPassDesc" | i18n }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
|
||||||
|
<div class="d-flex">
|
||||||
|
<input
|
||||||
|
id="masterPasswordRetype"
|
||||||
|
type="{{ showPassword ? 'text' : 'password' }}"
|
||||||
|
name="MasterPasswordRetype"
|
||||||
|
class="text-monospace form-control"
|
||||||
|
[(ngModel)]="confirmMasterPassword"
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="hint">{{ "masterPassHint" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
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 class="form-group" *ngIf="showTerms">
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="acceptPolicies"
|
||||||
|
[(ngModel)]="acceptPolicies"
|
||||||
|
name="AcceptPolicies"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label small text-muted" for="acceptPolicies">
|
||||||
|
{{ "acceptPolicies" | i18n }}<br />
|
||||||
|
<a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{
|
||||||
|
"termsOfService" | i18n
|
||||||
|
}}</a
|
||||||
|
>,
|
||||||
|
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{
|
||||||
|
"privacyPolicy" | i18n
|
||||||
|
}}</a>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="d-flex mb-2">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
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>
|
||||||
|
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||||
|
{{ "cancel" | i18n }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,115 +1,150 @@
|
|||||||
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) {
|
|
||||||
this.layout = this.referenceData.layout = qParams.layout;
|
|
||||||
}
|
|
||||||
if (qParams.reference != null) {
|
|
||||||
this.referenceData.id = qParams.reference;
|
|
||||||
} else {
|
|
||||||
this.referenceData.id = ('; ' + document.cookie).split('; reference=').pop().split(';').shift();
|
|
||||||
}
|
|
||||||
// Are they coming from an email for sponsoring a families organization
|
|
||||||
if (qParams.sponsorshipToken != null) {
|
|
||||||
// After logging in redirect them to setup the families sponsorship
|
|
||||||
this.stateService.setLoginRedirect({
|
|
||||||
route: '/setup/families-for-enterprise',
|
|
||||||
qParams: { token: qParams.sponsorshipToken },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.referenceData.id === '') {
|
|
||||||
this.referenceData.id = null;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
const invite = await this.stateService.getOrganizationInvitation();
|
}
|
||||||
if (invite != null) {
|
if (qParams.layout != null) {
|
||||||
try {
|
this.layout = this.referenceData.layout = qParams.layout;
|
||||||
const policies = await this.apiService.getPoliciesByToken(invite.organizationId, invite.token,
|
}
|
||||||
invite.email, invite.organizationUserId);
|
if (qParams.reference != null) {
|
||||||
if (policies.data != null) {
|
this.referenceData.id = qParams.reference;
|
||||||
const policiesData = policies.data.map(p => new PolicyData(p));
|
} else {
|
||||||
this.policies = policiesData.map(p => new Policy(p));
|
this.referenceData.id = ("; " + document.cookie)
|
||||||
}
|
.split("; reference=")
|
||||||
} catch (e) {
|
.pop()
|
||||||
this.logService.error(e);
|
.split(";")
|
||||||
}
|
.shift();
|
||||||
|
}
|
||||||
|
// Are they coming from an email for sponsoring a families organization
|
||||||
|
if (qParams.sponsorshipToken != null) {
|
||||||
|
// After logging in redirect them to setup the families sponsorship
|
||||||
|
this.stateService.setLoginRedirect({
|
||||||
|
route: "/setup/families-for-enterprise",
|
||||||
|
qParams: { token: qParams.sponsorshipToken },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.referenceData.id === "") {
|
||||||
|
this.referenceData.id = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const invite = await this.stateService.getOrganizationInvitation();
|
||||||
|
if (invite != null) {
|
||||||
|
try {
|
||||||
|
const policies = await this.apiService.getPoliciesByToken(
|
||||||
|
invite.organizationId,
|
||||||
|
invite.token,
|
||||||
|
invite.email,
|
||||||
|
invite.organizationUserId
|
||||||
|
);
|
||||||
|
if (policies.data != null) {
|
||||||
|
const policiesData = policies.data.map((p) => new PolicyData(p));
|
||||||
|
this.policies = policiesData.map((p) => new Policy(p));
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
if (this.policies != null) {
|
this.logService.error(e);
|
||||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(this.policies);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await super.ngOnInit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
if (this.policies != null) {
|
||||||
if (this.enforcedPolicyOptions != null &&
|
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(
|
||||||
!this.policyService.evaluateMasterPassword(this.masterPasswordScore, this.masterPassword,
|
this.policies
|
||||||
this.enforcedPolicyOptions)) {
|
);
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
|
||||||
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await super.submit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await super.ngOnInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
if (
|
||||||
|
this.enforcedPolicyOptions != null &&
|
||||||
|
!this.policyService.evaluateMasterPassword(
|
||||||
|
this.masterPasswordScore,
|
||||||
|
this.masterPassword,
|
||||||
|
this.enforcedPolicyOptions
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
|
this.i18nService.t("errorOccurred"),
|
||||||
|
this.i18nService.t("masterPasswordPolicyRequirementsNotMet")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await super.submit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,55 @@
|
|||||||
<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"
|
||||||
</p>
|
title="{{ 'loading' | i18n }}"
|
||||||
</div>
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</p>
|
||||||
|
</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"
|
||||||
</button>
|
(click)="convert()"
|
||||||
<button type="button" class="btn btn-outline-secondary btn-block" (click)="leave()" [disabled]="actionPromise">
|
[disabled]="actionPromise"
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="leaving"></i>
|
>
|
||||||
{{'leaveOrganization' | i18n}}
|
<i
|
||||||
</button>
|
class="fa fa-spinner fa-spin"
|
||||||
</div>
|
title="{{ 'loading' | i18n }}"
|
||||||
</div>
|
aria-hidden="true"
|
||||||
|
*ngIf="continuing"
|
||||||
|
></i>
|
||||||
|
{{ "removeMasterPassword" | i18n }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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 {}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,73 +1,117 @@
|
|||||||
<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 class="card-body" *ngIf="!syncLoading">
|
|
||||||
<app-callout type="info">{{'ssoCompleteRegistration' | i18n}}</app-callout>
|
|
||||||
<app-callout type="warning" title="{{'resetPasswordPolicyAutoEnroll' | i18n}}"
|
|
||||||
*ngIf="resetPasswordAutoEnroll">
|
|
||||||
{{'resetPasswordAutoEnrollInviteWarning' | i18n}}
|
|
||||||
</app-callout>
|
|
||||||
<div class="form-group">
|
|
||||||
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions"
|
|
||||||
*ngIf="enforcedPolicyOptions">
|
|
||||||
</app-callout>
|
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
|
||||||
<div class="d-flex">
|
|
||||||
<div class="w-100">
|
|
||||||
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
|
|
||||||
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>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button type="button" class="ml-1 btn btn-link"
|
|
||||||
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
|
|
||||||
<i class="fa fa-lg" aria-hidden="true"
|
|
||||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
|
||||||
</button>
|
|
||||||
<div class="progress-bar invisible"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted">{{'masterPassDesc' | i18n}}</small>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
|
|
||||||
<div class="d-flex">
|
|
||||||
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
|
|
||||||
name="MasterPasswordRetype" class="text-monospace form-control"
|
|
||||||
[(ngModel)]="masterPasswordRetype" 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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="hint">{{'masterPassHint' | i18n}}</label>
|
|
||||||
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint">
|
|
||||||
<small class="form-text text-muted">{{'masterPassHintDesc' | i18n}}</small>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="d-flex">
|
|
||||||
<button type="submit" 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 type="button" class="btn btn-outline-secondary btn-block ml-2 mt-0" (click)="logOut()">
|
|
||||||
{{'logOut' | i18n}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-body" *ngIf="!syncLoading">
|
||||||
|
<app-callout type="info">{{ "ssoCompleteRegistration" | i18n }}</app-callout>
|
||||||
|
<app-callout
|
||||||
|
type="warning"
|
||||||
|
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
|
||||||
|
*ngIf="resetPasswordAutoEnroll"
|
||||||
|
>
|
||||||
|
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
|
||||||
|
</app-callout>
|
||||||
|
<div class="form-group">
|
||||||
|
<app-callout
|
||||||
|
type="info"
|
||||||
|
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||||
|
*ngIf="enforcedPolicyOptions"
|
||||||
|
>
|
||||||
|
</app-callout>
|
||||||
|
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="w-100">
|
||||||
|
<input
|
||||||
|
id="masterPassword"
|
||||||
|
type="{{ showPassword ? 'text' : 'password' }}"
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ml-1 btn btn-link"
|
||||||
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
|
(click)="togglePassword(false)"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-lg"
|
||||||
|
aria-hidden="true"
|
||||||
|
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
|
||||||
|
></i>
|
||||||
|
</button>
|
||||||
|
<div class="progress-bar invisible"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">{{ "masterPassDesc" | i18n }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
|
||||||
|
<div class="d-flex">
|
||||||
|
<input
|
||||||
|
id="masterPasswordRetype"
|
||||||
|
type="{{ showPassword ? 'text' : 'password' }}"
|
||||||
|
name="MasterPasswordRetype"
|
||||||
|
class="text-monospace form-control"
|
||||||
|
[(ngModel)]="masterPasswordRetype"
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="hint">{{ "masterPassHint" | i18n }}</label>
|
||||||
|
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint" />
|
||||||
|
<small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="d-flex">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
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
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary btn-block ml-2 mt-0"
|
||||||
|
(click)="logOut()"
|
||||||
|
>
|
||||||
|
{{ "logOut" | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,52 @@
|
|||||||
<form #form (ngSubmit)="submit()" class="container" [appApiAction]="initiateSsoFormPromise" ngNativeValidate>
|
<form
|
||||||
<div class="row justify-content-md-center mt-5">
|
#form
|
||||||
<div class="col-5">
|
(ngSubmit)="submit()"
|
||||||
<img class="logo mb-2 logo-themed" alt="Bitwarden">
|
class="container"
|
||||||
<div class="card d-block mt-4">
|
[appApiAction]="initiateSsoFormPromise"
|
||||||
<div class="card-body" *ngIf="loggingIn">
|
ngNativeValidate
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
>
|
||||||
{{'loading' | i18n}}
|
<div class="row justify-content-md-center mt-5">
|
||||||
</div>
|
<div class="col-5">
|
||||||
<div class="card-body" *ngIf="!loggingIn">
|
<img class="logo mb-2 logo-themed" alt="Bitwarden" />
|
||||||
<p>{{'ssoLogInWithOrgIdentifier' | i18n}}</p>
|
<div class="card d-block mt-4">
|
||||||
<div class="form-group">
|
<div class="card-body" *ngIf="loggingIn">
|
||||||
<label for="identifier">{{'organizationIdentifier' | i18n}}</label>
|
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||||
<input id="identifier" class="form-control" type="text" name="Identifier"
|
{{ "loading" | i18n }}
|
||||||
[(ngModel)]="identifier" required appAutofocus>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="d-flex">
|
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
|
|
||||||
<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>
|
|
||||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
|
||||||
{{'cancel' | i18n}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-body" *ngIf="!loggingIn">
|
||||||
|
<p>{{ "ssoLogInWithOrgIdentifier" | i18n }}</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="identifier">{{ "organizationIdentifier" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
id="identifier"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
name="Identifier"
|
||||||
|
[(ngModel)]="identifier"
|
||||||
|
required
|
||||||
|
appAutofocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="d-flex">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary btn-block btn-submit"
|
||||||
|
[disabled]="form.loading"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||||
|
{{ "cancel" | i18n }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,59 +1,74 @@
|
|||||||
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 {
|
||||||
const storedIdentifier = await this.stateService.getSsoOrgIdentifier();
|
const storedIdentifier = await this.stateService.getSsoOrgIdentifier();
|
||||||
if (storedIdentifier != null) {
|
if (storedIdentifier != null) {
|
||||||
this.identifier = storedIdentifier;
|
this.identifier = storedIdentifier;
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async submit() {
|
|
||||||
await this.stateService.setSsoOrganizationIdentifier(this.identifier);
|
|
||||||
if (this.clientId === 'browser') {
|
|
||||||
document.cookie = `ssoHandOffMessage=${this.i18nService.t('ssoHandOff')};SameSite=strict`;
|
|
||||||
}
|
}
|
||||||
super.submit();
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
await this.stateService.setSsoOrganizationIdentifier(this.identifier);
|
||||||
|
if (this.clientId === "browser") {
|
||||||
|
document.cookie = `ssoHandOffMessage=${this.i18nService.t("ssoHandOff")};SameSite=strict`;
|
||||||
}
|
}
|
||||||
|
super.submit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,68 @@
|
|||||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="twoStepOptionsTitle">
|
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="twoStepOptionsTitle">
|
||||||
<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
|
||||||
<span aria-hidden="true">×</span>
|
type="button"
|
||||||
|
class="close"
|
||||||
|
data-dismiss="modal"
|
||||||
|
appA11yTitle="{{ 'close' | i18n }}"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="list-group list-group-flush-2fa">
|
||||||
|
<div *ngFor="let p of providers" class="list-group-item list-group-item-action">
|
||||||
|
<div class="two-factor-content">
|
||||||
|
<div class="logo-col">
|
||||||
|
<img [class]="'mfaType' + p.type" [alt]="p.name + ' logo'" />
|
||||||
|
</div>
|
||||||
|
<div class="text-col">
|
||||||
|
<h3>{{ p.name }}</h3>
|
||||||
|
{{ p.description }}
|
||||||
|
</div>
|
||||||
|
<div class="btn-col">
|
||||||
|
<button
|
||||||
|
[attr.aria-describedby]="p.name"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary btn-sm"
|
||||||
|
(click)="choose(p)"
|
||||||
|
>
|
||||||
|
{{ "select" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
</div>
|
||||||
<div class="list-group list-group-flush-2fa">
|
<div class="list-group-item list-group-item-action" (click)="recover()">
|
||||||
<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="recovery-code-img" alt="rc logo" />
|
||||||
<img [class]="'mfaType' + p.type" [alt]="p.name + ' logo'">
|
</div>
|
||||||
</div>
|
<div class="text-col">
|
||||||
<div class="text-col">
|
<h3>{{ "recoveryCodeTitle" | i18n }}</h3>
|
||||||
<h3>{{p.name}}</h3>
|
{{ "recoveryCodeDesc" | i18n }}
|
||||||
{{p.description}}
|
</div>
|
||||||
</div>
|
<div class="btn-col">
|
||||||
<div class="btn-col">
|
<button
|
||||||
<button [attr.aria-describedby]="p.name" type="button"
|
[attr.aria-descibedby]="'recoveryCodeTitle' | i18n"
|
||||||
class="btn btn-outline-secondary btn-sm" (click)="choose(p)">
|
type="button"
|
||||||
{{'select' | i18n}}
|
class="btn btn-outline-secondary btn-sm"
|
||||||
</button>
|
(click)="recover()"
|
||||||
</div>
|
>
|
||||||
</div>
|
{{ "select" | i18n }}
|
||||||
</div>
|
</button>
|
||||||
<div class="list-group-item list-group-item-action" (click)="recover()">
|
</div>
|
||||||
<div class="two-factor-content">
|
|
||||||
<div class="logo-col">
|
|
||||||
<img class="recovery-code-img" alt="rc logo">
|
|
||||||
</div>
|
|
||||||
<div class="text-col">
|
|
||||||
<h3>{{'recoveryCodeTitle' | i18n}}</h3>
|
|
||||||
{{'recoveryCodeDesc' | i18n}}
|
|
||||||
</div>
|
|
||||||
<div class="btn-col">
|
|
||||||
<button [attr.aria-descibedby]="'recoveryCodeTitle' | i18n" type="button"
|
|
||||||
class="btn btn-outline-secondary btn-sm" (click)="recover()">
|
|
||||||
{{'select' | i18n}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' |
|
|
||||||
i18n}}</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||||
|
{{ "close" | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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,
|
||||||
super(authService, router, i18nService, platformUtilsService, window);
|
router: Router,
|
||||||
}
|
i18nService: I18nService,
|
||||||
|
platformUtilsService: PlatformUtilsService
|
||||||
|
) {
|
||||||
|
super(authService, router, i18nService, platformUtilsService, window);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +1,148 @@
|
|||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate autocomplete="off">
|
<form
|
||||||
<div class="row justify-content-md-center mt-5">
|
#form
|
||||||
<div class="col-5"
|
(ngSubmit)="submit()"
|
||||||
[ngClass]="{'col-9': selectedProviderType === providerType.Duo || selectedProviderType === providerType.OrganizationDuo}">
|
[appApiAction]="formPromise"
|
||||||
<p class="lead text-center mb-4">{{title}}</p>
|
class="container"
|
||||||
<div class="card d-block">
|
ngNativeValidate
|
||||||
<div class="card-body">
|
autocomplete="off"
|
||||||
<ng-container
|
>
|
||||||
*ngIf="selectedProviderType === providerType.Email || selectedProviderType === providerType.Authenticator">
|
<div class="row justify-content-md-center mt-5">
|
||||||
<p *ngIf="selectedProviderType === providerType.Authenticator">
|
<div
|
||||||
{{'enterVerificationCodeApp' | i18n}}</p>
|
class="col-5"
|
||||||
<p *ngIf="selectedProviderType === providerType.Email">
|
[ngClass]="{
|
||||||
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}}
|
'col-9':
|
||||||
</p>
|
selectedProviderType === providerType.Duo ||
|
||||||
<div class="form-group">
|
selectedProviderType === providerType.OrganizationDuo
|
||||||
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label>
|
}"
|
||||||
<input id="code" type="text" name="Code" class="form-control" [(ngModel)]="token" required
|
>
|
||||||
appAutofocus inputmode="tel" appInputVerbatim>
|
<p class="lead text-center mb-4">{{ title }}</p>
|
||||||
<small class="form-text" *ngIf="selectedProviderType === providerType.Email">
|
<div class="card d-block">
|
||||||
<a href="#" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise"
|
<div class="card-body">
|
||||||
*ngIf="selectedProviderType === providerType.Email">
|
<ng-container
|
||||||
{{'sendVerificationCodeEmailAgain' | i18n}}
|
*ngIf="
|
||||||
</a>
|
selectedProviderType === providerType.Email ||
|
||||||
</small>
|
selectedProviderType === providerType.Authenticator
|
||||||
</div>
|
"
|
||||||
</ng-container>
|
>
|
||||||
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
|
<p *ngIf="selectedProviderType === providerType.Authenticator">
|
||||||
<p class="text-center">{{'insertYubiKey' | i18n}}</p>
|
{{ "enterVerificationCodeApp" | i18n }}
|
||||||
<img src="../../images/yubikey.jpg" class="rounded img-fluid mb-3" alt="">
|
</p>
|
||||||
<div class="form-group">
|
<p *ngIf="selectedProviderType === providerType.Email">
|
||||||
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label>
|
{{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }}
|
||||||
<input id="code" type="password" name="Code" class="form-control" [(ngModel)]="token"
|
</p>
|
||||||
required appAutofocus appInputVerbatim autocomplete="new-password">
|
<div class="form-group">
|
||||||
</div>
|
<label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
|
||||||
</ng-container>
|
<input
|
||||||
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
|
id="code"
|
||||||
<div id="web-authn-frame" class="mb-3">
|
type="text"
|
||||||
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
|
name="Code"
|
||||||
</div>
|
class="form-control"
|
||||||
</ng-container>
|
[(ngModel)]="token"
|
||||||
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
|
required
|
||||||
selectedProviderType === providerType.OrganizationDuo">
|
appAutofocus
|
||||||
<div id="duo-frame" class="mb-3">
|
inputmode="tel"
|
||||||
<iframe id="duo_iframe"></iframe>
|
appInputVerbatim
|
||||||
</div>
|
/>
|
||||||
</ng-container>
|
<small class="form-text" *ngIf="selectedProviderType === providerType.Email">
|
||||||
<i class="fa fa-spinner text-muted fa-spin pull-right" title="{{'loading' | i18n}}"
|
<a
|
||||||
*ngIf="form.loading && selectedProviderType === providerType.WebAuthn" aria-hidden="true"></i>
|
href="#"
|
||||||
<div class="form-check" *ngIf="selectedProviderType != null">
|
appStopClick
|
||||||
<input id="remember" type="checkbox" name="Remember" class="form-check-input"
|
(click)="sendEmail(true)"
|
||||||
[(ngModel)]="remember">
|
[appApiAction]="emailPromise"
|
||||||
<label for="remember" class="form-check-label">{{'rememberMe' | i18n}}</label>
|
*ngIf="selectedProviderType === providerType.Email"
|
||||||
</div>
|
>
|
||||||
<ng-container *ngIf="selectedProviderType == null">
|
{{ "sendVerificationCodeEmailAgain" | i18n }}
|
||||||
<p>{{'noTwoStepProviders' | i18n}}</p>
|
</a>
|
||||||
<p>{{'noTwoStepProviders2' | i18n}}</p>
|
</small>
|
||||||
</ng-container>
|
|
||||||
<hr>
|
|
||||||
<div class="d-flex mb-3">
|
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading"
|
|
||||||
*ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
|
|
||||||
selectedProviderType !== providerType.OrganizationDuo && selectedProviderType !== providerType.WebAuthn">
|
|
||||||
<span>
|
|
||||||
<i class="fa fa-sign-in" aria-hidden="true"></i> {{'continue' | i18n}}
|
|
||||||
</span>
|
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
|
||||||
{{'cancel' | i18n}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
|
||||||
<a href="#" appStopClick (click)="anotherMethod()">{{'useAnotherTwoStepMethod' | i18n}}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
|
||||||
|
<p class="text-center">{{ "insertYubiKey" | i18n }}</p>
|
||||||
|
<img src="../../images/yubikey.jpg" class="rounded img-fluid mb-3" alt="" />
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
id="code"
|
||||||
|
type="password"
|
||||||
|
name="Code"
|
||||||
|
class="form-control"
|
||||||
|
[(ngModel)]="token"
|
||||||
|
required
|
||||||
|
appAutofocus
|
||||||
|
appInputVerbatim
|
||||||
|
autocomplete="new-password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
|
||||||
|
<div id="web-authn-frame" class="mb-3">
|
||||||
|
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container
|
||||||
|
*ngIf="
|
||||||
|
selectedProviderType === providerType.Duo ||
|
||||||
|
selectedProviderType === providerType.OrganizationDuo
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div id="duo-frame" class="mb-3">
|
||||||
|
<iframe id="duo_iframe"></iframe>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<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">
|
||||||
|
<input
|
||||||
|
id="remember"
|
||||||
|
type="checkbox"
|
||||||
|
name="Remember"
|
||||||
|
class="form-check-input"
|
||||||
|
[(ngModel)]="remember"
|
||||||
|
/>
|
||||||
|
<label for="remember" class="form-check-label">{{ "rememberMe" | i18n }}</label>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngIf="selectedProviderType == null">
|
||||||
|
<p>{{ "noTwoStepProviders" | i18n }}</p>
|
||||||
|
<p>{{ "noTwoStepProviders2" | i18n }}</p>
|
||||||
|
</ng-container>
|
||||||
|
<hr />
|
||||||
|
<div class="d-flex mb-3">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary btn-block btn-submit"
|
||||||
|
[disabled]="form.loading"
|
||||||
|
*ngIf="
|
||||||
|
selectedProviderType != null &&
|
||||||
|
selectedProviderType !== providerType.Duo &&
|
||||||
|
selectedProviderType !== providerType.OrganizationDuo &&
|
||||||
|
selectedProviderType !== providerType.WebAuthn
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<i class="fa fa-sign-in" aria-hidden="true"></i> {{ "continue" | i18n }}
|
||||||
|
</span>
|
||||||
|
<i
|
||||||
|
class="fa fa-spinner fa-spin"
|
||||||
|
title="{{ 'loading' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
</button>
|
||||||
|
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||||
|
{{ "cancel" | i18n }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="#" appStopClick (click)="anotherMethod()">{{
|
||||||
|
"useAnotherTwoStepMethod" | i18n
|
||||||
|
}}</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<ng-template #twoFactorOptions></ng-template>
|
<ng-template #twoFactorOptions></ng-template>
|
||||||
|
|||||||
@@ -1,71 +1,86 @@
|
|||||||
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,
|
||||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
environmentService: EnvironmentService,
|
||||||
}
|
private modalService: ModalService,
|
||||||
|
route: ActivatedRoute,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
authService,
|
||||||
|
router,
|
||||||
|
i18nService,
|
||||||
|
apiService,
|
||||||
|
platformUtilsService,
|
||||||
|
window,
|
||||||
|
environmentService,
|
||||||
|
stateService,
|
||||||
|
route,
|
||||||
|
logService
|
||||||
|
);
|
||||||
|
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
||||||
|
}
|
||||||
|
|
||||||
async anotherMethod() {
|
async anotherMethod() {
|
||||||
const [modal] = await this.modalService.openViewRef(TwoFactorOptionsComponent, this.twoFactorOptionsModal, comp => {
|
const [modal] = await this.modalService.openViewRef(
|
||||||
comp.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
|
TwoFactorOptionsComponent,
|
||||||
modal.close();
|
this.twoFactorOptionsModal,
|
||||||
this.selectedProviderType = provider;
|
(comp) => {
|
||||||
await this.init();
|
comp.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
|
||||||
});
|
modal.close();
|
||||||
comp.onRecoverSelected.subscribe(() => {
|
this.selectedProviderType = provider;
|
||||||
modal.close();
|
await this.init();
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
comp.onRecoverSelected.subscribe(() => {
|
||||||
|
modal.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async goAfterLogIn() {
|
async goAfterLogIn() {
|
||||||
const loginRedirect = await this.stateService.getLoginRedirect();
|
const loginRedirect = await this.stateService.getLoginRedirect();
|
||||||
if (loginRedirect != null) {
|
if (loginRedirect != null) {
|
||||||
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
|
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
|
||||||
await this.stateService.setLoginRedirect(null);
|
await this.stateService.setLoginRedirect(null);
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate([this.successRoute], {
|
this.router.navigate([this.successRoute], {
|
||||||
queryParams: {
|
queryParams: {
|
||||||
identifier: this.identifier,
|
identifier: this.identifier,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +1,105 @@
|
|||||||
<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
|
||||||
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions"
|
type="info"
|
||||||
*ngIf="enforcedPolicyOptions">
|
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||||
</app-callout>
|
*ngIf="enforcedPolicyOptions"
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
>
|
||||||
<div class="d-flex">
|
</app-callout>
|
||||||
<div class="w-100">
|
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||||
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
|
<div class="d-flex">
|
||||||
name="MasterPasswordHash" class="text-monospace form-control mb-1"
|
<div class="w-100">
|
||||||
[(ngModel)]="masterPassword" (input)="updatePasswordStrength()" required
|
<input
|
||||||
appInputVerbatim>
|
id="masterPassword"
|
||||||
<app-password-strength [score]="masterPasswordScore" [showText]="true">
|
type="{{ showPassword ? 'text' : 'password' }}"
|
||||||
</app-password-strength>
|
name="MasterPasswordHash"
|
||||||
</div>
|
class="text-monospace form-control mb-1"
|
||||||
<div>
|
[(ngModel)]="masterPassword"
|
||||||
<button type="button" class="ml-1 btn btn-link"
|
(input)="updatePasswordStrength()"
|
||||||
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
|
required
|
||||||
<i class="fa fa-lg" aria-hidden="true"
|
appInputVerbatim
|
||||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
/>
|
||||||
</button>
|
<app-password-strength [score]="masterPasswordScore" [showText]="true">
|
||||||
<div class="progress-bar invisible"></div>
|
</app-password-strength>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
</div>
|
<button
|
||||||
<div class="form-group">
|
type="button"
|
||||||
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
|
class="ml-1 btn btn-link"
|
||||||
<div class="d-flex">
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
|
(click)="togglePassword(false)"
|
||||||
name="MasterPasswordRetype" class="text-monospace form-control"
|
>
|
||||||
[(ngModel)]="masterPasswordRetype" required appInputVerbatim>
|
<i
|
||||||
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}"
|
class="fa fa-lg"
|
||||||
(click)="togglePassword(true)">
|
aria-hidden="true"
|
||||||
<i class="fa fa-lg" aria-hidden="true"
|
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
|
||||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
<div class="progress-bar invisible"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label for="hint">{{'masterPassHint' | i18n}}</label>
|
|
||||||
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint">
|
|
||||||
<small class="form-text text-muted">{{'masterPassHintDesc' | i18n}}</small>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="d-flex">
|
|
||||||
<button type="submit" 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 type="button" class="btn btn-outline-secondary btn-block ml-2 mt-0" (click)="logOut()">
|
|
||||||
{{'logOut' | i18n}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
|
||||||
|
<div class="d-flex">
|
||||||
|
<input
|
||||||
|
id="masterPasswordRetype"
|
||||||
|
type="{{ showPassword ? 'text' : 'password' }}"
|
||||||
|
name="MasterPasswordRetype"
|
||||||
|
class="text-monospace form-control"
|
||||||
|
[(ngModel)]="masterPasswordRetype"
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="hint">{{ "masterPassHint" | i18n }}</label>
|
||||||
|
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint" />
|
||||||
|
<small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="d-flex">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
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
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary btn-block ml-2 mt-0"
|
||||||
|
(click)="logOut()"
|
||||||
|
>
|
||||||
|
{{ "logOut" | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
</p>
|
title="{{ 'loading' | i18n }}"
|
||||||
</div>
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,55 +1,50 @@
|
|||||||
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(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
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()) {
|
);
|
||||||
await this.apiService.refreshIdentityToken();
|
if (await this.stateService.getIsAuthenticated()) {
|
||||||
}
|
await this.apiService.refreshIdentityToken();
|
||||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('emailVerified'));
|
}
|
||||||
this.router.navigate(['/']);
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("emailVerified"));
|
||||||
return;
|
this.router.navigate(["/"]);
|
||||||
} catch (e) {
|
return;
|
||||||
this.logService.error(e);
|
} catch (e) {
|
||||||
}
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('emailVerifiedFailed'));
|
}
|
||||||
this.router.navigate(['/']);
|
this.platformUtilsService.showToast("error", null, this.i18nService.t("emailVerifiedFailed"));
|
||||||
});
|
this.router.navigate(["/"]);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,34 @@
|
|||||||
<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"
|
||||||
</button>
|
[disabled]="form.loading"
|
||||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
>
|
||||||
{{'cancel' | i18n}}
|
<span>{{ "deleteAccount" | i18n }}</span>
|
||||||
</a>
|
<i
|
||||||
</div>
|
class="fa fa-spinner fa-spin"
|
||||||
</div>
|
title="{{ 'loading' | i18n }}"
|
||||||
</div>
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
</button>
|
||||||
|
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||||
|
{{ "cancel" | i18n }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,59 +1,60 @@
|
|||||||
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;
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
|
|
||||||
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,
|
||||||
ngOnInit() {
|
private route: ActivatedRoute,
|
||||||
this.route.queryParams.pipe(first()).subscribe(async qParams => {
|
private logService: LogService
|
||||||
if (qParams.userId != null && qParams.token != null && qParams.email != null) {
|
) {}
|
||||||
this.userId = qParams.userId;
|
|
||||||
this.token = qParams.token;
|
ngOnInit() {
|
||||||
this.email = qParams.email;
|
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||||
} else {
|
if (qParams.userId != null && qParams.token != null && qParams.email != null) {
|
||||||
this.router.navigate(['/']);
|
this.userId = qParams.userId;
|
||||||
}
|
this.token = qParams.token;
|
||||||
});
|
this.email = qParams.email;
|
||||||
}
|
} else {
|
||||||
|
this.router.navigate(["/"]);
|
||||||
async submit() {
|
}
|
||||||
try {
|
});
|
||||||
const request = new VerifyDeleteRecoverRequest(this.userId, this.token);
|
}
|
||||||
this.formPromise = this.apiService.postAccountRecoverDeleteToken(request);
|
|
||||||
await this.formPromise;
|
async submit() {
|
||||||
this.platformUtilsService.showToast('success', this.i18nService.t('accountDeleted'),
|
try {
|
||||||
this.i18nService.t('accountDeletedDesc'));
|
const request = new VerifyDeleteRecoverRequest(this.userId, this.token);
|
||||||
this.router.navigate(['/']);
|
this.formPromise = this.apiService.postAccountRecoverDeleteToken(request);
|
||||||
} catch (e) {
|
await this.formPromise;
|
||||||
this.logService.error(e);
|
this.platformUtilsService.showToast(
|
||||||
}
|
"success",
|
||||||
|
this.i18nService.t("accountDeleted"),
|
||||||
|
this.i18nService.t("accountDeletedDesc")
|
||||||
|
);
|
||||||
|
this.router.navigate(["/"]);
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,300 +1,310 @@
|
|||||||
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 idleTimer: number = null;
|
||||||
|
private isIdle = false;
|
||||||
|
|
||||||
private lastActivity: number = null;
|
constructor(
|
||||||
private idleTimer: number = null;
|
private broadcasterService: BroadcasterService,
|
||||||
private isIdle = false;
|
private tokenService: TokenService,
|
||||||
|
private folderService: FolderService,
|
||||||
|
private settingsService: SettingsService,
|
||||||
|
private syncService: SyncService,
|
||||||
|
private passwordGenerationService: PasswordGenerationService,
|
||||||
|
private cipherService: CipherService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router,
|
||||||
|
private toastrService: ToastrService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private ngZone: NgZone,
|
||||||
|
private vaultTimeoutService: VaultTimeoutService,
|
||||||
|
private cryptoService: CryptoService,
|
||||||
|
private collectionService: CollectionService,
|
||||||
|
private sanitizer: DomSanitizer,
|
||||||
|
private searchService: SearchService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private routerService: RouterService,
|
||||||
|
private stateService: StateService,
|
||||||
|
private eventService: EventService,
|
||||||
|
private policyService: PolicyService,
|
||||||
|
protected policyListService: PolicyListService,
|
||||||
|
private keyConnectorService: KeyConnectorService
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor(
|
ngOnInit() {
|
||||||
private broadcasterService: BroadcasterService,
|
this.ngZone.runOutsideAngular(() => {
|
||||||
private tokenService: TokenService,
|
window.onmousemove = () => this.recordActivity();
|
||||||
private folderService: FolderService,
|
window.onmousedown = () => this.recordActivity();
|
||||||
private settingsService: SettingsService,
|
window.ontouchstart = () => this.recordActivity();
|
||||||
private syncService: SyncService,
|
window.onclick = () => this.recordActivity();
|
||||||
private passwordGenerationService: PasswordGenerationService,
|
window.onscroll = () => this.recordActivity();
|
||||||
private cipherService: CipherService,
|
window.onkeypress = () => this.recordActivity();
|
||||||
private authService: AuthService,
|
});
|
||||||
private router: Router,
|
|
||||||
private toastrService: ToastrService,
|
|
||||||
private i18nService: I18nService,
|
|
||||||
private platformUtilsService: PlatformUtilsService,
|
|
||||||
private ngZone: NgZone,
|
|
||||||
private vaultTimeoutService: VaultTimeoutService,
|
|
||||||
private cryptoService: CryptoService,
|
|
||||||
private collectionService: CollectionService,
|
|
||||||
private sanitizer: DomSanitizer,
|
|
||||||
private searchService: SearchService,
|
|
||||||
private notificationsService: NotificationsService,
|
|
||||||
private routerService: RouterService,
|
|
||||||
private stateService: StateService,
|
|
||||||
private eventService: EventService,
|
|
||||||
private policyService: PolicyService,
|
|
||||||
protected policyListService: PolicyListService,
|
|
||||||
private keyConnectorService: KeyConnectorService
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit() {
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||||
this.ngZone.runOutsideAngular(() => {
|
this.ngZone.run(async () => {
|
||||||
window.onmousemove = () => this.recordActivity();
|
switch (message.command) {
|
||||||
window.onmousedown = () => this.recordActivity();
|
case "loggedIn":
|
||||||
window.ontouchstart = () => this.recordActivity();
|
case "loggedOut":
|
||||||
window.onclick = () => this.recordActivity();
|
case "unlocked":
|
||||||
window.onscroll = () => this.recordActivity();
|
this.notificationsService.updateConnection(false);
|
||||||
window.onkeypress = () => this.recordActivity();
|
break;
|
||||||
});
|
case "authBlocked":
|
||||||
|
this.router.navigate(["/"]);
|
||||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
break;
|
||||||
this.ngZone.run(async () => {
|
case "logout":
|
||||||
switch (message.command) {
|
this.logOut(!!message.expired);
|
||||||
case 'loggedIn':
|
break;
|
||||||
case 'loggedOut':
|
case "lockVault":
|
||||||
case 'unlocked':
|
await this.vaultTimeoutService.lock();
|
||||||
this.notificationsService.updateConnection(false);
|
break;
|
||||||
break;
|
case "locked":
|
||||||
case 'authBlocked':
|
this.notificationsService.updateConnection(false);
|
||||||
this.router.navigate(['/']);
|
this.router.navigate(["lock"]);
|
||||||
break;
|
break;
|
||||||
case 'logout':
|
case "lockedUrl":
|
||||||
this.logOut(!!message.expired);
|
window.setTimeout(() => this.routerService.setPreviousUrl(message.url), 500);
|
||||||
break;
|
break;
|
||||||
case 'lockVault':
|
case "syncStarted":
|
||||||
await this.vaultTimeoutService.lock();
|
break;
|
||||||
break;
|
case "syncCompleted":
|
||||||
case 'locked':
|
break;
|
||||||
this.notificationsService.updateConnection(false);
|
case "upgradeOrganization":
|
||||||
this.router.navigate(['lock']);
|
const upgradeConfirmed = await this.platformUtilsService.showDialog(
|
||||||
break;
|
this.i18nService.t("upgradeOrganizationDesc"),
|
||||||
case 'lockedUrl':
|
this.i18nService.t("upgradeOrganization"),
|
||||||
window.setTimeout(() => this.routerService.setPreviousUrl(message.url), 500);
|
this.i18nService.t("upgradeOrganization"),
|
||||||
break;
|
this.i18nService.t("cancel")
|
||||||
case 'syncStarted':
|
);
|
||||||
break;
|
if (upgradeConfirmed) {
|
||||||
case 'syncCompleted':
|
this.router.navigate([
|
||||||
break;
|
"organizations",
|
||||||
case 'upgradeOrganization':
|
message.organizationId,
|
||||||
const upgradeConfirmed = await this.platformUtilsService.showDialog(
|
"settings",
|
||||||
this.i18nService.t('upgradeOrganizationDesc'), this.i18nService.t('upgradeOrganization'),
|
"billing",
|
||||||
this.i18nService.t('upgradeOrganization'), this.i18nService.t('cancel'));
|
]);
|
||||||
if (upgradeConfirmed) {
|
|
||||||
this.router.navigate(['organizations', message.organizationId, 'settings', 'billing']);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'premiumRequired':
|
|
||||||
const premiumConfirmed = await this.platformUtilsService.showDialog(
|
|
||||||
this.i18nService.t('premiumRequiredDesc'), this.i18nService.t('premiumRequired'),
|
|
||||||
this.i18nService.t('learnMore'), this.i18nService.t('cancel'));
|
|
||||||
if (premiumConfirmed) {
|
|
||||||
this.router.navigate(['settings/premium']);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'emailVerificationRequired':
|
|
||||||
const emailVerificationConfirmed = await this.platformUtilsService.showDialog(
|
|
||||||
this.i18nService.t('emailVerificationRequiredDesc'),
|
|
||||||
this.i18nService.t('emailVerificationRequired'),
|
|
||||||
this.i18nService.t('learnMore'), this.i18nService.t('cancel'));
|
|
||||||
if (emailVerificationConfirmed) {
|
|
||||||
this.platformUtilsService.launchUri('https://bitwarden.com/help/article/create-bitwarden-account/');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'showToast':
|
|
||||||
this.showToast(message);
|
|
||||||
break;
|
|
||||||
case 'setFullWidth':
|
|
||||||
this.setFullWidth();
|
|
||||||
break;
|
|
||||||
case 'convertAccountToKeyConnector':
|
|
||||||
this.keyConnectorService.setConvertAccountRequired(true);
|
|
||||||
this.router.navigate(['/remove-password']);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.router.events.subscribe(event => {
|
|
||||||
if (event instanceof NavigationEnd) {
|
|
||||||
const modals = Array.from(document.querySelectorAll('.modal'));
|
|
||||||
for (const modal of modals) {
|
|
||||||
(jq(modal) as any).modal('hide');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.querySelector('.swal-modal') != null) {
|
|
||||||
Swal.close(undefined);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
break;
|
||||||
|
case "premiumRequired":
|
||||||
this.policyListService.addPolicies([
|
const premiumConfirmed = await this.platformUtilsService.showDialog(
|
||||||
new TwoFactorAuthenticationPolicy(),
|
this.i18nService.t("premiumRequiredDesc"),
|
||||||
new MasterPasswordPolicy(),
|
this.i18nService.t("premiumRequired"),
|
||||||
new PasswordGeneratorPolicy(),
|
this.i18nService.t("learnMore"),
|
||||||
new SingleOrgPolicy(),
|
this.i18nService.t("cancel")
|
||||||
new RequireSsoPolicy(),
|
);
|
||||||
new PersonalOwnershipPolicy(),
|
if (premiumConfirmed) {
|
||||||
new DisableSendPolicy(),
|
this.router.navigate(["settings/premium"]);
|
||||||
new SendOptionsPolicy(),
|
|
||||||
new ResetPasswordPolicy(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.setFullWidth();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async logOut(expired: boolean) {
|
|
||||||
await this.eventService.uploadEvents();
|
|
||||||
const userId = await this.stateService.getUserId();
|
|
||||||
await Promise.all([
|
|
||||||
this.eventService.clearEvents(),
|
|
||||||
this.syncService.setLastSync(new Date(0)),
|
|
||||||
this.tokenService.clearToken(),
|
|
||||||
this.cryptoService.clearKeys(),
|
|
||||||
this.settingsService.clear(userId),
|
|
||||||
this.cipherService.clear(userId),
|
|
||||||
this.folderService.clear(userId),
|
|
||||||
this.collectionService.clear(userId),
|
|
||||||
this.policyService.clear(userId),
|
|
||||||
this.passwordGenerationService.clear(),
|
|
||||||
this.keyConnectorService.clear(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.searchService.clearIndex();
|
|
||||||
this.authService.logOut(async () => {
|
|
||||||
if (expired) {
|
|
||||||
this.platformUtilsService.showToast('warning', this.i18nService.t('loggedOut'),
|
|
||||||
this.i18nService.t('loginExpired'));
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
await this.stateService.clean({ userId: userId });
|
case "emailVerificationRequired":
|
||||||
Swal.close();
|
const emailVerificationConfirmed = await this.platformUtilsService.showDialog(
|
||||||
this.router.navigate(['/']);
|
this.i18nService.t("emailVerificationRequiredDesc"),
|
||||||
});
|
this.i18nService.t("emailVerificationRequired"),
|
||||||
}
|
this.i18nService.t("learnMore"),
|
||||||
|
this.i18nService.t("cancel")
|
||||||
private async recordActivity() {
|
);
|
||||||
const now = (new Date()).getTime();
|
if (emailVerificationConfirmed) {
|
||||||
if (this.lastActivity != null && now - this.lastActivity < 250) {
|
this.platformUtilsService.launchUri(
|
||||||
return;
|
"https://bitwarden.com/help/article/create-bitwarden-account/"
|
||||||
}
|
);
|
||||||
|
|
||||||
this.lastActivity = now;
|
|
||||||
this.stateService.setLastActive(now);
|
|
||||||
// Idle states
|
|
||||||
if (this.isIdle) {
|
|
||||||
this.isIdle = false;
|
|
||||||
this.idleStateChanged();
|
|
||||||
}
|
|
||||||
if (this.idleTimer != null) {
|
|
||||||
window.clearTimeout(this.idleTimer);
|
|
||||||
this.idleTimer = null;
|
|
||||||
}
|
|
||||||
this.idleTimer = window.setTimeout(() => {
|
|
||||||
if (!this.isIdle) {
|
|
||||||
this.isIdle = true;
|
|
||||||
this.idleStateChanged();
|
|
||||||
}
|
}
|
||||||
}, IdleTimeout);
|
break;
|
||||||
}
|
case "showToast":
|
||||||
|
this.showToast(message);
|
||||||
private showToast(msg: any) {
|
break;
|
||||||
let message = '';
|
case "setFullWidth":
|
||||||
|
this.setFullWidth();
|
||||||
const options: Partial<IndividualConfig> = {};
|
break;
|
||||||
|
case "convertAccountToKeyConnector":
|
||||||
if (typeof (msg.text) === 'string') {
|
this.keyConnectorService.setConvertAccountRequired(true);
|
||||||
message = msg.text;
|
this.router.navigate(["/remove-password"]);
|
||||||
} else if (msg.text.length === 1) {
|
break;
|
||||||
message = msg.text[0];
|
default:
|
||||||
} else {
|
break;
|
||||||
msg.text.forEach((t: string) =>
|
|
||||||
message += ('<p>' + this.sanitizer.sanitize(SecurityContext.HTML, t) + '</p>'));
|
|
||||||
options.enableHtml = true;
|
|
||||||
}
|
}
|
||||||
if (msg.options != null) {
|
});
|
||||||
if (msg.options.trustedHtml === true) {
|
});
|
||||||
options.enableHtml = true;
|
|
||||||
}
|
this.router.events.subscribe((event) => {
|
||||||
if (msg.options.timeout != null && msg.options.timeout > 0) {
|
if (event instanceof NavigationEnd) {
|
||||||
options.timeOut = msg.options.timeout;
|
const modals = Array.from(document.querySelectorAll(".modal"));
|
||||||
}
|
for (const modal of modals) {
|
||||||
|
(jq(modal) as any).modal("hide");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.toastrService.show(message, msg.title, options, 'toast-' + msg.type);
|
if (document.querySelector(".swal-modal") != null) {
|
||||||
|
Swal.close(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.policyListService.addPolicies([
|
||||||
|
new TwoFactorAuthenticationPolicy(),
|
||||||
|
new MasterPasswordPolicy(),
|
||||||
|
new PasswordGeneratorPolicy(),
|
||||||
|
new SingleOrgPolicy(),
|
||||||
|
new RequireSsoPolicy(),
|
||||||
|
new PersonalOwnershipPolicy(),
|
||||||
|
new DisableSendPolicy(),
|
||||||
|
new SendOptionsPolicy(),
|
||||||
|
new ResetPasswordPolicy(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.setFullWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async logOut(expired: boolean) {
|
||||||
|
await this.eventService.uploadEvents();
|
||||||
|
const userId = await this.stateService.getUserId();
|
||||||
|
await Promise.all([
|
||||||
|
this.eventService.clearEvents(),
|
||||||
|
this.syncService.setLastSync(new Date(0)),
|
||||||
|
this.tokenService.clearToken(),
|
||||||
|
this.cryptoService.clearKeys(),
|
||||||
|
this.settingsService.clear(userId),
|
||||||
|
this.cipherService.clear(userId),
|
||||||
|
this.folderService.clear(userId),
|
||||||
|
this.collectionService.clear(userId),
|
||||||
|
this.policyService.clear(userId),
|
||||||
|
this.passwordGenerationService.clear(),
|
||||||
|
this.keyConnectorService.clear(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.searchService.clearIndex();
|
||||||
|
this.authService.logOut(async () => {
|
||||||
|
if (expired) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"warning",
|
||||||
|
this.i18nService.t("loggedOut"),
|
||||||
|
this.i18nService.t("loginExpired")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.stateService.clean({ userId: userId });
|
||||||
|
Swal.close();
|
||||||
|
this.router.navigate(["/"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async recordActivity() {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
if (this.lastActivity != null && now - this.lastActivity < 250) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private idleStateChanged() {
|
this.lastActivity = now;
|
||||||
if (this.isIdle) {
|
this.stateService.setLastActive(now);
|
||||||
this.notificationsService.disconnectFromInactivity();
|
// Idle states
|
||||||
} else {
|
if (this.isIdle) {
|
||||||
this.notificationsService.reconnectFromActivity();
|
this.isIdle = false;
|
||||||
}
|
this.idleStateChanged();
|
||||||
|
}
|
||||||
|
if (this.idleTimer != null) {
|
||||||
|
window.clearTimeout(this.idleTimer);
|
||||||
|
this.idleTimer = null;
|
||||||
|
}
|
||||||
|
this.idleTimer = window.setTimeout(() => {
|
||||||
|
if (!this.isIdle) {
|
||||||
|
this.isIdle = true;
|
||||||
|
this.idleStateChanged();
|
||||||
|
}
|
||||||
|
}, IdleTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private showToast(msg: any) {
|
||||||
|
let message = "";
|
||||||
|
|
||||||
|
const options: Partial<IndividualConfig> = {};
|
||||||
|
|
||||||
|
if (typeof msg.text === "string") {
|
||||||
|
message = msg.text;
|
||||||
|
} else if (msg.text.length === 1) {
|
||||||
|
message = msg.text[0];
|
||||||
|
} else {
|
||||||
|
msg.text.forEach(
|
||||||
|
(t: string) =>
|
||||||
|
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>")
|
||||||
|
);
|
||||||
|
options.enableHtml = true;
|
||||||
|
}
|
||||||
|
if (msg.options != null) {
|
||||||
|
if (msg.options.trustedHtml === true) {
|
||||||
|
options.enableHtml = true;
|
||||||
|
}
|
||||||
|
if (msg.options.timeout != null && msg.options.timeout > 0) {
|
||||||
|
options.timeOut = msg.options.timeout;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setFullWidth() {
|
this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
|
||||||
const enableFullWidth = await this.stateService.getEnableFullWidth();
|
}
|
||||||
if (enableFullWidth) {
|
|
||||||
document.body.classList.add('full-width');
|
private idleStateChanged() {
|
||||||
} else {
|
if (this.isIdle) {
|
||||||
document.body.classList.remove('full-width');
|
this.notificationsService.disconnectFromInactivity();
|
||||||
}
|
} else {
|
||||||
|
this.notificationsService.reconnectFromActivity();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setFullWidth() {
|
||||||
|
const enableFullWidth = await this.stateService.getEnableFullWidth();
|
||||||
|
if (enableFullWidth) {
|
||||||
|
document.body.classList.add("full-width");
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove("full-width");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,35 @@
|
|||||||
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: [
|
||||||
OssModule,
|
OssModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ServicesModule,
|
ServicesModule,
|
||||||
BitwardenToastModule.forRoot({
|
BitwardenToastModule.forRoot({
|
||||||
maxOpened: 5,
|
maxOpened: 5,
|
||||||
autoDismiss: true,
|
autoDismiss: true,
|
||||||
closeButton: true,
|
closeButton: true,
|
||||||
}),
|
}),
|
||||||
InfiniteScrollModule,
|
InfiniteScrollModule,
|
||||||
DragDropModule,
|
DragDropModule,
|
||||||
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 {}
|
||||||
|
|||||||
@@ -1,76 +1,78 @@
|
|||||||
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 {
|
||||||
loading = true;
|
loading = true;
|
||||||
authed = false;
|
authed = false;
|
||||||
email: string;
|
email: string;
|
||||||
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();
|
||||||
|
|
||||||
if (this.authed) {
|
if (this.authed) {
|
||||||
try {
|
try {
|
||||||
await this.authedHandler(qParams);
|
await this.authedHandler(qParams);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = true;
|
error = true;
|
||||||
errorMessage = e.message;
|
errorMessage = e.message;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await this.stateService.setLoginRedirect({
|
await this.stateService.setLoginRedirect({
|
||||||
route: this.getRedirectRoute(),
|
route: this.getRedirectRoute(),
|
||||||
qParams: qParams,
|
qParams: qParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.email = qParams.email;
|
this.email = qParams.email;
|
||||||
await this.unauthedHandler(qParams);
|
await this.unauthedHandler(qParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.loading = false;
|
|
||||||
});
|
});
|
||||||
}
|
this.router.navigate(["/"]);
|
||||||
|
}
|
||||||
|
|
||||||
getRedirectRoute() {
|
this.loading = false;
|
||||||
const urlTree = this.router.parseUrl(this.router.url);
|
});
|
||||||
urlTree.queryParams = {};
|
}
|
||||||
return urlTree.toString();
|
|
||||||
}
|
getRedirectRoute() {
|
||||||
|
const urlTree = this.router.parseUrl(this.router.url);
|
||||||
|
urlTree.queryParams = {};
|
||||||
|
return urlTree.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,156 +1,193 @@
|
|||||||
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 {
|
||||||
loading = true;
|
loading = true;
|
||||||
loaded = false;
|
loaded = false;
|
||||||
events: EventView[];
|
events: EventView[];
|
||||||
start: string;
|
start: string;
|
||||||
end: string;
|
end: string;
|
||||||
dirtyDates: boolean = true;
|
dirtyDates: boolean = true;
|
||||||
continuationToken: string;
|
continuationToken: string;
|
||||||
refreshPromise: Promise<any>;
|
refreshPromise: Promise<any>;
|
||||||
exportPromise: Promise<any>;
|
exportPromise: Promise<any>;
|
||||||
morePromise: Promise<any>;
|
morePromise: Promise<any>;
|
||||||
|
|
||||||
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,
|
||||||
const defaultDates = this.eventService.getDefaultDateFilters();
|
protected exportService: ExportService,
|
||||||
this.start = defaultDates[0];
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
this.end = defaultDates[1];
|
protected logService: LogService
|
||||||
|
) {
|
||||||
|
const defaultDates = this.eventService.getDefaultDateFilters();
|
||||||
|
this.start = defaultDates[0];
|
||||||
|
this.end = defaultDates[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
async exportEvents() {
|
||||||
|
if (this.appApiPromiseUnfulfilled() || this.dirtyDates) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async exportEvents() {
|
this.loading = true;
|
||||||
if (this.appApiPromiseUnfulfilled() || this.dirtyDates) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loading = true;
|
const dates = this.parseDates();
|
||||||
|
if (dates == null) {
|
||||||
const dates = this.parseDates();
|
return;
|
||||||
if (dates == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.exportPromise = this.export(dates[0], dates[1]);
|
|
||||||
|
|
||||||
await this.exportPromise;
|
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(`Handled exception: ${e}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.exportPromise = null;
|
|
||||||
this.loading = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadEvents(clearExisting: boolean) {
|
try {
|
||||||
if (this.appApiPromiseUnfulfilled()) {
|
this.exportPromise = this.export(dates[0], dates[1]);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dates = this.parseDates();
|
await this.exportPromise;
|
||||||
if (dates == null) {
|
} catch (e) {
|
||||||
return;
|
this.logService.error(`Handled exception: ${e}`);
|
||||||
}
|
|
||||||
|
|
||||||
this.loading = true;
|
|
||||||
let events: EventView[] = [];
|
|
||||||
try {
|
|
||||||
const promise = this.loadAndParseEvents(dates[0], dates[1], clearExisting ? null : this.continuationToken);
|
|
||||||
if (clearExisting) {
|
|
||||||
this.refreshPromise = promise;
|
|
||||||
} else {
|
|
||||||
this.morePromise = promise;
|
|
||||||
}
|
|
||||||
const result = await promise;
|
|
||||||
this.continuationToken = result.continuationToken;
|
|
||||||
events = result.events;
|
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(`Handled exception: ${e}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!clearExisting && this.events != null && this.events.length > 0) {
|
|
||||||
this.events = this.events.concat(events);
|
|
||||||
} else {
|
|
||||||
this.events = events;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dirtyDates = false;
|
|
||||||
this.loading = false;
|
|
||||||
this.morePromise = null;
|
|
||||||
this.refreshPromise = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract requestEvents(startDate: string, endDate: string, continuationToken: string): Promise<ListResponse<EventResponse>>;
|
this.exportPromise = null;
|
||||||
protected abstract getUserName(r: EventResponse, userId: string): { name: string, email: string };
|
this.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
protected async loadAndParseEvents(startDate: string, endDate: string, continuationToken: string) {
|
async loadEvents(clearExisting: boolean) {
|
||||||
const response = await this.requestEvents(startDate, endDate, continuationToken);
|
if (this.appApiPromiseUnfulfilled()) {
|
||||||
|
return;
|
||||||
const events = await Promise.all(response.data.map(async r => {
|
|
||||||
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
|
|
||||||
const eventInfo = await this.eventService.getEventInfo(r);
|
|
||||||
const user = this.getUserName(r, userId);
|
|
||||||
return new EventView({
|
|
||||||
message: eventInfo.message,
|
|
||||||
humanReadableMessage: eventInfo.humanReadableMessage,
|
|
||||||
appIcon: eventInfo.appIcon,
|
|
||||||
appName: eventInfo.appName,
|
|
||||||
userId: userId,
|
|
||||||
userName: user != null ? user.name : this.i18nService.t('unknown'),
|
|
||||||
userEmail: user != null ? user.email : '',
|
|
||||||
date: r.date,
|
|
||||||
ip: r.ipAddress,
|
|
||||||
type: r.type,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
return { continuationToken: response.continuationToken, events: events };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected parseDates() {
|
const dates = this.parseDates();
|
||||||
let dates: string[] = null;
|
if (dates == null) {
|
||||||
try {
|
return;
|
||||||
dates = this.eventService.formatDateFilters(this.start, this.end);
|
|
||||||
} catch (e) {
|
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
|
||||||
this.i18nService.t('invalidDateRange'));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return dates;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected appApiPromiseUnfulfilled() {
|
this.loading = true;
|
||||||
return this.refreshPromise != null || this.morePromise != null || this.exportPromise != null;
|
let events: EventView[] = [];
|
||||||
|
try {
|
||||||
|
const promise = this.loadAndParseEvents(
|
||||||
|
dates[0],
|
||||||
|
dates[1],
|
||||||
|
clearExisting ? null : this.continuationToken
|
||||||
|
);
|
||||||
|
if (clearExisting) {
|
||||||
|
this.refreshPromise = promise;
|
||||||
|
} else {
|
||||||
|
this.morePromise = promise;
|
||||||
|
}
|
||||||
|
const result = await promise;
|
||||||
|
this.continuationToken = result.continuationToken;
|
||||||
|
events = result.events;
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(`Handled exception: ${e}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async export(start: string, end: string) {
|
if (!clearExisting && this.events != null && this.events.length > 0) {
|
||||||
let continuationToken = this.continuationToken;
|
this.events = this.events.concat(events);
|
||||||
let events = [].concat(this.events);
|
} else {
|
||||||
|
this.events = events;
|
||||||
while (continuationToken != null) {
|
|
||||||
const result = await this.loadAndParseEvents(start, end, continuationToken);
|
|
||||||
continuationToken = result.continuationToken;
|
|
||||||
events = events.concat(result.events);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await this.exportService.getEventExport(events);
|
|
||||||
const fileName = this.exportService.getFileName(this.exportFileName, 'csv');
|
|
||||||
this.platformUtilsService.saveFile(window, data, { type: 'text/plain' }, fileName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.dirtyDates = false;
|
||||||
|
this.loading = false;
|
||||||
|
this.morePromise = null;
|
||||||
|
this.refreshPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract requestEvents(
|
||||||
|
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
|
||||||
|
) {
|
||||||
|
const response = await this.requestEvents(startDate, endDate, continuationToken);
|
||||||
|
|
||||||
|
const events = await Promise.all(
|
||||||
|
response.data.map(async (r) => {
|
||||||
|
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
|
||||||
|
const eventInfo = await this.eventService.getEventInfo(r);
|
||||||
|
const user = this.getUserName(r, userId);
|
||||||
|
return new EventView({
|
||||||
|
message: eventInfo.message,
|
||||||
|
humanReadableMessage: eventInfo.humanReadableMessage,
|
||||||
|
appIcon: eventInfo.appIcon,
|
||||||
|
appName: eventInfo.appName,
|
||||||
|
userId: userId,
|
||||||
|
userName: user != null ? user.name : this.i18nService.t("unknown"),
|
||||||
|
userEmail: user != null ? user.email : "",
|
||||||
|
date: r.date,
|
||||||
|
ip: r.ipAddress,
|
||||||
|
type: r.type,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
continuationToken: response.continuationToken,
|
||||||
|
events: events,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected parseDates() {
|
||||||
|
let dates: string[] = null;
|
||||||
|
try {
|
||||||
|
dates = this.eventService.formatDateFilters(this.start, this.end);
|
||||||
|
} catch (e) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
|
this.i18nService.t("errorOccurred"),
|
||||||
|
this.i18nService.t("invalidDateRange")
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return dates;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected appApiPromiseUnfulfilled() {
|
||||||
|
return this.refreshPromise != null || this.morePromise != null || this.exportPromise != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async export(start: string, end: string) {
|
||||||
|
let continuationToken = this.continuationToken;
|
||||||
|
let events = [].concat(this.events);
|
||||||
|
|
||||||
|
while (continuationToken != null) {
|
||||||
|
const result = await this.loadAndParseEvents(start, end, continuationToken);
|
||||||
|
continuationToken = result.continuationToken;
|
||||||
|
events = events.concat(result.events);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await this.exportService.getEventExport(events);
|
||||||
|
const fileName = this.exportService.getFileName(this.exportFileName, "csv");
|
||||||
|
this.platformUtilsService.saveFile(
|
||||||
|
window,
|
||||||
|
data,
|
||||||
|
{
|
||||||
|
type: "text/plain",
|
||||||
|
},
|
||||||
|
fileName
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,320 +1,353 @@
|
|||||||
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() {
|
||||||
|
return this.allUsers != null ? this.allUsers.length : 0;
|
||||||
|
}
|
||||||
|
|
||||||
get allCount() {
|
get invitedCount() {
|
||||||
return this.allUsers != null ? this.allUsers.length : 0;
|
return this.statusMap.has(this.userStatusType.Invited)
|
||||||
|
? this.statusMap.get(this.userStatusType.Invited).length
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get acceptedCount() {
|
||||||
|
return this.statusMap.has(this.userStatusType.Accepted)
|
||||||
|
? this.statusMap.get(this.userStatusType.Accepted).length
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get confirmedCount() {
|
||||||
|
return this.statusMap.has(this.userStatusType.Confirmed)
|
||||||
|
? this.statusMap.get(this.userStatusType.Confirmed).length
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get showConfirmUsers(): boolean {
|
||||||
|
return (
|
||||||
|
this.allUsers != null &&
|
||||||
|
this.statusMap != null &&
|
||||||
|
this.allUsers.length > 1 &&
|
||||||
|
this.confirmedCount > 0 &&
|
||||||
|
this.confirmedCount < 3 &&
|
||||||
|
this.acceptedCount > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get showBulkConfirmUsers(): boolean {
|
||||||
|
return this.acceptedCount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract userType: typeof OrganizationUserType | typeof ProviderUserType;
|
||||||
|
abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType;
|
||||||
|
|
||||||
|
loading = true;
|
||||||
|
statusMap = new Map<StatusType, UserType[]>();
|
||||||
|
status: StatusType;
|
||||||
|
users: UserType[] = [];
|
||||||
|
pagedUsers: UserType[] = [];
|
||||||
|
searchText: string;
|
||||||
|
actionPromise: Promise<any>;
|
||||||
|
|
||||||
|
protected allUsers: UserType[] = [];
|
||||||
|
|
||||||
|
protected didScroll = false;
|
||||||
|
protected pageSize = 100;
|
||||||
|
|
||||||
|
private pagedUsersCount = 0;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected apiService: ApiService,
|
||||||
|
private searchService: SearchService,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
protected cryptoService: CryptoService,
|
||||||
|
protected validationService: ValidationService,
|
||||||
|
protected modalService: ModalService,
|
||||||
|
private logService: LogService,
|
||||||
|
private searchPipe: SearchPipe,
|
||||||
|
protected userNamePipe: UserNamePipe,
|
||||||
|
protected stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
abstract edit(user: UserType): void;
|
||||||
|
abstract getUsers(): Promise<ListResponse<UserType>>;
|
||||||
|
abstract deleteUser(id: string): Promise<any>;
|
||||||
|
abstract reinviteUser(id: string): Promise<any>;
|
||||||
|
abstract confirmUser(user: UserType, publicKey: Uint8Array): Promise<any>;
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
const response = await this.getUsers();
|
||||||
|
this.statusMap.clear();
|
||||||
|
for (const status of Utils.iterateEnum(this.userStatusType)) {
|
||||||
|
this.statusMap.set(status, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
get invitedCount() {
|
this.allUsers = response.data != null && response.data.length > 0 ? response.data : [];
|
||||||
return this.statusMap.has(this.userStatusType.Invited) ?
|
this.allUsers.sort(Utils.getSortFunction(this.i18nService, "email"));
|
||||||
this.statusMap.get(this.userStatusType.Invited).length : 0;
|
this.allUsers.forEach((u) => {
|
||||||
|
if (!this.statusMap.has(u.status)) {
|
||||||
|
this.statusMap.set(u.status, [u]);
|
||||||
|
} else {
|
||||||
|
this.statusMap.get(u.status).push(u);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.filter(this.status);
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
filter(status: StatusType) {
|
||||||
|
this.status = status;
|
||||||
|
if (this.status != null) {
|
||||||
|
this.users = this.statusMap.get(this.status);
|
||||||
|
} else {
|
||||||
|
this.users = this.allUsers;
|
||||||
|
}
|
||||||
|
// Reset checkbox selecton
|
||||||
|
this.selectAll(false);
|
||||||
|
this.resetPaging();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMore() {
|
||||||
|
if (!this.users || this.users.length <= this.pageSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pagedLength = this.pagedUsers.length;
|
||||||
|
let pagedSize = this.pageSize;
|
||||||
|
if (pagedLength === 0 && this.pagedUsersCount > this.pageSize) {
|
||||||
|
pagedSize = this.pagedUsersCount;
|
||||||
|
}
|
||||||
|
if (this.users.length > pagedLength) {
|
||||||
|
this.pagedUsers = this.pagedUsers.concat(
|
||||||
|
this.users.slice(pagedLength, pagedLength + pagedSize)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.pagedUsersCount = this.pagedUsers.length;
|
||||||
|
this.didScroll = this.pagedUsers.length > this.pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkUser(user: OrganizationUserUserDetailsResponse, select?: boolean) {
|
||||||
|
(user as any).checked = select == null ? !(user as any).checked : select;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAll(select: boolean) {
|
||||||
|
if (select) {
|
||||||
|
this.selectAll(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
get acceptedCount() {
|
const filteredUsers = this.searchPipe.transform(
|
||||||
return this.statusMap.has(this.userStatusType.Accepted) ?
|
this.users,
|
||||||
this.statusMap.get(this.userStatusType.Accepted).length : 0;
|
this.searchText,
|
||||||
|
"name",
|
||||||
|
"email",
|
||||||
|
"id"
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectCount =
|
||||||
|
select && filteredUsers.length > MaxCheckedCount ? MaxCheckedCount : filteredUsers.length;
|
||||||
|
for (let i = 0; i < selectCount; i++) {
|
||||||
|
this.checkUser(filteredUsers[i], select);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async resetPaging() {
|
||||||
|
this.pagedUsers = [];
|
||||||
|
this.loadMore();
|
||||||
|
}
|
||||||
|
|
||||||
|
invite() {
|
||||||
|
this.edit(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(user: UserType) {
|
||||||
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
|
this.deleteWarningMessage(user),
|
||||||
|
this.userNamePipe.transform(user),
|
||||||
|
this.i18nService.t("yes"),
|
||||||
|
this.i18nService.t("no"),
|
||||||
|
"warning"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get confirmedCount() {
|
this.actionPromise = this.deleteUser(user.id);
|
||||||
return this.statusMap.has(this.userStatusType.Confirmed) ?
|
try {
|
||||||
this.statusMap.get(this.userStatusType.Confirmed).length : 0;
|
await this.actionPromise;
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("removedUserId", this.userNamePipe.transform(user))
|
||||||
|
);
|
||||||
|
this.removeUser(user);
|
||||||
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
|
}
|
||||||
|
this.actionPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async reinvite(user: UserType) {
|
||||||
|
if (this.actionPromise != null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
get showConfirmUsers(): boolean {
|
this.actionPromise = this.reinviteUser(user.id);
|
||||||
return this.allUsers != null && this.statusMap != null && this.allUsers.length > 1 &&
|
try {
|
||||||
this.confirmedCount > 0 && this.confirmedCount < 3 && this.acceptedCount > 0;
|
await this.actionPromise;
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("hasBeenReinvited", this.userNamePipe.transform(user))
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
|
}
|
||||||
|
this.actionPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async confirm(user: UserType) {
|
||||||
|
function updateUser(self: BasePeopleComponent<UserType>) {
|
||||||
|
user.status = self.userStatusType.Confirmed;
|
||||||
|
const mapIndex = self.statusMap.get(self.userStatusType.Accepted).indexOf(user);
|
||||||
|
if (mapIndex > -1) {
|
||||||
|
self.statusMap.get(self.userStatusType.Accepted).splice(mapIndex, 1);
|
||||||
|
self.statusMap.get(self.userStatusType.Confirmed).push(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get showBulkConfirmUsers(): boolean {
|
const confirmUser = async (publicKey: Uint8Array) => {
|
||||||
return this.acceptedCount > 0;
|
try {
|
||||||
}
|
this.actionPromise = this.confirmUser(user, publicKey);
|
||||||
|
await this.actionPromise;
|
||||||
abstract userType: typeof OrganizationUserType | typeof ProviderUserType;
|
updateUser(this);
|
||||||
abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType;
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
loading = true;
|
null,
|
||||||
statusMap = new Map<StatusType, UserType[]>();
|
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(user))
|
||||||
status: StatusType;
|
);
|
||||||
users: UserType[] = [];
|
} catch (e) {
|
||||||
pagedUsers: UserType[] = [];
|
this.validationService.showError(e);
|
||||||
searchText: string;
|
throw e;
|
||||||
actionPromise: Promise<any>;
|
} finally {
|
||||||
|
|
||||||
protected allUsers: UserType[] = [];
|
|
||||||
|
|
||||||
protected didScroll = false;
|
|
||||||
protected pageSize = 100;
|
|
||||||
|
|
||||||
private pagedUsersCount = 0;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected apiService: ApiService,
|
|
||||||
private searchService: SearchService,
|
|
||||||
protected i18nService: I18nService,
|
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
|
||||||
protected cryptoService: CryptoService,
|
|
||||||
protected validationService: ValidationService,
|
|
||||||
protected modalService: ModalService,
|
|
||||||
private logService: LogService,
|
|
||||||
private searchPipe: SearchPipe,
|
|
||||||
protected userNamePipe: UserNamePipe,
|
|
||||||
protected stateService: StateService,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
abstract edit(user: UserType): void;
|
|
||||||
abstract getUsers(): Promise<ListResponse<UserType>>;
|
|
||||||
abstract deleteUser(id: string): Promise<any>;
|
|
||||||
abstract reinviteUser(id: string): Promise<any>;
|
|
||||||
abstract confirmUser(user: UserType, publicKey: Uint8Array): Promise<any>;
|
|
||||||
|
|
||||||
async load() {
|
|
||||||
const response = await this.getUsers();
|
|
||||||
this.statusMap.clear();
|
|
||||||
for (const status of Utils.iterateEnum(this.userStatusType)) {
|
|
||||||
this.statusMap.set(status, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.allUsers = response.data != null && response.data.length > 0 ? response.data : [];
|
|
||||||
this.allUsers.sort(Utils.getSortFunction(this.i18nService, 'email'));
|
|
||||||
this.allUsers.forEach(u => {
|
|
||||||
if (!this.statusMap.has(u.status)) {
|
|
||||||
this.statusMap.set(u.status, [u]);
|
|
||||||
} else {
|
|
||||||
this.statusMap.get(u.status).push(u);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.filter(this.status);
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
filter(status: StatusType) {
|
|
||||||
this.status = status;
|
|
||||||
if (this.status != null) {
|
|
||||||
this.users = this.statusMap.get(this.status);
|
|
||||||
} else {
|
|
||||||
this.users = this.allUsers;
|
|
||||||
}
|
|
||||||
// Reset checkbox selecton
|
|
||||||
this.selectAll(false);
|
|
||||||
this.resetPaging();
|
|
||||||
}
|
|
||||||
|
|
||||||
loadMore() {
|
|
||||||
if (!this.users || this.users.length <= this.pageSize) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pagedLength = this.pagedUsers.length;
|
|
||||||
let pagedSize = this.pageSize;
|
|
||||||
if (pagedLength === 0 && this.pagedUsersCount > this.pageSize) {
|
|
||||||
pagedSize = this.pagedUsersCount;
|
|
||||||
}
|
|
||||||
if (this.users.length > pagedLength) {
|
|
||||||
this.pagedUsers = this.pagedUsers.concat(this.users.slice(pagedLength, pagedLength + pagedSize));
|
|
||||||
}
|
|
||||||
this.pagedUsersCount = this.pagedUsers.length;
|
|
||||||
this.didScroll = this.pagedUsers.length > this.pageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkUser(user: OrganizationUserUserDetailsResponse, select?: boolean) {
|
|
||||||
(user as any).checked = select == null ? !(user as any).checked : select;
|
|
||||||
}
|
|
||||||
|
|
||||||
selectAll(select: boolean) {
|
|
||||||
if (select) {
|
|
||||||
this.selectAll(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredUsers = this.searchPipe.transform(this.users, this.searchText, 'name', 'email', 'id');
|
|
||||||
|
|
||||||
const selectCount = select && filteredUsers.length > MaxCheckedCount
|
|
||||||
? MaxCheckedCount
|
|
||||||
: filteredUsers.length;
|
|
||||||
for (let i = 0; i < selectCount; i++) {
|
|
||||||
this.checkUser(filteredUsers[i], select);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetPaging() {
|
|
||||||
this.pagedUsers = [];
|
|
||||||
this.loadMore();
|
|
||||||
}
|
|
||||||
|
|
||||||
invite() {
|
|
||||||
this.edit(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(user: UserType) {
|
|
||||||
const confirmed = await this.platformUtilsService.showDialog(
|
|
||||||
this.deleteWarningMessage(user), this.userNamePipe.transform(user),
|
|
||||||
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.actionPromise = this.deleteUser(user.id);
|
|
||||||
try {
|
|
||||||
await this.actionPromise;
|
|
||||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedUserId',
|
|
||||||
this.userNamePipe.transform(user)));
|
|
||||||
this.removeUser(user);
|
|
||||||
} catch (e) {
|
|
||||||
this.validationService.showError(e);
|
|
||||||
}
|
|
||||||
this.actionPromise = null;
|
this.actionPromise = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.actionPromise != null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async reinvite(user: UserType) {
|
try {
|
||||||
if (this.actionPromise != null) {
|
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
|
||||||
return;
|
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||||
}
|
|
||||||
|
|
||||||
this.actionPromise = this.reinviteUser(user.id);
|
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
|
||||||
try {
|
if (autoConfirm == null || !autoConfirm) {
|
||||||
await this.actionPromise;
|
const [modal] = await this.modalService.openViewRef(
|
||||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('hasBeenReinvited',
|
UserConfirmComponent,
|
||||||
this.userNamePipe.transform(user)));
|
this.confirmModalRef,
|
||||||
} catch (e) {
|
(comp) => {
|
||||||
this.validationService.showError(e);
|
comp.name = this.userNamePipe.transform(user);
|
||||||
}
|
comp.userId = user != null ? user.userId : null;
|
||||||
this.actionPromise = null;
|
comp.publicKey = publicKey;
|
||||||
}
|
comp.onConfirmedUser.subscribe(async () => {
|
||||||
|
try {
|
||||||
async confirm(user: UserType) {
|
comp.formPromise = confirmUser(publicKey);
|
||||||
function updateUser(self: BasePeopleComponent<UserType>) {
|
await comp.formPromise;
|
||||||
user.status = self.userStatusType.Confirmed;
|
modal.close();
|
||||||
const mapIndex = self.statusMap.get(self.userStatusType.Accepted).indexOf(user);
|
} catch (e) {
|
||||||
if (mapIndex > -1) {
|
|
||||||
self.statusMap.get(self.userStatusType.Accepted).splice(mapIndex, 1);
|
|
||||||
self.statusMap.get(self.userStatusType.Confirmed).push(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmUser = async (publicKey: Uint8Array) => {
|
|
||||||
try {
|
|
||||||
this.actionPromise = this.confirmUser(user, publicKey);
|
|
||||||
await this.actionPromise;
|
|
||||||
updateUser(this);
|
|
||||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('hasBeenConfirmed',
|
|
||||||
this.userNamePipe.transform(user)));
|
|
||||||
} catch (e) {
|
|
||||||
this.validationService.showError(e);
|
|
||||||
throw e;
|
|
||||||
} finally {
|
|
||||||
this.actionPromise = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.actionPromise != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
|
|
||||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
|
||||||
|
|
||||||
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
|
|
||||||
if (autoConfirm == null || !autoConfirm) {
|
|
||||||
const [modal] = await this.modalService.openViewRef(UserConfirmComponent, this.confirmModalRef, comp => {
|
|
||||||
comp.name = this.userNamePipe.transform(user);
|
|
||||||
comp.userId = user != null ? user.userId : null;
|
|
||||||
comp.publicKey = publicKey;
|
|
||||||
comp.onConfirmedUser.subscribe(async () => {
|
|
||||||
try {
|
|
||||||
comp.formPromise = confirmUser(publicKey);
|
|
||||||
await comp.formPromise;
|
|
||||||
modal.close();
|
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey.buffer);
|
|
||||||
this.logService.info(`User's fingerprint: ${fingerprint.join('-')}`);
|
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
await confirmUser(publicKey);
|
});
|
||||||
} catch (e) {
|
}
|
||||||
this.logService.error(`Handled exception: ${e}`);
|
);
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isSearching() {
|
try {
|
||||||
return this.searchService.isSearchable(this.searchText);
|
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey.buffer);
|
||||||
|
this.logService.info(`User's fingerprint: ${fingerprint.join("-")}`);
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
}
|
||||||
|
await confirmUser(publicKey);
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(`Handled exception: ${e}`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isPaging() {
|
isSearching() {
|
||||||
const searching = this.isSearching();
|
return this.searchService.isSearchable(this.searchText);
|
||||||
if (searching && this.didScroll) {
|
}
|
||||||
this.resetPaging();
|
|
||||||
}
|
isPaging() {
|
||||||
return !searching && this.users && this.users.length > this.pageSize;
|
const searching = this.isSearching();
|
||||||
|
if (searching && this.didScroll) {
|
||||||
|
this.resetPaging();
|
||||||
}
|
}
|
||||||
|
return !searching && this.users && this.users.length > this.pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
protected deleteWarningMessage(user: UserType): string {
|
protected deleteWarningMessage(user: UserType): string {
|
||||||
return this.i18nService.t('removeUserConfirmation');
|
return this.i18nService.t("removeUserConfirmation");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getCheckedUsers() {
|
||||||
|
return this.users.filter((u) => (u as any).checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected removeUser(user: UserType) {
|
||||||
|
let index = this.users.indexOf(user);
|
||||||
|
if (index > -1) {
|
||||||
|
this.users.splice(index, 1);
|
||||||
|
this.resetPaging();
|
||||||
}
|
}
|
||||||
|
if (this.statusMap.has(user.status)) {
|
||||||
protected getCheckedUsers() {
|
index = this.statusMap.get(user.status).indexOf(user);
|
||||||
return this.users.filter(u => (u as any).checked);
|
if (index > -1) {
|
||||||
|
this.statusMap.get(user.status).splice(index, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
protected removeUser(user: UserType) {
|
|
||||||
let index = this.users.indexOf(user);
|
|
||||||
if (index > -1) {
|
|
||||||
this.users.splice(index, 1);
|
|
||||||
this.resetPaging();
|
|
||||||
}
|
|
||||||
if (this.statusMap.has(user.status)) {
|
|
||||||
index = this.statusMap.get(user.status).indexOf(user);
|
|
||||||
if (index > -1) {
|
|
||||||
this.statusMap.get(user.status).splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,30 @@
|
|||||||
<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"
|
||||||
<label class="form-check-label font-weight-normal" [for]="parentId">
|
type="checkbox"
|
||||||
{{parentId | i18n}}
|
[name]="pascalize(parentId)"
|
||||||
</label>
|
[id]="parentId"
|
||||||
</div>
|
[(ngModel)]="parentChecked"
|
||||||
<div class="form-group form-group-child-check mb-0">
|
[indeterminate]="parentIndeterminate"
|
||||||
<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()"
|
<label class="form-check-label font-weight-normal" [for]="parentId">
|
||||||
(ngModelChange)="c.set($event)">
|
{{ parentId | i18n }}
|
||||||
<label class="form-check-label font-weight-normal" [for]="c.id">
|
</label>
|
||||||
{{c.id | i18n}}
|
</div>
|
||||||
</label>
|
<div class="form-group form-group-child-check mb-0">
|
||||||
</div>
|
<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()"
|
||||||
|
(ngModelChange)="c.set($event)"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label font-weight-normal" [for]="c.id">
|
||||||
|
{{ c.id | i18n }}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user