1
0
mirror of https://github.com/bitwarden/web synced 2025-12-11 13:53:17 +00:00

Compare commits

...

65 Commits

Author SHA1 Message Date
Hinton
ac8bd7292e Wrap connector calls in try to handle offline key connector 2022-01-11 20:23:45 +01:00
Hinton
2a4f6415d6 Changed methods to be protected 2022-01-11 20:16:06 +01:00
Hinton
91eb60ebef Fix linting 2022-01-11 20:14:37 +01:00
Hinton
29476f6744 Refactor 2022-01-11 20:05:10 +01:00
Hinton
ff7151fbbd Add support for setting keys 2022-01-11 19:45:34 +01:00
Hinton
4415720f3f Fix linting 2022-01-11 17:54:59 +01:00
Hinton
c49a9aa330 Only run it on cloud 2022-01-11 16:59:47 +01:00
Hinton
189b4437d4 Move logic to web, remove debug statements 2022-01-11 16:51:46 +01:00
Hinton
d22f17fc81 Update connector with changes from Kyle 2022-01-10 16:36:46 +01:00
Hinton
6efe992680 Undo some changes prettier made 2022-01-10 16:34:36 +01:00
Hinton
a1cde3c820 Merge branch 'master' of github.com:bitwarden/web into feature/cme-connector 2022-01-10 16:25:17 +01:00
Hinton
79b6f3595e Merge commit '56477eb39cfd8a73c9920577d24d75fed36e2cf5' into feature/cme-connector 2022-01-10 16:25:01 +01:00
Hinton
d0c0db70c5 Apply prettier 2022-01-10 16:24:38 +01:00
Hinton
f05b9439cc Merge commit '2b0a9d995e0147601ca8ae4778434a19354a60c2' into feature/cme-connector 2022-01-10 16:23:59 +01:00
Oscar Hinton
fd1d512a0f Run prettier on #1232 (#1383) 2022-01-10 14:50:54 +01:00
Daniel James Smith
14b8903d9a Fix items not opening when they had a password reprompt set (#1381) 2022-01-10 14:12:35 +01:00
Simon Legner
45284eefb3 Compress images u2fkey/yubikey using avif/webp (#1232)
Co-authored-by: Hinton <oscar@oscarhinton.com>
2022-01-10 12:37:21 +01:00
Daniel James Smith
49f6cfab7f Fixed linting issues (ran prettier) (#1379) 2022-01-07 14:28:25 +01:00
github-actions[bot]
2d271460e3 Autosync the updated translations (#1378)
Co-authored-by: github-actions <>
2022-01-07 13:51:08 +01:00
github-actions[bot]
241004f13b Bumped version to 2.25.1 (#1376)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-01-06 14:15:35 -08:00
Oscar Hinton
2f5d0201fe [BEEEP] Add script for optimizing images (#1374) 2022-01-06 21:20:35 +01:00
Daniel James Smith
7ffb5db310 Add --ignore-unknown to prettier on lint-staged (#1375) 2022-01-06 16:47:55 +01:00
Daniel James Smith
6603521d88 Add all filetypes to prettier and ignore via .prettierignore (#1373) 2022-01-06 15:13:29 +01:00
Addison Beck
d066e0586a [bug] Add defaults for vault timeout (#1365)
* [bug] Add state defaults for vault timeout

* [chore] Update jslib
2022-01-04 11:15:58 -05:00
Daniel James Smith
d0e661b84b Update year in copyright (#1370) 2022-01-03 17:14:50 +01:00
github-actions[bot]
6fa77cef88 Autosync the updated translations (#1368)
Co-authored-by: github-actions <>
2022-01-01 17:51:25 +01:00
Robyn MacCallum
6f408b871f Rename fb to formBuilder (#1369) 2021-12-31 10:06:07 -05:00
Addison Beck
8a9b992757 [bug(Account Switching)] Allow for never lock for dev setups (#1345)
* [bug(Account Switching)] Allow for never lock for dev setups

* [chore] Remove unecassary import

* [style] Ran prettier

* [chore] Update jslib
2021-12-28 10:47:41 -05:00
github-actions[bot]
55ecc4b804 Autosync the updated translations (#1359)
Co-authored-by: github-actions <>
2021-12-24 01:09:22 +01:00
Joseph Flinn
a71ce448f4 Change QA deploy SP & Re-enable feature branch deploy (#1358) 2021-12-23 09:14:10 -05:00
Jake Fink
bc82ae961e update jslib (#1356) 2021-12-21 13:09:09 -05:00
Linus Aarnio
ebcfdcd8a4 Add credit card logos to allow displaying icons based on brand (#1280)
Co-authored-by: Hinton <oscar@oscarhinton.com>
2021-12-21 12:08:08 +01:00
Vince Grassia
8991dcbf32 Set image tag to be 'dev' for now (#1353) 2021-12-20 13:16:53 -05:00
Micaiah Martin
cc9b9c91d7 Patch CI to allow for redeployments. Add Github actions to prettier .ignore as well. (#1352)
* Added inputs for reruns

* Added github workflows to .prettierignore

* Moved the Redeploy logic into the setup job
2021-12-20 10:15:43 -07:00
Daniel James Smith
3880d60101 Fix missing translation for unlinking SSO dialog (#1350) 2021-12-17 21:11:02 +01:00
Jake Fink
f5fdb34f7d Bug/sso properties globalstate (#1342)
* move sso properties in jslib to globalstate

* update jslib

* update jslib with prettier changes
2021-12-17 11:24:58 -05:00
Oscar Hinton
5b8f2034c3 Add .git-blame-ignore-revs (#1349) 2021-12-17 16:07:28 +01:00
Oscar Hinton
56477eb39c Apply Prettier (#1347) 2021-12-17 15:57:11 +01:00
Oscar Hinton
2b0a9d995e Add Prettier configuration (#1346) 2021-12-17 15:44:44 +01:00
github-actions[bot]
595722dfa1 Autosync the updated translations (#1348)
Co-authored-by: github-actions <>
2021-12-17 01:17:36 +01:00
Vince Grassia
6a1e683a93 Update workflows (#1344) 2021-12-16 11:46:26 -05:00
Addison Beck
97ca771a00 [bug] Correct bad BroadcasterService import (#1341)
* [bug] Correct bad BroadcasterService import

* [chore] update jslib
2021-12-14 22:07:56 -05:00
Jake Fink
214f82e142 send configType and redirectBehavior as int instead of string for request (#1339) 2021-12-14 13:25:48 -05:00
Addison Beck
17ae5ee57c [Account Switching] [Refactor] Implement new account centric services (#1220)
* [chore] updated services.module to use account services

* [refactor] sorted services provided by services.module

* [chore] removed references to deleted jslib services

* [chore] used activeAccount over storageService for account level storage items

* [chore] resolved linter warnings

* Refactor activeAccountService to stateService

* [bug] Remove uneeded calls to state service on logout

This was causing console erros on logout. Clearing of data is handled fully in dedicated services, clearing them in state afterwards is essentially a redundant call.

* [bug] Add back null locked callback to VaultTimeoutService

* Move call to get showUpdateKey

* [bug] Ensure HtmlStorageService does not override StateService options and locations

* [bug] Adjust theme logic to pull from the new storage locations

* [bug] Correct theme not sticking on refresh

* [bug] Add enableFullWidth to the account model

* [bug] fix theme option empty when light is selected

* [bug] init state on application start

* [bug] Reinit state when coming back from a lock

* [style] Fix lint complaints

* [bug] Clean state on logout

* [chore] Resolved merge issues

* [bug] Correct default for enableGravitars

* Bump angular to 12.

* Remove angular.json

* Bump rxjs

* Fix build errors, remove file-loader with asset/resource

* Use contenthash

* Bump jslib

* Bump ngx-toastr

* [chore] resolve issues from merge

* [chore] resolve issues from merge

* [bug] Add missing bracket

* Use newer import syntax

* [bug] Correct service orge

* [style] Fix lint complaints

* [chore] update jslib

* [review] Address code review

* [review] Address code review

* [review] Rename providerService to webProviderService

Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
Co-authored-by: Hinton <oscar@oscarhinton.com>
2021-12-14 11:10:26 -05:00
Daniel James Smith
71075cf878 Bump node to v16 (#1336)
* Pull in jslib

* Bump engines required to node 16 and npm 8

* Bump @types/node to 16

* Modify build.yml to build with node 16 and npm 8

* Update requirements in README.md

* Removed step to install npm8
npm8 is included in node v16

* Pull jslib
2021-12-13 17:16:57 +01:00
Thomas Rittson
56e2c86a7f Fix name for compatibility with config.js (#1334) 2021-12-10 18:10:20 +10:00
github-actions[bot]
8fba2a693e Autosync the updated translations (#1333)
Co-authored-by: github-actions <>
2021-12-10 01:18:21 +01:00
Oscar Hinton
f582d3e7a6 Bump angular to v12 (#1325) 2021-12-09 22:12:53 +01:00
Vince Grassia
75984a2e37 Remove old 'release' ref in workflow (#1328) 2021-12-07 22:49:27 -05:00
github-actions[bot]
1cba6dc3b9 Bumped version to 2.25.0 (#1327)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2021-12-07 19:46:24 -07:00
Oscar Hinton
a803d58c52 Fix components trying to use anglar bradcastservice vs interface (#1326) 2021-12-07 23:16:47 +01:00
Oscar Hinton
d5c0783619 Replace toaster library (#1322) 2021-12-07 20:41:45 +01:00
github-actions[bot]
35a7d6434a Autosync the updated translations (#1318)
Co-authored-by: github-actions <>
2021-12-07 17:06:08 +01:00
Oscar Hinton
78942cabf2 BEEEP: Refactor services DI (#1313) 2021-12-03 02:32:58 +00:00
Matt Gibson
d9231ae3f3 Fix families sponsorship redeem page (#1321)
* Display sponsorship warning when sponsoring an org

Move actions to drop down menu

Fix revoke cancel success popup

* Only show warning when sponsorship exists
2021-12-01 19:48:05 -06:00
Micaiah Martin
bca7c14319 Create workflow to automatically bump version (#1320) 2021-12-01 12:45:06 -07:00
Thomas Rittson
221931ecaa Update jslib (#1319)
* Update jslib

* Update constructors

* Update jslib
2021-11-29 10:14:49 +10:00
Hinton
371c21553c WIP 2021-11-26 23:13:08 +01:00
Justin Baur
4b856d9016 Fix basePrice to reflect the sponsorship (#1311)
* Fix basePrice to reflect the sponsorship

* Ran linter

* Add latest copy

* Remove unneeded if

* Fix times

* Stopped hardcoding basePrice

* Stopped hardcoding 40 in UI

* Switch to single small block

* Update jslib

* Revert "Update jslib"

This reverts commit 28534f2230.

* Revert "Remove unneeded if"

This reverts commit 5540b19998.

* Fix revert issue
2021-11-24 16:12:06 -05:00
Matt Gibson
4029554658 Fix formatting and title of sponsoring org drop down (#1317) 2021-11-24 14:41:57 -06:00
Matt Gibson
6ec22a9408 Add sponsorship pre validate to families redeem page (#1315)
* Add sponsorship pre validate to families redeem page

* Update messaging

* update jslib
2021-11-24 14:31:16 -06:00
Matt Gibson
9cc7dfb884 Force sponsorship friendly name to recipient address (#1316) 2021-11-24 13:38:38 -06:00
Oscar Hinton
dca12def8d Run npm lint in CI (#1314) 2021-11-24 19:23:31 +01:00
Arun Pattni
cbf65c5f42 Use 2fa.directory API v3 in inactive 2FA report (#1103)
* Use 2fa.directory API v3 in inactive 2FA report

* Fix issues

* Fix lint error

* Apply suggestions from code review

* Apply style suggestions

* Style fixes
2021-11-24 16:25:01 +01:00
Matt Gibson
f8c943c042 Display sponsored status for sponsored org subscription (#1312)
* Display sponsored status for sponsored org subscription

* Linter fixes
2021-11-24 08:33:34 -06:00
514 changed files with 45959 additions and 38077 deletions

View File

@@ -12,7 +12,7 @@ insert_final_newline = true
[*.{js,ts,scss,html}] [*.{js,ts,scss,html}]
charset = utf-8 charset = utf-8
indent_style = space indent_style = space
indent_size = 4 indent_size = 2
[*.{ts}] [*.{ts}]
quote_type = single quote_type = single

2
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1,2 @@
# Apply Prettier https://github.com/bitwarden/web/pull/1347
56477eb39cfd8a73c9920577d24d75fed36e2cf5

4
.gitattributes vendored
View File

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

View File

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

View File

@@ -9,8 +9,8 @@ on:
required: false required: false
push: push:
branches-ignore: branches-ignore:
- 'l10n_master' - "l10n_master"
- 'gh-pages' - "gh-pages"
jobs: jobs:
cloc: cloc:
@@ -28,7 +28,6 @@ jobs:
- name: Print lines of code - name: Print lines of code
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
setup: setup:
name: Setup name: Setup
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -40,9 +39,7 @@ jobs:
- name: Get GitHub sha as version - name: Get GitHub sha as version
id: version id: version
run: | run: echo "::set-output name=value::${GITHUB_SHA:0:7}"
echo "::set-output name=value::${GITHUB_SHA:0:7}"
build-oss-selfhost: build-oss-selfhost:
name: Build OSS zip name: Build OSS zip
@@ -54,17 +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: '14' node-version: "16"
- name: Update NPM
run: |
npm install -g npm@7
- 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
@@ -95,7 +88,6 @@ jobs:
path: ./web-${{ env._VERSION }}-selfhosted-open-source.zip path: ./web-${{ env._VERSION }}-selfhosted-open-source.zip
if-no-files-found: error if-no-files-found: error
build-cloud: build-cloud:
name: Build Cloud zip name: Build Cloud zip
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -106,17 +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: '14' node-version: "16"
- name: Update NPM
run: |
npm install -g npm@7
- 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
@@ -147,7 +135,6 @@ jobs:
path: ./web-${{ env._VERSION }}-cloud-COMMERCIAL.zip path: ./web-${{ env._VERSION }}-cloud-COMMERCIAL.zip
if-no-files-found: error if-no-files-found: error
build-commercial-selfhost: build-commercial-selfhost:
name: Build SelfHost Docker image name: Build SelfHost Docker image
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -158,17 +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: '14' node-version: "16"
- name: Update NPM
run: |
npm install -g npm@7
- 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
@@ -182,7 +165,7 @@ jobs:
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Setup DCT - name: Setup DCT
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/release' if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix'
id: setup-dct id: setup-dct
uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff
with: with:
@@ -228,12 +211,12 @@ jobs:
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
run: docker tag bitwarden/web bitwarden/web:dev run: docker tag bitwarden/web bitwarden/web:dev
- name: Tag release branch - name: Tag hotfix branch
if: github.ref == 'refs/heads/release' if: github.ref == 'refs/heads/hotfix'
run: docker tag bitwarden/web bitwarden/web:latest run: docker tag bitwarden/web bitwarden/web:hotfix
- name: List Docker images - name: List Docker images
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/release' if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix'
run: docker images run: docker images
- name: Push rc image - name: Push rc image
@@ -250,18 +233,17 @@ jobs:
DOCKER_CONTENT_TRUST: 1 DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }} DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
- name: Push latest image - name: Push hotfix image
if: github.ref == 'refs/heads/release' if: github.ref == 'refs/heads/hotfix'
run: docker push bitwarden/web:latest run: docker push bitwarden/web:hotfix
env: env:
DOCKER_CONTENT_TRUST: 1 DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }} DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
- name: Log out of Docker - name: Log out of Docker
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/release' 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
@@ -269,17 +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: '14' node-version: "16"
- name: Update NPM
run: |
npm install -g npm@7
- 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
@@ -329,7 +307,7 @@ jobs:
- name: Get image tag - name: Get image tag
id: image-tag id: image-tag
run: | run: |
IMAGE_TAG=$(echo "$GITHUB_REF" | awk '{split($0, a, "/"); print a[3];}') IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g")
TAG_EXTENSION=${{ github.event.inputs.custom_tag_extension }} TAG_EXTENSION=${{ github.event.inputs.custom_tag_extension }}
if [[ $TAG_EXTENSION ]]; then if [[ $TAG_EXTENSION ]]; then
@@ -361,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
@@ -369,7 +346,7 @@ jobs:
- name: Set up NuGet - name: Set up NuGet
uses: nuget/setup-nuget@04b0c2b8d1b97922f67eca497d7cf0bf17b8ffe1 uses: nuget/setup-nuget@04b0c2b8d1b97922f67eca497d7cf0bf17b8ffe1
with: with:
nuget-version: 'latest' nuget-version: "latest"
- name: Set up MSBuild - name: Set up MSBuild
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d
@@ -378,17 +355,13 @@ jobs:
id: npm-cache id: npm-cache
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6 uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
with: with:
path: '~/.npm' path: "~/.npm"
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- name: Set up Node - name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with: with:
node-version: '14' node-version: "16"
- name: Update NPM
run: |
npm install -g npm@7
- name: Print environment - name: Print environment
run: | run: |
@@ -409,13 +382,12 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: NPM install - name: Run linter
run: npm ci 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'
@@ -454,7 +426,6 @@ jobs:
upload_sources: true upload_sources: true
upload_translations: false upload_translations: false
check-failures: check-failures:
name: Check for failures name: Check for failures
if: always() if: always()

View File

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

View File

@@ -9,8 +9,8 @@ on:
required: false required: false
env: env:
_QA_CLUSTER_RESOURCE_GROUP: "bitwarden-devops" _QA_CLUSTER_RESOURCE_GROUP: "bw-env-qa"
_QA_CLUSTER_NAME: "dev-aks" _QA_CLUSTER_NAME: "bw-aks-qa"
_QA_K8S_NAMESPACE: "bw-qa" _QA_K8S_NAMESPACE: "bw-qa"
_QA_K8S_APP_NAME: "bw-web" _QA_K8S_APP_NAME: "bw-web"
@@ -23,8 +23,7 @@ jobs:
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Setup - name: Setup
run: run: export PATH=$PATH:~/work/web/web
export PATH=$PATH:~/work/web/web
- name: Login to Azure - name: Login to Azure
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
@@ -36,16 +35,16 @@ jobs:
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403 uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
with: with:
keyvault: "bitwarden-qa-kv" keyvault: "bitwarden-qa-kv"
secrets: "dev-aks-kubectl-credentials" secrets: "qa-aks-kubectl-credentials"
- name: Login to dev-aks-kubectl SP - name: Login with qa-aks-kubectl-credentials SP
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with: with:
creds: ${{ env.dev-aks-kubectl-credentials }} creds: ${{ env.qa-aks-kubectl-credentials }}
- name: Setup AKS access - name: Setup AKS access
env: #env:
USER_ID: ${{ env.qa-kubectl-managed-identity-clientId }} # USER_ID: ${{ env.qa-kubectl-managed-identity-clientId }}
run: | run: |
echo "---az install---" echo "---az install---"
az aks install-cli --install-location ./kubectl --kubelogin-install-location ./kubelogin az aks install-cli --install-location ./kubectl --kubelogin-install-location ./kubelogin
@@ -55,7 +54,7 @@ jobs:
- name: Get image tag - name: Get image tag
id: image_tag id: image_tag
run: | run: |
IMAGE_TAG=$(echo "$GITHUB_REF" | awk '{split($0, a, "/"); print a[3];}') IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g")
TAG_EXTENSION=${{ github.event.inputs.image_extension }} TAG_EXTENSION=${{ github.event.inputs.image_extension }}
if [[ $TAG_EXTENSION ]]; then if [[ $TAG_EXTENSION ]]; then

View File

@@ -3,7 +3,15 @@ name: Release
on: on:
workflow_dispatch: workflow_dispatch:
inputs: {} inputs:
release_type:
description: 'Release Options'
required: true
default: 'Initial Release'
type: choice
options:
- Initial Release
- Redeploy
jobs: jobs:
setup: setup:
@@ -34,7 +42,8 @@ jobs:
curl -sL https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest | jq -r ".tag_name" curl -sL https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest | jq -r ".tag_name"
) )
if [ "v$version" == "$previous_release_tag_version" ]; then if [ "v$version" == "$previous_release_tag_version" ] && \
[ "${{ github.event.inputs.release_type }}" == "Initial Release" ]; then
echo "[!] Already released v$version. Please bump version to continue" echo "[!] Already released v$version. Please bump version to continue"
exit 1 exit 1
fi fi
@@ -48,12 +57,12 @@ 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
needs: setup needs: setup
env: env:
_BRANCH_NAME: ${{ needs.setup.outputs.branch-name }}
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }} _RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
steps: steps:
- name: Print environment - name: Print environment
@@ -73,27 +82,28 @@ jobs:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Pull latest selfhost Release image - name: Pull latest selfhost image
run: docker pull bitwarden/web:latest run: docker pull bitwarden/web:$_BRANCH_NAME
- name: Tag version - name: Tag version and latest
run: | run: |
docker tag bitwarden/web:latest bitwarden/web:$_RELEASE_VERSION docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:$_RELEASE_VERSION
docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:latest
- name: List Docker images - name: List Docker images
run: docker images run: docker images
- name: Push images - name: Push version and latest image
run: |
docker push bitwarden/web:$_RELEASE_VERSION
env: env:
DOCKER_CONTENT_TRUST: 1 DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }} DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
run: |
docker push bitwarden/web:$_RELEASE_VERSION
docker push bitwarden/web:latest
- 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
@@ -116,8 +126,6 @@ jobs:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
ref: release
- name: Setup git config - name: Setup git config
run: | run: |
@@ -158,7 +166,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

71
.github/workflows/version-bump.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
---
name: Version Bump
on:
workflow_dispatch:
inputs:
version_number:
description: "New Version"
required: true
jobs:
bump_props_version:
name: "Create version_bump_${{ github.event.inputs.version_number }} branch"
runs-on: ubuntu-20.04
steps:
- name: Checkout Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Create Version Branch
run: |
git switch -c version_bump_${{ github.event.inputs.version_number }}
git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Checkout Version Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
with:
ref: version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - package.json
uses: bitwarden/gh-actions/version-bump@0c263b3963211ccaf5804313c3b3a0bcc52d4b19
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./package.json"
- name: Bump Version - package-lock.json
uses: bitwarden/gh-actions/version-bump@0c263b3963211ccaf5804313c3b3a0bcc52d4b19
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./package-lock.json"
- name: Commit files
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
- name: Push changes
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Create Version PR
env:
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
BASE_BRANCH: master
TITLE: "Bump version to ${{ github.event.inputs.version_number }}"
run: |
gh pr create --title "$TITLE" \
--base "$BASE" \
--head "$PR_BRANCH" \
--label "version update" \
--label "automated pr" \
--body "
## Type of change
- [ ] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [X] Other
## Objective
Automated version bump to ${{ github.event.inputs.version_number }}"

4
.husky/pre-commit Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

13
.prettierignore Normal file
View File

@@ -0,0 +1,13 @@
# Build directories
build
dist
jslib
# External libraries / auto synced locales
src/locales
src/404/*.min.css
src/scripts/u2f.js
# Github Workflows
.github/workflows

3
.prettierrc.json Normal file
View File

@@ -0,0 +1,3 @@
{
"printWidth": 100
}

View File

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

View File

@@ -23,8 +23,8 @@
### Requirements ### Requirements
- [Node.js](https://nodejs.org) v14.17 or greater - [Node.js](https://nodejs.org) v16.13.1 or greater
- NPM v7 - NPM v8
### Run the app ### Run the app
@@ -53,11 +53,9 @@ You can also manually adjusting your API endpoint settings by adding `config/loc
"proxyIdentity": "http://your-identity-url", "proxyIdentity": "http://your-identity-url",
"proxyEvents": "http://your-events-url", "proxyEvents": "http://your-events-url",
"proxyNotifications": "http://your-notifications-url", "proxyNotifications": "http://your-notifications-url",
"allowedHosts": ["hostnames-to-allow-in-webpack"], "allowedHosts": ["hostnames-to-allow-in-webpack"]
}, },
"urls": { "urls": {}
}
} }
``` ```
@@ -68,3 +66,23 @@ Where the `urls` object is defined by the [Urls type in jslib](https://github.co
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file. Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file. Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
## Prettier
We recently migrated to using Prettier as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps:
1. Check out your local Branch
2. Run `git merge 2b0a9d995e0147601ca8ae4778434a19354a60c2`
3. Resolve any merge conflicts, commit.
4. Run `npm run prettier`
5. Commit
6. Run `git merge -Xours 56477eb39cfd8a73c9920577d24d75fed36e2cf5`
7. Push
### Git blame
We also recommend that you configure git to ignore the prettier revision using:
```bash
git config blame.ignoreRevsFile .git-blame-ignore-revs
```

View File

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

View File

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

View File

@@ -1,22 +1,22 @@
import { ToasterModule } from 'angular2-toaster'; import { DragDropModule } from "@angular/cdk/drag-drop";
import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { RouterModule } from "@angular/router";
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { DragDropModule } from '@angular/cdk/drag-drop'; import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule } from '@angular/router';
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: [
@@ -25,7 +25,11 @@ import { WildcardRoutingModule } from 'src/app/wildcard-routing.module';
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
ServicesModule, ServicesModule,
ToasterModule.forRoot(), BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
InfiniteScrollModule, InfiniteScrollModule,
DragDropModule, DragDropModule,
AppRoutingModule, AppRoutingModule,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,33 +1,35 @@
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.formBuilder.group({
data = this.fb.group({
hours: [null], hours: [null],
minutes: [null], minutes: [null],
}); });
constructor(private fb: FormBuilder, private i18nService: I18nService) { constructor(private formBuilder: FormBuilder, private i18nService: I18nService) {
super(); super();
} }
@@ -57,12 +59,12 @@ export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent {
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> { buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false; const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false;
if (this.enabled.value && !singleOrgEnabled) { if (this.enabled.value && !singleOrgEnabled) {
throw new Error(this.i18nService.t('requireSsoPolicyReqError')); throw new Error(this.i18nService.t("requireSsoPolicyReqError"));
} }
const data = this.buildRequestData(); const data = this.buildRequestData();
if (data?.minutes == null || data?.minutes <= 0) { if (data?.minutes == null || data?.minutes <= 0) {
throw new Error(this.i18nService.t('invalidMaximumVaultTimeout')); throw new Error(this.i18nService.t("invalidMaximumVaultTimeout"));
} }
return super.buildRequest(policiesEnabledMap); return super.buildRequest(policiesEnabledMap);

View File

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

View File

@@ -1,32 +1,21 @@
import { import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
Component,
EventEmitter,
Input,
OnInit,
Output
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
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 { ProviderService } from "jslib-common/abstractions/provider.service";
import { UserService } from 'jslib-common/abstractions/user.service';
import { ValidationService } from 'jslib-angular/services/validation.service'; import { ValidationService } from "jslib-angular/services/validation.service";
import { ProviderService } from '../services/provider.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";
import { PlanType } from 'jslib-common/enums/planType';
@Component({ @Component({
selector: 'provider-add-organization', selector: "provider-add-organization",
templateUrl: 'add-organization.component.html', templateUrl: "add-organization.component.html",
}) })
export class AddOrganizationComponent implements OnInit { export class AddOrganizationComponent implements OnInit {
@Input() providerId: string; @Input() providerId: string;
@Input() organizations: Organization[]; @Input() organizations: Organization[];
@Output() onAddedOrganization = new EventEmitter(); @Output() onAddedOrganization = new EventEmitter();
@@ -35,10 +24,13 @@ export class AddOrganizationComponent implements OnInit {
formPromise: Promise<any>; formPromise: Promise<any>;
loading = true; loading = true;
constructor(private userService: UserService, private providerService: ProviderService, constructor(
private toasterService: ToasterService, private i18nService: I18nService, private providerService: ProviderService,
private platformUtilsService: PlatformUtilsService, private validationService: ValidationService, private webProviderService: WebProviderService,
private apiService: ApiService) { } private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private validationService: ValidationService
) {}
async ngOnInit() { async ngOnInit() {
await this.load(); await this.load();
@@ -49,7 +41,7 @@ export class AddOrganizationComponent implements OnInit {
return; return;
} }
this.provider = await this.userService.getProvider(this.providerId); this.provider = await this.providerService.get(this.providerId);
this.loading = false; this.loading = false;
} }
@@ -60,15 +52,22 @@ export class AddOrganizationComponent implements OnInit {
} }
const confirmed = await this.platformUtilsService.showDialog( const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('addOrganizationConfirmation', organization.name, this.provider.name), organization.name, this.i18nService.t("addOrganizationConfirmation", organization.name, this.provider.name),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); organization.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) { if (!confirmed) {
return false; return false;
} }
try { try {
this.formPromise = this.providerService.addOrganizationToProvider(this.providerId, organization.id); this.formPromise = this.webProviderService.addOrganizationToProvider(
this.providerId,
organization.id
);
await this.formPromise; await this.formPromise;
} catch (e) { } catch (e) {
this.validationService.showError(e); this.validationService.showError(e);
@@ -77,7 +76,11 @@ export class AddOrganizationComponent implements OnInit {
this.formPromise = null; this.formPromise = null;
} }
this.toasterService.popAsync('success', null, this.i18nService.t('organizationJoinedProvider')); this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("organizationJoinedProvider")
);
this.onAddedOrganization.emit(); this.onAddedOrganization.emit();
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,45 +1,53 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from "@angular/router";
import { Toast, ToasterService } from 'angular2-toaster';
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 { StateService } from 'jslib-common/abstractions/state.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { UserService } from 'jslib-common/abstractions/user.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(router: Router, toasterService: ToasterService, i18nService: I18nService, route: ActivatedRoute, constructor(
userService: UserService, stateService: StateService, private apiService: ApiService) { router: Router,
super(router, toasterService, i18nService, route, userService, stateService); i18nService: I18nService,
route: ActivatedRoute,
stateService: StateService,
private apiService: ApiService,
platformUtilService: PlatformUtilsService
) {
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(
const toast: Toast = { qParams.providerId,
type: 'success', qParams.providerUserId,
title: this.i18nService.t('inviteAccepted'), request
body: this.i18nService.t('providerInviteAcceptedDesc'), );
timeout: 10000, this.platformUtilService.showToast(
}; "success",
this.toasterService.popAsync(toast); this.i18nService.t("inviteAccepted"),
this.router.navigate(['/vault']); this.i18nService.t("providerInviteAcceptedDesc"),
{ timeout: 10000 }
);
this.router.navigate(["/vault"]);
} }
async unauthedHandler(qParams: any) { async unauthedHandler(qParams: any) {

View File

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

View File

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

View File

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

View File

@@ -1,49 +1,53 @@
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 { ToasterService } from 'angular2-toaster';
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 { UserService } from 'jslib-common/abstractions/user.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(private apiService: ApiService, private route: ActivatedRoute, eventService: EventService, constructor(
i18nService: I18nService, toasterService: ToasterService, private userService: UserService, private apiService: ApiService,
exportService: ExportService, platformUtilsService: PlatformUtilsService, private router: Router, private route: ActivatedRoute,
logService: LogService, private userNamePipe: UserNamePipe) { eventService: EventService,
super(eventService, i18nService, toasterService, exportService, platformUtilsService, logService); i18nService: I18nService,
private providerService: ProviderService,
exportService: ExportService,
platformUtilsService: PlatformUtilsService,
private router: Router,
logService: LogService,
private userNamePipe: UserNamePipe
) {
super(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.userService.getProvider(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();
@@ -52,7 +56,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit {
async load() { async load() {
const response = await this.apiService.getProviderUsers(this.providerId); const response = await this.apiService.getProviderUsers(this.providerId);
response.data.forEach(u => { response.data.forEach((u) => {
const name = this.userNamePipe.transform(u); const name = this.userNamePipe.transform(u);
this.providerUsersIdMap.set(u.id, { name: name, email: u.email }); this.providerUsersIdMap.set(u.id, { name: name, email: u.email });
this.providerUsersUserIdMap.set(u.userId, { name: name, email: u.email }); this.providerUsersUserIdMap.set(u.userId, { name: name, email: u.email });
@@ -62,10 +66,17 @@ export class EventsComponent extends BaseEventsComponent implements OnInit {
} }
protected requestEvents(startDate: string, endDate: string, continuationToken: string) { protected requestEvents(startDate: string, endDate: string, continuationToken: string) {
return this.apiService.getEventsProvider(this.providerId, startDate, endDate, continuationToken); return this.apiService.getEventsProvider(
this.providerId,
startDate,
endDate,
continuationToken
);
} }
protected getUserName(r: EventResponse, userId: string) { protected getUserName(r: EventResponse, userId: string) {
return userId != null && this.providerUsersUserIdMap.has(userId) ? this.providerUsersUserIdMap.get(userId) : null; return userId != null && this.providerUsersUserIdMap.has(userId)
? this.providerUsersUserIdMap.get(userId)
: null;
} }
} }

View File

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

View File

@@ -1,26 +1,23 @@
import { import { Component, OnInit } from "@angular/core";
Component, import { ActivatedRoute } from "@angular/router";
OnInit,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { UserService } from 'jslib-common/abstractions/user.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 userService: UserService) { } 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.userService.getProvider(params.providerId); this.provider = await this.providerService.get(params.providerId);
this.accessEvents = this.provider.useEvents; this.accessEvents = this.provider.useEvents;
}); });
} }

View File

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

View File

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

View File

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

View File

@@ -1,28 +1,20 @@
import { import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { ToasterService } from 'angular2-toaster'; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ProviderUserInviteRequest } from "jslib-common/models/request/provider/providerUserInviteRequest";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { 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 { ProviderUserUpdateRequest } from "jslib-common/models/request/provider/providerUserUpdateRequest";
import { ProviderUserType } from 'jslib-common/enums/providerUserType';
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;
@@ -38,21 +30,24 @@ export class UserAddEditComponent implements OnInit {
type: ProviderUserType = ProviderUserType.ServiceUser; type: ProviderUserType = ProviderUserType.ServiceUser;
permissions = new PermissionsApi(); permissions = new PermissionsApi();
showCustom = false; showCustom = false;
access: 'all' | 'selected' = 'selected'; access: "all" | "selected" = "selected";
formPromise: Promise<any>; formPromise: Promise<any>;
deletePromise: Promise<any>; deletePromise: Promise<any>;
userType = ProviderUserType; userType = ProviderUserType;
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(
private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService, private apiService: ApiService,
private logService: LogService) { } 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;
@@ -60,7 +55,7 @@ export class UserAddEditComponent implements OnInit {
this.logService.error(e); this.logService.error(e);
} }
} else { } else {
this.title = this.i18nService.t('inviteUser'); this.title = this.i18nService.t("inviteUser");
} }
this.loading = false; this.loading = false;
@@ -71,7 +66,11 @@ export class UserAddEditComponent implements OnInit {
if (this.editMode) { if (this.editMode) {
const request = new ProviderUserUpdateRequest(); const request = new ProviderUserUpdateRequest();
request.type = this.type; request.type = this.type;
this.formPromise = this.apiService.putProviderUser(this.providerId, this.providerUserId, request); this.formPromise = this.apiService.putProviderUser(
this.providerId,
this.providerUserId,
request
);
} else { } else {
const request = new ProviderUserInviteRequest(); const request = new ProviderUserInviteRequest();
request.emails = this.emails.trim().split(/\s*,\s*/); request.emails = this.emails.trim().split(/\s*,\s*/);
@@ -79,8 +78,11 @@ export class UserAddEditComponent implements OnInit {
this.formPromise = this.apiService.postProviderUserInvite(this.providerId, request); this.formPromise = this.apiService.postProviderUserInvite(this.providerId, request);
} }
await this.formPromise; await this.formPromise;
this.toasterService.popAsync('success', null, this.platformUtilsService.showToast(
this.i18nService.t(this.editMode ? 'editedUserId' : 'invitedUsers', this.name)); "success",
null,
this.i18nService.t(this.editMode ? "editedUserId" : "invitedUsers", this.name)
);
this.onSavedUser.emit(); this.onSavedUser.emit();
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
@@ -93,8 +95,12 @@ export class UserAddEditComponent implements OnInit {
} }
const confirmed = await this.platformUtilsService.showDialog( const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('removeUserConfirmation'), this.name, this.i18nService.t("removeUserConfirmation"),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); this.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) { if (!confirmed) {
return false; return false;
} }
@@ -102,11 +108,14 @@ export class UserAddEditComponent implements OnInit {
try { try {
this.deletePromise = this.apiService.deleteProviderUser(this.providerId, this.providerUserId); this.deletePromise = this.apiService.deleteProviderUser(this.providerId, this.providerUserId);
await this.deletePromise; await this.deletePromise;
this.toasterService.popAsync('success', null, this.i18nService.t('removedUserId', this.name)); this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("removedUserId", this.name)
);
this.onDeletedUser.emit(); this.onDeletedUser.emit();
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
} }
} }

View File

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

View File

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

View File

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

View File

@@ -1,44 +1,39 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from "@angular/common";
import { ComponentFactoryResolver } from '@angular/core'; import { ComponentFactoryResolver } from "@angular/core";
import { NgModule } from '@angular/core'; import { NgModule } from "@angular/core";
import { FormsModule } from '@angular/forms'; import { FormsModule } from "@angular/forms";
import { ModalService } from 'jslib-angular/services/modal.service'; import { ModalService } from "jslib-angular/services/modal.service";
import { ProviderGuardService } from './services/provider-guard.service'; import { ProviderGuardService } from "./services/provider-guard.service";
import { ProviderTypeGuardService } from './services/provider-type-guard.service'; import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
import { ProviderService } from './services/provider.service'; import { WebProviderService } from "./services/webProvider.service";
import { ProvidersLayoutComponent } from './providers-layout.component'; import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProvidersRoutingModule } from './providers-routing.module'; import { ProvidersRoutingModule } from "./providers-routing.module";
import { AddOrganizationComponent } from './clients/add-organization.component'; import { AddOrganizationComponent } from "./clients/add-organization.component";
import { ClientsComponent } from './clients/clients.component'; import { ClientsComponent } from "./clients/clients.component";
import { CreateOrganizationComponent } from './clients/create-organization.component'; import { CreateOrganizationComponent } from "./clients/create-organization.component";
import { AcceptProviderComponent } from './manage/accept-provider.component'; import { AcceptProviderComponent } from "./manage/accept-provider.component";
import { BulkConfirmComponent } from './manage/bulk/bulk-confirm.component'; import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component";
import { BulkRemoveComponent } from './manage/bulk/bulk-remove.component'; import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component";
import { EventsComponent } from './manage/events.component'; import { EventsComponent } from "./manage/events.component";
import { ManageComponent } from './manage/manage.component'; import { ManageComponent } from "./manage/manage.component";
import { PeopleComponent } from './manage/people.component'; import { PeopleComponent } from "./manage/people.component";
import { UserAddEditComponent } from './manage/user-add-edit.component'; import { UserAddEditComponent } from "./manage/user-add-edit.component";
import { AccountComponent } from './settings/account.component'; import { AccountComponent } from "./settings/account.component";
import { SettingsComponent } from './settings/settings.component'; import { SettingsComponent } from "./settings/settings.component";
import { SetupProviderComponent } from './setup/setup-provider.component'; import { SetupProviderComponent } from "./setup/setup-provider.component";
import { SetupComponent } from './setup/setup.component'; import { SetupComponent } from "./setup/setup.component";
import { OssModule } from 'src/app/oss.module'; import { OssModule } from "src/app/oss.module";
@NgModule({ @NgModule({
imports: [ imports: [CommonModule, FormsModule, OssModule, ProvidersRoutingModule],
CommonModule,
FormsModule,
OssModule,
ProvidersRoutingModule,
],
declarations: [ declarations: [
AcceptProviderComponent, AcceptProviderComponent,
AccountComponent, AccountComponent,
@@ -56,14 +51,13 @@ import { OssModule } from 'src/app/oss.module';
SetupProviderComponent, SetupProviderComponent,
UserAddEditComponent, UserAddEditComponent,
], ],
providers: [ providers: [WebProviderService, ProviderGuardService, ProviderTypeGuardService],
ProviderService,
ProviderGuardService,
ProviderTypeGuardService,
],
}) })
export class ProvidersModule { export class ProvidersModule {
constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) { constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) {
modalService.registerComponentFactoryResolver(AddOrganizationComponent, componentFactoryResolver); modalService.registerComponentFactoryResolver(
AddOrganizationComponent,
componentFactoryResolver
);
} }
} }

View File

@@ -1,29 +1,28 @@
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 { ToasterService } from 'angular2-toaster'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { ProviderService } from "jslib-common/abstractions/provider.service";
import { UserService } from 'jslib-common/abstractions/user.service';
@Injectable() @Injectable()
export class ProviderGuardService implements CanActivate { export class ProviderGuardService implements CanActivate {
constructor(private userService: UserService, private router: Router, constructor(
private toasterService: ToasterService, private i18nService: I18nService) { } private router: Router,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private providerService: ProviderService
) {}
async canActivate(route: ActivatedRouteSnapshot) { async canActivate(route: ActivatedRouteSnapshot) {
const provider = await this.userService.getProvider(route.params.providerId); const provider = await this.providerService.get(route.params.providerId);
if (provider == null) { if (provider == null) {
this.router.navigate(['/']); this.router.navigate(["/"]);
return false; return false;
} }
if (!provider.isProviderAdmin && !provider.enabled) { if (!provider.isProviderAdmin && !provider.enabled) {
this.toasterService.popAsync('error', null, this.i18nService.t('providerIsDisabled')); this.platformUtilsService.showToast("error", null, this.i18nService.t("providerIsDisabled"));
this.router.navigate(['/']); this.router.navigate(["/"]);
return false; return false;
} }

View File

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

View File

@@ -1,32 +0,0 @@
import { Injectable } from '@angular/core';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ProviderAddOrganizationRequest } from 'jslib-common/models/request/provider/providerAddOrganizationRequest';
@Injectable()
export class ProviderService {
constructor(private cryptoService: CryptoService, private syncService: SyncService, private apiService: ApiService) {}
async addOrganizationToProvider(providerId: string, organizationId: string) {
const orgKey = await this.cryptoService.getOrgKey(organizationId);
const providerKey = await this.cryptoService.getProviderKey(providerId);
const encryptedOrgKey = await this.cryptoService.encrypt(orgKey.key, providerKey);
const request = new ProviderAddOrganizationRequest();
request.organizationId = organizationId;
request.key = encryptedOrgKey.encryptedString;
const response = await this.apiService.postProviderAddOrganization(providerId, request);
await this.syncService.fullSync(true);
return response;
}
async detachOrganizastion(providerId: string, organizationId: string): Promise<any> {
await this.apiService.deleteProviderOrganization(providerId, organizationId);
await this.syncService.fullSync(true);
}
}

View File

@@ -0,0 +1,36 @@
import { Injectable } from "@angular/core";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { ProviderAddOrganizationRequest } from "jslib-common/models/request/provider/providerAddOrganizationRequest";
@Injectable()
export class WebProviderService {
constructor(
private cryptoService: CryptoService,
private syncService: SyncService,
private apiService: ApiService
) {}
async addOrganizationToProvider(providerId: string, organizationId: string) {
const orgKey = await this.cryptoService.getOrgKey(organizationId);
const providerKey = await this.cryptoService.getProviderKey(providerId);
const encryptedOrgKey = await this.cryptoService.encrypt(orgKey.key, providerKey);
const request = new ProviderAddOrganizationRequest();
request.organizationId = organizationId;
request.key = encryptedOrgKey.encryptedString;
const response = await this.apiService.postProviderAddOrganization(providerId, request);
await this.syncService.fullSync(true);
return response;
}
async detachOrganizastion(providerId: string, organizationId: string): Promise<any> {
await this.apiService.deleteProviderOrganization(providerId, organizationId);
await this.syncService.fullSync(true);
}
}

View File

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

View File

@@ -1,20 +1,19 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from "@angular/router";
import { ToasterService } from 'angular2-toaster';
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;
@@ -25,14 +24,18 @@ export class AccountComponent {
private providerId: string; private providerId: string;
constructor(private apiService: ApiService, private i18nService: I18nService, constructor(
private toasterService: ToasterService, private route: ActivatedRoute, private apiService: ApiService,
private syncService: SyncService, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private logService: LogService) { } 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);
@@ -54,7 +57,7 @@ export class AccountComponent {
return this.syncService.fullSync(true); return this.syncService.fullSync(true);
}); });
await this.formPromise; await this.formPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('providerUpdated')); this.platformUtilsService.showToast("success", null, this.i18nService.t("providerUpdated"));
} catch (e) { } catch (e) {
this.logService.error(`Handled exception: ${e}`); this.logService.error(`Handled exception: ${e}`);
} }

View File

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

View File

@@ -1,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 { UserService } from 'jslib-common/abstractions/user.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 userService: UserService, 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.userService.getProvider(params.providerId); const provider = await this.providerService.get(params.providerId);
}); });
} }
} }

View File

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

View File

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

View File

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

View File

@@ -1,29 +1,20 @@
import { import { Component, OnInit } from "@angular/core";
Component, import { ActivatedRoute, Router } from "@angular/router";
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import {
Toast,
ToasterService,
} from 'angular2-toaster';
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 { SyncService } from 'jslib-common/abstractions/sync.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderSetupRequest } from 'jslib-common/models/request/provider/providerSetupRequest'; import { SyncService } from "jslib-common/abstractions/sync.service";
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;
@@ -36,25 +27,32 @@ export class SetupComponent implements OnInit {
name: string; name: string;
billingEmail: string; billingEmail: string;
constructor(private router: Router, private toasterService: ToasterService, 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) {
const toast: Toast = { this.platformUtilsService.showToast(
type: 'error', "error",
title: null, null,
body: this.i18nService.t('emergencyInviteAcceptFailed'), this.i18nService.t("emergencyInviteAcceptFailed"),
{
timeout: 10000, timeout: 10000,
}; }
this.toasterService.popAsync(toast); );
this.router.navigate(['/']); this.router.navigate(["/"]);
return; return;
} }
@@ -65,11 +63,11 @@ export class SetupComponent implements OnInit {
try { try {
const provider = await this.apiService.getProvider(this.providerId); const provider = await this.apiService.getProvider(this.providerId);
if (provider.name != null) { if (provider.name != null) {
this.router.navigate(['/providers', provider.id], { replaceUrl: true }); this.router.navigate(["/providers", provider.id], { replaceUrl: true });
} }
} catch (e) { } catch (e) {
this.validationService.showError(e); this.validationService.showError(e);
this.router.navigate(['/']); this.router.navigate(["/"]);
} }
}); });
} }
@@ -92,10 +90,10 @@ export class SetupComponent implements OnInit {
request.key = key; request.key = key;
const provider = await this.apiService.postProviderSetup(this.providerId, request); const provider = await this.apiService.postProviderSetup(this.providerId, request);
this.toasterService.popAsync('success', null, this.i18nService.t('providerSetup')); this.platformUtilsService.showToast("success", null, this.i18nService.t("providerSetup"));
await this.syncService.fullSync(true); await this.syncService.fullSync(true);
this.router.navigate(['/providers', provider.id]); this.router.navigate(["/providers", provider.id]);
} catch (e) { } catch (e) {
this.validationService.showError(e); this.validationService.showError(e);
} }

View File

@@ -1,11 +1,11 @@
const AngularCompilerPlugin = require('@ngtools/webpack').AngularCompilerPlugin; 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 AngularCompilerPlugin({ webpackConfig.plugins[webpackConfig.plugins.length - 1] = new AngularWebpackPlugin({
tsConfigPath: 'tsconfig.json', tsConfigPath: "tsconfig.json",
entryModule: 'bitwarden_license/src/app/app.module#AppModule', entryModule: "bitwarden_license/src/app/app.module#AppModule",
sourceMap: true, sourceMap: true,
}); });

View File

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

View File

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

2
jslib

Submodule jslib updated: b4f475251a...462a4d7c56

13698
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "bitwarden-web", "name": "bitwarden-web",
"version": "2.24.4", "version": "2.25.1",
"license": "GPL-3.0", "license": "GPL-3.0",
"repository": "https://github.com/bitwarden/web", "repository": "https://github.com/bitwarden/web",
"scripts": { "scripts": {
@@ -29,44 +29,57 @@
"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' || true", "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 .",
"prepare": "husky install"
}, },
"devDependencies": { "devDependencies": {
"@angular/compiler-cli": "^11.2.11", "@angular/compiler-cli": "^12.2.13",
"@ngtools/webpack": "^11.2.10", "@ngtools/webpack": "^12.2.13",
"@types/jquery": "^3.5.5", "@types/jquery": "^3.5.5",
"@types/node": "^14.17.2", "@types/node": "^16.11.12",
"@types/webcrypto": "^0.0.28", "@types/webcrypto": "^0.0.28",
"@types/webpack": "^4.4.27", "@types/webpack": "^5.28.0",
"clean-webpack-plugin": "^3.0.0", "buffer": "^6.0.3",
"copy-webpack-plugin": "^6.4.0", "clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.0.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^5.2.3", "css-loader": "^6.5.1",
"del": "^6.0.0",
"file-loader": "^6.2.0",
"gh-pages": "^3.1.0", "gh-pages": "^3.1.0",
"html-loader": "^1.3.2", "html-loader": "^3.0.1",
"html-webpack-injector": "1.1.4", "html-webpack-injector": "1.1.4",
"html-webpack-plugin": "^4.5.1", "html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^1.5.0", "husky": "^7.0.4",
"lint-staged": "^12.1.2",
"mini-css-extract-plugin": "^2.4.5",
"prettier": "2.5.1",
"process": "^0.11.10",
"sass": "^1.32.10", "sass": "^1.32.10",
"sass-loader": "^10.1.1", "sass-loader": "^12.4.0",
"style-loader": "^2.0.0", "style-loader": "^3.3.1",
"tapable": "^1.1.3", "terser-webpack-plugin": "^5.2.5",
"terser-webpack-plugin": "^4.2.3", "ts-loader": "^9.2.5",
"ts-loader": "^8.1.0",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"tslint-loader": "^3.5.4", "tslint-loader": "^3.5.4",
"typescript": "4.1.5", "typescript": "4.3.5",
"webpack": "^4.46.0", "util": "^0.12.4",
"webpack-cli": "^4.6.0", "webpack": "^5.64.4",
"webpack-dev-server": "^3.11.2" "webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.6.0"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "^12.2.13",
"@angular/cdk": "^12.2.13",
"@angular/common": "^12.2.13",
"@angular/compiler": "^12.2.13",
"@angular/core": "^12.2.13",
"@angular/forms": "^12.2.13",
"@angular/platform-browser": "^12.2.13",
"@angular/platform-browser-dynamic": "^12.2.13",
"@angular/router": "^12.2.13",
"@bitwarden/jslib-angular": "file:jslib/angular", "@bitwarden/jslib-angular": "file:jslib/angular",
"@bitwarden/jslib-common": "file:jslib/common", "@bitwarden/jslib-common": "file:jslib/common",
"angular2-toaster": "11.0.1",
"bootstrap": "4.6.0", "bootstrap": "4.6.0",
"braintree-web-drop-in": "1.30.1", "braintree-web-drop-in": "1.30.1",
"browser-hrtime": "^1.1.8", "browser-hrtime": "^1.1.8",
@@ -75,14 +88,20 @@
"font-awesome": "4.7.0", "font-awesome": "4.7.0",
"jquery": "3.6.0", "jquery": "3.6.0",
"ngx-infinite-scroll": "^10.0.1", "ngx-infinite-scroll": "^10.0.1",
"ngx-toastr": "14.1.4",
"popper.js": "1.16.1", "popper.js": "1.16.1",
"qrious": "4.0.2", "qrious": "4.0.2",
"rxjs": "^7.4.0",
"sweetalert2": "^10.16.6", "sweetalert2": "^10.16.6",
"webcrypto-shim": "0.1.7", "webcrypto-shim": "0.1.7",
"whatwg-fetch": "3.6.2" "whatwg-fetch": "3.6.2"
}, },
"engines": { "engines": {
"node": "~14", "node": "~16",
"npm": "~7" "npm": "~8"
},
"lint-staged": {
"*": "prettier --ignore-unknown --write",
"*.png": "node scripts/optimize.js"
} }
} }

21
scripts/optimize.js Normal file
View File

@@ -0,0 +1,21 @@
const child_process = require("child_process");
const path = require("path");
const images = process.argv.slice(2);
images.forEach((img) => {
switch (img.split(".").pop()) {
case "png":
child_process.execSync(
`npx @squoosh/cli --oxipng {} --output-dir "${path.dirname(img)}" "${img}"`
);
break;
case "jpg":
child_process.execSync(
`npx @squoosh/cli --mozjpeg {"quality":85,"baseline":false,"arithmetic":false,"progressive":true,"optimize_coding":true,"smoothing":0,"color_space":3,"quant_table":3,"trellis_multipass":false,"trellis_opt_zero":false,"trellis_opt_table":false,"trellis_loops":1,"auto_subsample":true,"chroma_subsample":2,"separate_chroma_quality":false,"chroma_quality":75} --output-dir "${path.dirname(
img
)}" "${img}"`
);
break;
}
});

View File

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

View File

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

View File

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

View File

@@ -1,38 +1,33 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { import { ActivatedRoute, Router } from "@angular/router";
ActivatedRoute,
Router,
} from '@angular/router';
import { import { ApiService } from "jslib-common/abstractions/api.service";
Toast, import { I18nService } from "jslib-common/abstractions/i18n.service";
ToasterService, import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
} from 'angular2-toaster'; import { StateService } from "jslib-common/abstractions/state.service";
import { EmergencyAccessAcceptRequest } from "jslib-common/models/request/emergencyAccessAcceptRequest";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { BaseAcceptComponent } from "../common/base.accept.component";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { EmergencyAccessAcceptRequest } from 'jslib-common/models/request/emergencyAccessAcceptRequest';
import { BaseAcceptComponent } from '../common/base.accept.component';
@Component({ @Component({
selector: 'app-accept-emergency', selector: "app-accept-emergency",
templateUrl: 'accept-emergency.component.html', templateUrl: "accept-emergency.component.html",
}) })
export class AcceptEmergencyComponent extends BaseAcceptComponent { export class AcceptEmergencyComponent extends BaseAcceptComponent {
name: string; name: string;
protected requiredParameters: string[] = ['id', 'name', 'email', 'token']; protected requiredParameters: string[] = ["id", "name", "email", "token"];
protected failedShortMessage = 'emergencyInviteAcceptFailedShort'; protected failedShortMessage = "emergencyInviteAcceptFailedShort";
protected failedMessage = 'emergencyInviteAcceptFailed'; protected failedMessage = "emergencyInviteAcceptFailed";
constructor(router: Router, toasterService: ToasterService, constructor(
i18nService: I18nService, route: ActivatedRoute, router: Router,
private apiService: ApiService, userService: UserService, platformUtilsService: PlatformUtilsService,
stateService: StateService) { i18nService: I18nService,
super(router, toasterService, i18nService, route, userService, stateService); route: ActivatedRoute,
private apiService: ApiService,
stateService: StateService
) {
super(router, platformUtilsService, i18nService, route, stateService);
} }
async authedHandler(qParams: any): Promise<void> { async authedHandler(qParams: any): Promise<void> {
@@ -40,21 +35,20 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent {
request.token = qParams.token; request.token = qParams.token;
this.actionPromise = this.apiService.postEmergencyAccessAccept(qParams.id, request); this.actionPromise = this.apiService.postEmergencyAccessAccept(qParams.id, request);
await this.actionPromise; await this.actionPromise;
const toast: Toast = { this.platformUtilService.showToast(
type: 'success', "success",
title: this.i18nService.t('inviteAccepted'), this.i18nService.t("inviteAccepted"),
body: this.i18nService.t('emergencyInviteAcceptedDesc'), this.i18nService.t("emergencyInviteAcceptedDesc"),
timeout: 10000, { timeout: 10000 }
}; );
this.toasterService.popAsync(toast); this.router.navigate(["/vault"]);
this.router.navigate(['/vault']);
} }
async unauthedHandler(qParams: any): Promise<void> { async unauthedHandler(qParams: any): Promise<void> {
this.name = qParams.name; this.name = qParams.name;
if (this.name != null) { if (this.name != null) {
// Fix URL encoding of space issue with Angular // Fix URL encoding of space issue with Angular
this.name = this.name.replace(/\+/g, ' '); this.name = this.name.replace(/\+/g, " ");
} }
} }
} }

View File

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

View File

@@ -1,57 +1,57 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { import { ActivatedRoute, Router } from "@angular/router";
ActivatedRoute,
Router,
} from '@angular/router';
import { import { ApiService } from "jslib-common/abstractions/api.service";
Toast, import { CryptoService } from "jslib-common/abstractions/crypto.service";
ToasterService, import { I18nService } from "jslib-common/abstractions/i18n.service";
} from 'angular2-toaster'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { OrganizationUserAcceptRequest } from "jslib-common/models/request/organizationUserAcceptRequest";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { OrganizationUserAcceptRequest } from 'jslib-common/models/request/organizationUserAcceptRequest'; import { Utils } from "jslib-common/misc/utils";
import { OrganizationUserResetPasswordEnrollmentRequest } from 'jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest'; import { Policy } from "jslib-common/models/domain/policy";
import { BaseAcceptComponent } from "../common/base.accept.component";
import { Utils } from 'jslib-common/misc/utils';
import { Policy } from 'jslib-common/models/domain/policy';
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(router: Router, toasterService: ToasterService, constructor(
i18nService: I18nService, route: ActivatedRoute, router: Router,
private apiService: ApiService, userService: UserService, platformUtilsService: PlatformUtilsService,
stateService: StateService, private cryptoService: CryptoService, i18nService: I18nService,
private policyService: PolicyService, private logService: LogService) { route: ActivatedRoute,
super(router, toasterService, i18nService, route, userService, stateService); private apiService: ApiService,
stateService: StateService,
private cryptoService: CryptoService,
private policyService: PolicyService,
private logService: LogService
) {
super(router, platformUtilsService, i18nService, route, stateService);
} }
async authedHandler(qParams: any): Promise<void> { async authedHandler(qParams: any): Promise<void> {
const request = new OrganizationUserAcceptRequest(); const request = new OrganizationUserAcceptRequest();
request.token = qParams.token; request.token = qParams.token;
if (await this.performResetPasswordAutoEnroll(qParams)) { if (await this.performResetPasswordAutoEnroll(qParams)) {
this.actionPromise = this.apiService.postOrganizationUserAccept(qParams.organizationId, this.actionPromise = this.apiService
qParams.organizationUserId, request).then(() => { .postOrganizationUserAccept(qParams.organizationId, qParams.organizationUserId, request)
.then(() => {
// Retrieve Public Key // Retrieve Public Key
return this.apiService.getOrganizationKeys(qParams.organizationId); return this.apiService.getOrganizationKeys(qParams.organizationId);
}).then(async response => { })
.then(async (response) => {
if (response == null) { if (response == null) {
throw new Error(this.i18nService.t('resetPasswordOrgKeysError')); throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
} }
const publicKey = Utils.fromB64ToArray(response.publicKey); const publicKey = Utils.fromB64ToArray(response.publicKey);
@@ -64,50 +64,60 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(); const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.resetPasswordKey = encryptedKey.encryptedString; resetRequest.resetPasswordKey = encryptedKey.encryptedString;
// Get User Id return this.apiService.putOrganizationUserResetPasswordEnrollment(
const userId = await this.userService.getUserId(); qParams.organizationId,
await this.stateService.getUserId(),
return this.apiService.putOrganizationUserResetPasswordEnrollment(qParams.organizationId, userId, resetRequest); resetRequest
);
}); });
} else { } else {
this.actionPromise = this.apiService.postOrganizationUserAccept(qParams.organizationId, this.actionPromise = this.apiService.postOrganizationUserAccept(
qParams.organizationUserId, request); qParams.organizationId,
qParams.organizationUserId,
request
);
} }
await this.actionPromise; await this.actionPromise;
const toast: Toast = { this.platformUtilService.showToast(
type: 'success', "success",
title: this.i18nService.t('inviteAccepted'), this.i18nService.t("inviteAccepted"),
body: this.i18nService.t('inviteAcceptedDesc'), this.i18nService.t("inviteAcceptedDesc"),
timeout: 10000, { timeout: 10000 }
}; );
this.toasterService.popAsync(toast);
await this.stateService.remove('orgInvitation'); await this.stateService.setOrganizationInvitation(null);
this.router.navigate(['/vault']); this.router.navigate(["/vault"]);
} }
async unauthedHandler(qParams: any): Promise<void> { async unauthedHandler(qParams: any): Promise<void> {
this.orgName = qParams.organizationName; this.orgName = qParams.organizationName;
if (this.orgName != null) { if (this.orgName != null) {
// Fix URL encoding of space issue with Angular // Fix URL encoding of space issue with Angular
this.orgName = this.orgName.replace(/\+/g, ' '); this.orgName = this.orgName.replace(/\+/g, " ");
} }
await this.stateService.save('orgInvitation', qParams); await this.stateService.setOrganizationInvitation(qParams);
} }
private async performResetPasswordAutoEnroll(qParams: any): Promise<boolean> { private async performResetPasswordAutoEnroll(qParams: any): Promise<boolean> {
let policyList: Policy[] = null; let policyList: Policy[] = null;
try { try {
const policies = await this.apiService.getPoliciesByToken(qParams.organizationId, qParams.token, const policies = await this.apiService.getPoliciesByToken(
qParams.email, qParams.organizationUserId); qParams.organizationId,
qParams.token,
qParams.email,
qParams.organizationUserId
);
policyList = this.policyService.mapPoliciesFromToken(policies); policyList = this.policyService.mapPoliciesFromToken(policies);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
if (policyList != null) { if (policyList != null) {
const result = this.policyService.getResetPasswordPolicyOptions(policyList, qParams.organizationId); const result = this.policyService.getResetPasswordPolicyOptions(
policyList,
qParams.organizationId
);
// Return true if policy enabled and auto-enroll enabled // Return true if policy enabled and auto-enroll enabled
return result[1] && result[0].autoEnrollEnabled; return result[1] && result[0].autoEnrollEnabled;
} }

View File

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

View File

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

View File

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

View File

@@ -1,45 +1,62 @@
import { Component } from '@angular/core'; import { Component, NgZone } 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 { 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 { StorageService } from 'jslib-common/abstractions/storage.service'; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { UserService } from 'jslib-common/abstractions/user.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,
userService: UserService, cryptoService: CryptoService, i18nService: I18nService,
storageService: StorageService, vaultTimeoutService: VaultTimeoutService, platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService, private routerService: RouterService, messagingService: MessagingService,
stateService: StateService, apiService: ApiService, logService: LogService, cryptoService: CryptoService,
keyConnectorService: KeyConnectorService) { vaultTimeoutService: VaultTimeoutService,
super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService, environmentService: EnvironmentService,
storageService, vaultTimeoutService, environmentService, stateService, apiService, logService, private routerService: RouterService,
keyConnectorService); 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 = () => { this.onSuccessfulSubmit = async () => {
const previousUrl = this.routerService.getPreviousUrl(); const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl !== '/' && previousUrl.indexOf('lock') === -1) { if (previousUrl !== "/" && previousUrl.indexOf("lock") === -1) {
this.successRoute = previousUrl; this.successRoute = previousUrl;
} }
this.router.navigate([this.successRoute]); this.router.navigate([this.successRoute]);

View File

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

View File

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

View File

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

View File

@@ -1,26 +1,28 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { Router } from '@angular/router'; import { Router } from "@angular/router";
import { ToasterService } from 'angular2-toaster'; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { DeleteRecoverRequest } from "jslib-common/models/request/deleteRecoverRequest";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
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 toasterService: ToasterService, 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 {
@@ -28,8 +30,12 @@ export class RecoverDeleteComponent {
request.email = this.email.trim().toLowerCase(); request.email = this.email.trim().toLowerCase();
this.formPromise = this.apiService.postAccountRecoverDelete(request); this.formPromise = this.apiService.postAccountRecoverDelete(request);
await this.formPromise; await this.formPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('deleteRecoverEmailSent')); this.platformUtilsService.showToast(
this.router.navigate(['/']); "success",
null,
this.i18nService.t("deleteRecoverEmailSent")
);
this.router.navigate(["/"]);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }

View File

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

View File

@@ -1,19 +1,18 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { Router } from '@angular/router'; import { Router } from "@angular/router";
import { ToasterService } from 'angular2-toaster'; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { TwoFactorRecoveryRequest } from "jslib-common/models/request/twoFactorRecoveryRequest";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { 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;
@@ -21,22 +20,31 @@ export class RecoverTwoFactorComponent {
recoveryCode: string; recoveryCode: string;
formPromise: Promise<any>; formPromise: Promise<any>;
constructor(private router: Router, private apiService: ApiService, constructor(
private toasterService: ToasterService, 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.toasterService.popAsync('success', null, this.i18nService.t('twoStepRecoverDisabled')); this.platformUtilsService.showToast(
this.router.navigate(['/']); "success",
null,
this.i18nService.t("twoStepRecoverDisabled")
);
this.router.navigate(["/"]);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,34 +1,48 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { import { ActivatedRoute, Router } from "@angular/router";
ActivatedRoute,
Router,
} from '@angular/router';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { UserService } from 'jslib-common/abstractions/user.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,
userService: UserService, passwordGenerationService: PasswordGenerationService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, policyService: PolicyService, router: Router, cryptoService: CryptoService,
syncService: SyncService, route: ActivatedRoute) { messagingService: MessagingService,
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService, passwordGenerationService: PasswordGenerationService,
platformUtilsService, policyService, router, apiService, syncService, route); platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
router: Router,
syncService: SyncService,
route: ActivatedRoute,
stateService: StateService
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyService,
router,
apiService,
syncService,
route,
stateService
);
} }
} }

View File

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

View File

@@ -1,50 +1,62 @@
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 { StorageService } from 'jslib-common/abstractions/storage.service';
import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.component'; import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
const IdentifierStorageKey = 'ssoOrgIdentifier';
@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,
storageService: StorageService, stateService: StateService, router: Router,
platformUtilsService: PlatformUtilsService, apiService: ApiService, i18nService: I18nService,
cryptoFunctionService: CryptoFunctionService, environmentService: EnvironmentService, route: ActivatedRoute,
passwordGenerationService: PasswordGenerationService, logService: LogService) { stateService: StateService,
super(authService, router, i18nService, route, storageService, 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.storageService.get<string>(IdentifierStorageKey); const storedIdentifier = await this.stateService.getSsoOrgIdentifier();
if (storedIdentifier != null) { if (storedIdentifier != null) {
this.identifier = storedIdentifier; this.identifier = storedIdentifier;
} }
@@ -53,9 +65,9 @@ export class SsoComponent extends BaseSsoComponent {
} }
async submit() { async submit() {
await this.storageService.save(IdentifierStorageKey, this.identifier); await this.stateService.setSsoOrganizationIdentifier(this.identifier);
if (this.clientId === 'browser') { if (this.clientId === "browser") {
document.cookie = `ssoHandOffMessage=${this.i18nService.t('ssoHandOff')};SameSite=strict`; document.cookie = `ssoHandOffMessage=${this.i18nService.t("ssoHandOff")};SameSite=strict`;
} }
super.submit(); super.submit();
} }

View File

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

View File

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

View File

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

View File

@@ -1,50 +1,63 @@
import { import { Component, ViewChild, ViewContainerRef } from "@angular/core";
Component,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { import { ActivatedRoute, Router } from "@angular/router";
ActivatedRoute,
Router,
} from '@angular/router';
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StorageService } from 'jslib-common/abstractions/storage.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 { LogService } from 'jslib-common/abstractions/log.service'; 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,
storageService: StorageService, route: ActivatedRoute, logService: LogService) { apiService: ApiService,
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService, platformUtilsService: PlatformUtilsService,
stateService, storageService, route, logService); stateService: StateService,
environmentService: EnvironmentService,
private modalService: ModalService,
route: ActivatedRoute,
logService: LogService
) {
super(
authService,
router,
i18nService,
apiService,
platformUtilsService,
window,
environmentService,
stateService,
route,
logService
);
this.onSuccessfulLoginNavigate = this.goAfterLogIn; this.onSuccessfulLoginNavigate = this.goAfterLogIn;
} }
async anotherMethod() { async anotherMethod() {
const [modal] = await this.modalService.openViewRef(TwoFactorOptionsComponent, this.twoFactorOptionsModal, comp => { const [modal] = await this.modalService.openViewRef(
TwoFactorOptionsComponent,
this.twoFactorOptionsModal,
(comp) => {
comp.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => { comp.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close(); modal.close();
this.selectedProviderType = provider; this.selectedProviderType = provider;
@@ -53,14 +66,15 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
comp.onRecoverSelected.subscribe(() => { comp.onRecoverSelected.subscribe(() => {
modal.close(); modal.close();
}); });
}); }
);
} }
async goAfterLogIn() { async goAfterLogIn() {
const loginRedirect = await this.stateService.get<any>('loginRedirect'); 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.remove('loginRedirect'); await this.stateService.setLoginRedirect(null);
} else { } else {
this.router.navigate([this.successRoute], { this.router.navigate([this.successRoute], {
queryParams: { queryParams: {

View File

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

View File

@@ -1,30 +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 { UserService } from 'jslib-common/abstractions/user.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";
@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, userService: UserService, platformUtilsService: PlatformUtilsService,
messagingService: MessagingService, apiService: ApiService, passwordGenerationService: PasswordGenerationService,
syncService: SyncService, logService: LogService) { policyService: PolicyService,
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService, cryptoService: CryptoService,
userService, messagingService, apiService, syncService, logService); messagingService: MessagingService,
apiService: ApiService,
logService: LogService,
stateService: StateService,
syncService: SyncService
) {
super(
i18nService,
platformUtilsService,
passwordGenerationService,
policyService,
cryptoService,
messagingService,
apiService,
stateService,
syncService,
logService
);
} }
} }

View File

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

View File

@@ -1,52 +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 { ToasterService } from 'angular2-toaster'; import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { VerifyEmailRequest } from "jslib-common/models/request/verifyEmailRequest";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { UserService } from 'jslib-common/abstractions/user.service';
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(private router: Router, private toasterService: ToasterService, constructor(
private i18nService: I18nService, private route: ActivatedRoute, private router: Router,
private apiService: ApiService, private userService: UserService, private platformUtilsService: PlatformUtilsService,
private logService: LogService) { } private i18nService: I18nService,
private route: ActivatedRoute,
private apiService: ApiService,
private logService: LogService,
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)
const authed = await this.userService.isAuthenticated(); );
if (authed) { if (await this.stateService.getIsAuthenticated()) {
await this.apiService.refreshIdentityToken(); await this.apiService.refreshIdentityToken();
} }
this.toasterService.popAsync('success', null, this.i18nService.t('emailVerified')); this.platformUtilsService.showToast("success", null, this.i18nService.t("emailVerified"));
this.router.navigate(['/']); this.router.navigate(["/"]);
return; return;
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
} }
this.toasterService.popAsync('error', null, this.i18nService.t('emailVerifiedFailed')); this.platformUtilsService.showToast("error", null, this.i18nService.t("emailVerifiedFailed"));
this.router.navigate(['/']); this.router.navigate(["/"]);
}); });
} }
} }

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