1
0
mirror of https://github.com/bitwarden/web synced 2025-12-06 00:03:28 +00:00

Compare commits

..

83 Commits

Author SHA1 Message Date
Kyle Spearrin
62cd45030a New Crowdin updates (#1073)
* New translations messages.json (Romanian)

* New translations messages.json (Turkish)

* New translations messages.json (Ukrainian)

* New translations messages.json (Chinese Simplified)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Estonian)

* New translations messages.json (Serbian (Cyrillic))

* New translations messages.json (Latvian)

* New translations messages.json (Azerbaijani)

* New translations messages.json (English, United Kingdom)

* New translations messages.json (Kannada)

* New translations messages.json (Serbian (Latin))

* New translations messages.json (Swedish)

* New translations messages.json (French)

* New translations messages.json (Bulgarian)

* New translations messages.json (Catalan)

* New translations messages.json (Czech)

* New translations messages.json (Danish)

* New translations messages.json (German)

* New translations messages.json (Finnish)

* New translations messages.json (Slovak)

* New translations messages.json (Hungarian)

* New translations messages.json (Italian)

* New translations messages.json (Japanese)

* New translations messages.json (Korean)

* New translations messages.json (Dutch)

* New translations messages.json (Polish)

* New translations messages.json (Russian)
2021-07-07 17:44:59 -04:00
Thomas Rittson
76c5c2b8c4 Fix fingerprint phrases (#1071)
* Fix fingerprint phrases in the bulk confirm modal

* Update jslib
2021-07-07 21:46:12 +10:00
Thomas Rittson
2420b22485 Update jslib (#1070) 2021-07-07 16:50:46 +10:00
Oscar Hinton
d64e6179a3 Bump braintree to 1.30.1 (#1066)
(cherry picked from commit 1320a7c8cb)
2021-07-05 13:30:16 +02:00
Vincent Salucci
f3c1c68d0e [Reset Password] BUG Add permission gate to key backfill (#1061) 2021-07-03 07:42:56 -05:00
Chad Scharf
ec9a0976d8 Version bump 2.21.1 (#1060) 2021-07-02 12:45:10 -04:00
Vincent Salucci
29d06439ec [Reset Password] BUG Org Keys backfill force sync (#1055)
Merging on Vince's behalf
2021-07-01 10:11:59 -04:00
Joseph Flinn
007f953377 fixing the prod deploy workflow error with npm ci (#1054) 2021-06-29 20:52:12 -07:00
Joseph Flinn
c852d536ea manually resolving the duo_security package with https instead of ssh (#1053) 2021-06-29 20:24:14 -07:00
Kyle Spearrin
998ef86527 New Crowdin updates (#1052)
* New translations messages.json (Romanian)

* New translations messages.json (Croatian)

* New translations messages.json (Turkish)

* New translations messages.json (Ukrainian)

* New translations messages.json (Chinese Simplified)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (Vietnamese)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Indonesian)

* New translations messages.json (Bengali)

* New translations messages.json (Estonian)

* New translations messages.json (Serbian (Cyrillic))

* New translations messages.json (Latvian)

* New translations messages.json (English, United Kingdom)

* New translations messages.json (Esperanto)

* New translations messages.json (Malayalam)

* New translations messages.json (Sinhala)

* New translations messages.json (Kannada)

* New translations messages.json (Norwegian Bokmal)

* New translations messages.json (Serbian (Latin))

* New translations messages.json (Swedish)

* New translations messages.json (Slovenian)

* New translations messages.json (French)

* New translations messages.json (Greek)

* New translations messages.json (Spanish)

* New translations messages.json (Afrikaans)

* New translations messages.json (Belarusian)

* New translations messages.json (Bulgarian)

* New translations messages.json (Catalan)

* New translations messages.json (Czech)

* New translations messages.json (Danish)

* New translations messages.json (German)

* New translations messages.json (Finnish)

* New translations messages.json (Slovak)

* New translations messages.json (Hebrew)

* New translations messages.json (Hungarian)

* New translations messages.json (Italian)

* New translations messages.json (Japanese)

* New translations messages.json (Korean)

* New translations messages.json (Dutch)

* New translations messages.json (Polish)

* New translations messages.json (Portuguese)

* New translations messages.json (Russian)

* New translations messages.json (English, India)
2021-06-29 15:40:50 -04:00
Vincent Salucci
11545c7281 [Version] Bumped to 2.21.0 (#1050) 2021-06-28 12:38:48 -05:00
Kyle Spearrin
489491a724 add captcha connector (#1042)
* add captcha connector

* Update src/connectors/captcha.html

Co-authored-by: Addison Beck <abeck@bitwarden.com>

* Update src/connectors/captcha.scss

Co-authored-by: Addison Beck <abeck@bitwarden.com>

Co-authored-by: Addison Beck <abeck@bitwarden.com>
2021-06-22 15:44:56 -04:00
Matt Gibson
5029e56a92 Update jslib 2021-06-21 19:17:46 -04:00
Oscar Hinton
ff4586a873 Add reprompt help link (#1041)
(cherry picked from commit 34cb26416c)
2021-06-21 20:55:04 +02:00
Vincent Salucci
b370f3de56 [Toast] - BUG - Fixed styles (#1036)
* [Toast] BUG - Fixed toast stylings

* Updated toast-content padding
2021-06-21 09:15:04 -05:00
Matt Gibson
3dec25a607 Must await to get a value (#1035)
(cherry picked from commit c1a7b85f8b)
2021-06-16 09:36:55 -04:00
Thomas Rittson
cdf172f2a1 Update storageService implementations (#1033)
* Add htmlStorageService.has

* Add memoryStorageService.has
2021-06-16 07:40:41 +10:00
Thomas Rittson
25fa3e7a2e Update jslib 2021-06-16 07:00:16 +10:00
Vincent Salucci
6d54740aaf [Reset Password] Custom Permission pairing (#1027) 2021-06-14 13:12:15 -05:00
Joseph Flinn
c198ec32bb Fix deploy workflow (#1016)
* fixing the automated web deploys

* adding the action version numbers
2021-06-11 12:52:59 -07:00
Oscar Hinton
5939d590e3 Ensure we only select all visible users (#1025) 2021-06-10 16:36:30 +02:00
Chad Scharf
fd683e9d71 Fix #1020 - XSS via innerHTML property (#1022) 2021-06-09 15:58:07 -04:00
Oscar Hinton
fd328eef2a Refactor bulk delete and confirm (#1013)
* Prevent confirm dialog from showing when autoConfirm is enabled

* Fix bulk confirm not showing if more than 3 confirmed users in org.

* Refactor bulk confirm to show a single dialog with all fingerprints

* Move bulk status dialog to bulk folder

* Refactor bulk delete to use a custom modal

* Update src/locales/en/messages.json

Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>

Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>
2021-06-09 17:04:21 +02:00
Matt Gibson
b20206d350 update jslib (#1021) 2021-06-09 08:51:26 -05:00
Thomas Rittson
82ec4b12f7 bump jslib (#1018) 2021-06-09 10:40:45 +10:00
Matt Portune
d6496d51d3 Update entrypoint.sh (#1019)
Copy `assetlinks.json` to app dir
2021-06-08 14:01:46 -04:00
Oscar Hinton
b12d0387f6 Add jslib as a "real" dependency (#951)
* Add jslib as a dependency

* Cleanup tsconfig, webpack, add jslib-angular to package.json

* Update all import paths

* Add back @types/node.

* Lint

* Remove dummy module

* Remove merge conflict

* Group imports

* Bump jslib
2021-06-07 20:13:58 +02:00
Matt Gibson
f15e78b91d Correct appApiAction directive use. (#1015) 2021-06-04 13:10:00 -05:00
Thomas Rittson
c0f85366bd Convert sets to arrays before saving to storage (#1012) 2021-06-04 09:38:36 +10:00
Chad Scharf
a554c0e660 Revert "Theme Support with a Dark Mode (#974)" (#1011)
This reverts commit cf24113924.
2021-06-03 15:49:14 -04:00
Jens Spanier
2f8a721033 Fix non-unique ids on settings page (#1002) 2021-06-03 08:33:18 +10:00
Joseph Flinn
0a0e871696 Add deploy workflow (#1010)
* adding automated deploy workflow

* adding action versions
2021-06-02 13:45:31 -07:00
Danny Murphy
cf24113924 Theme Support with a Dark Mode (#974)
* Stylesheets

* Theme Configuration

* Options Area

* swal2 style

Missed the swal2 styling and improved the table theming

* Icon styling

* Fix theme not saving

* Update English

Remove colour to make it more translatable between English and American

* Update messages.json

* Login logo

* dropdown and login logo

* btn-link and totp fix

Added a border for extra readability on the btn-link

* Organisation Styling

* Update messages.json

* Update webauthn-fallback.ts

Add missing semicolon and enable console.error bypass for tslint

* Fix contrast issues

Update the blue to match the browser extension and lighten the grey for text-muted variable

* Add Paypal Container and Loading svg file

* Update jslib

* Password Generator contrast fix
2021-06-02 14:38:04 -04:00
Vincent Salucci
1bacc8b774 [Reset Password] Admin Actions (#935)
* [Reset Password] Admin Actions

* Updated components to pass orgUser.Id and use within password reset apis

* Removed password auto-generation, fixed loading visual bug by chaining promise actions

* Update jslib 97ece68 -> 73ec484

* Updated all classes to new reset password flows

* Update jslib (73ec484 -> 5f1ad85)

* Update jslib (5f1ad85 -> 395ded0)

* Update encryption steps for change-password flow

* Fixed merge conflicts

* Updated based on requested changes
2021-06-02 11:35:49 -05:00
Joseph Flinn
65b52617a8 fixing the release workflow (#1009) 2021-06-02 09:28:51 -07:00
Joseph Flinn
db3cf882d3 bumping version 2.20.4 (#1008) 2021-06-02 09:02:31 -07:00
Joseph Flinn
59f2b51d25 Release Workflow (#1007)
* spilting out the build workflow into build and release workflows. Solves the problem of the incorrect self-hosted version being released

* pinning action versions

* release workflow fixes

* removing unneeded env vars

* normalizing the naming conventions

* one more Docker
2021-06-02 08:28:56 -07:00
Matt Gibson
945e968e06 Export all events matching dates (#990)
* Export eagerly pulls down all events

Export does not add to rendered elements since that may cause slow down.
Export is tied to the currently rendered list of events though `dirtyDates` bool

* Use manual btn-submit class

* Remove unnecessary method

* Fix ExpressionChangedAfterItHasBeenCheckedError
2021-06-02 07:21:57 -05:00
Oscar Hinton
744e86601f Bump jslib (#1003) 2021-06-01 21:04:09 +02:00
Thomas Rittson
91643d40bd bump jslib (#998) 2021-05-28 09:46:21 +10:00
Joseph Flinn
9b7a1c7760 adding the self host check back in (#997) 2021-05-27 14:28:42 -07:00
Thomas Rittson
da0df3a73b Set baseUrl in QA cloud environment (#994)
* Set baseUrl if not deployed to prod server

* Add env variable TARGET and use to set baseUrl

* remove webPlatformUtilsService.isProdServer

* passing the ENV through to the Angular app

* switching the value of SELF_HOST back to true

* fixing some webpack.config variables

* fixing the selfhost angular process.env

* removing unecessary code

Co-authored-by: Joseph Flinn <joseph.s.flinn@gmail.com>
2021-05-27 09:46:26 -07:00
Vincent Salucci
6586af71f8 [Reset Password] Event updates (#993)
* [Reset Password] Event updates

* Update jslib 395ded0 -> 6fbe330
2021-05-27 11:42:05 -05:00
Oscar Hinton
b3f5c72ba9 Bump NPM to v7 (#995)
* Bump NPM to v7

* Refresh package-lock
2021-05-26 22:17:37 +02:00
Danny Murphy
fdbce4d84d Update webauthn-fallback.ts (#992)
Add missing semicolon and enable console.error bypass for tslint
2021-05-26 09:43:54 +10:00
Oscar Hinton
d31130b79f Bulk confirm (#987)
* Add bulk confirm

* Add confirmation modal to the other bulk actions

* Add spinner to bulk status to let the user know something is going on

* Fix linting

* Add await before reloading users

* Close modal on error

* Bump jslib
2021-05-25 19:24:09 +02:00
Oscar Hinton
d566c963c1 Bump version to 2.20.3 (#989) 2021-05-21 15:55:41 +02:00
Oscar Hinton
1098adc03d Correctly handle dash in locale, and add a fallback to en. (#988) 2021-05-21 13:07:33 +02:00
Thomas Rittson
e34e4728d0 Fix accessibility (a11y) on swal2 modals (#986)
* Remove tabindex on bootstrap modals if swal open

* fix linting
2021-05-21 06:52:44 +10:00
Matt Gibson
35346613d8 Version bump for org search hot fix (#985) 2021-05-19 13:13:55 -05:00
Oscar Hinton
0fd89e06c6 Rename Ciphers -> Items in trash cleanup message (#984) 2021-05-19 19:42:06 +02:00
Matt Gibson
1c5ce23d35 Set search index for limited collection org users (#983) 2021-05-19 11:11:11 -05:00
Oscar Hinton
45c31aa089 Bulk remove organization users (#970)
* Add support for bulk removal of org users

* Rename to UserBulkDeleteRequest

* Use OrganizationUserBulkRequest

* Bump jslib

* Fix linting
2021-05-18 10:27:52 +02:00
Vince Grassia
34be07c220 Pin versions of actions in workflow (#980) 2021-05-17 11:18:45 -04:00
Oscar Hinton
968a255269 Correctly handle errors on remove and reinvite of organization users (#979) 2021-05-17 15:13:26 +02:00
Oscar Hinton
a27be135da Change all remaining modals to be scrollable (#976)
* Change all remaining modals to be scrollable

* Fix password-generator-history and two-factor-options not using modal-body

* Remove modal-dialog-scrollable on two-factor-setup components
2021-05-14 21:03:45 +02:00
Oscar Hinton
bb95eb84ea Bump node to 14 (#955)
* Bump node to 14

* Update Readme

* Change engine to ~14

* Bump jslib

* Remove @angular/localize since it's not used
2021-05-14 20:08:03 +02:00
Matt Gibson
54cd5a68b3 Add event export (#967)
* Include human readable export message on events

* Add export currently visible events.

* PR feedback
2021-05-13 18:39:53 -05:00
Trey Greer
9abdefa947 Added additional languages (#975) 2021-05-13 17:08:27 -04:00
Kyle Spearrin
d9322c1307 use swal titletext to avoid XSS (#966) 2021-05-13 10:08:16 -04:00
Leon-Hikari
a8d614628a Adds folder word-wrap (#880)
Uses spaces and dashes as preferred separator in folder names
(instead of just breaking whereever the max width is encountered)
2021-05-13 14:02:39 +10:00
Thomas Rittson
7f9f6d3d0e Check encKey when importing encrypted JSON (#968)
* Check encKey when importing encrypted JSON

* bump jslib
2021-05-13 11:22:26 +10:00
Vince Grassia
4c1e36462c Fix docker tag version in workflow (#973) 2021-05-12 17:24:18 -04:00
Chad Scharf
32d04106a1 Version bump, 2.20.1 (#971) 2021-05-12 12:58:21 -04:00
Oscar Hinton
a3506e833a Change WebAuthn connectors from using inline onclick to external (#969) 2021-05-12 17:19:20 +02:00
Oscar Hinton
51f3fee75d Bulk re-invite of org users (#961)
* Add support for bulk re-invite of org users

* Add selectAll, resolve review comments
2021-05-12 16:38:17 +02:00
Thomas Rittson
3ac2ce079a Refactor Send 'copy link' functionality (#960)
* Refactor Send 'copy link' functionality

* bump jslib

* Print debug message if copyToClipboard fails

* fix linting
2021-05-12 10:51:12 +10:00
Daniel James Smith
97e1c7a2ea Fix typo in webAuthnAuthenticate (#964) 2021-05-11 17:19:55 -04:00
Joseph Flinn
29f741316c fixing docker push (#965)
* fixing docker push

* adding in the the missed vars
2021-05-11 12:46:14 -07:00
Joseph Flinn
293ae12e33 Updating the docker signing key (#963)
* Updating the docker signing key

* restricting the Azure login to specific branches that use docker

* only retrieving secrets on specific branches
2021-05-11 12:02:31 -07:00
Kyle Spearrin
49d1c135db New Crowdin updates (#962)
* New translations messages.json (Romanian)

* New translations messages.json (Bengali)

* New translations messages.json (Swedish)

* New translations messages.json (Turkish)

* New translations messages.json (Ukrainian)

* New translations messages.json (Chinese Simplified)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (Vietnamese)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Indonesian)

* New translations messages.json (Croatian)

* New translations messages.json (Slovenian)

* New translations messages.json (Estonian)

* New translations messages.json (Latvian)

* New translations messages.json (English, United Kingdom)

* New translations messages.json (Esperanto)

* New translations messages.json (Malayalam)

* New translations messages.json (Sinhala)

* New translations messages.json (Norwegian Bokmal)

* New translations messages.json (Serbian (Latin))

* New translations messages.json (Serbian (Cyrillic))

* New translations messages.json (Slovak)

* New translations messages.json (French)

* New translations messages.json (Greek)

* New translations messages.json (Spanish)

* New translations messages.json (Afrikaans)

* New translations messages.json (Belarusian)

* New translations messages.json (Bulgarian)

* New translations messages.json (Catalan)

* New translations messages.json (Czech)

* New translations messages.json (Danish)

* New translations messages.json (German)

* New translations messages.json (Finnish)

* New translations messages.json (Russian)

* New translations messages.json (Hebrew)

* New translations messages.json (Hungarian)

* New translations messages.json (Italian)

* New translations messages.json (Japanese)

* New translations messages.json (Korean)

* New translations messages.json (Dutch)

* New translations messages.json (Polish)

* New translations messages.json (Portuguese)

* New translations messages.json (English, India)
2021-05-11 13:51:17 -04:00
Oscar Hinton
d900d2d3f8 Change modal-dialog for add-edit cipher to be scrollable (#957) 2021-05-07 09:43:41 +02:00
Oscar Hinton
4a61f0ac04 Cleanup tsconfig (#954)
* Cleanup tsconfig

* Removed dummy module
2021-05-05 09:46:14 +02:00
Oscar Hinton
b1635debcc Password reprompt (#929)
* Use passwordRepromptService

* Rename passwordPrompt to reprompt. Protect bulk actions

* Change card to hidden, minor refactor.

* Explicit reprompt value check

* Ensure locales are the same on all platforms

* Move showPasswordDialog to platformutils

* Fix sweet alert validation message margin

* Update locale to be the same as browser
2021-05-03 20:55:42 +02:00
Thomas Rittson
b3a4f833a1 Fix "copy link to clipboard" for large file Sends (#949)
* Throw error if execCommand('copy') is disabled

* Use dialog for file Send creation success

* Show popup modal after long Send file uploads

* fix linting

* bump jslib
2021-04-28 07:40:36 +10:00
Oscar Hinton
dd56c9bc87 Add auto delete warning to trash page (#953)
* Add warning to trash page
2021-04-27 18:49:02 +02:00
Matt Gibson
19f92e74f5 Update jslib (#952) 2021-04-26 16:58:36 -05:00
Oscar Hinton
d71d0d9af6 Improve WebAuthn error detection for invalid data (#946) 2021-04-23 21:07:15 +02:00
Matt Gibson
2392d34ed8 Update jslib (#947) 2021-04-23 14:02:42 -05:00
Matt Gibson
f6eec08b70 Specify organization id as the indexing entity (#945)
* Specify organization id as the indexing entity

* Update jslib
2021-04-23 09:41:10 -05:00
Oscar Hinton
9547b72566 Bump dependencies (#936)
* Bump dependencies
2021-04-22 21:29:29 +02:00
Vincent Salucci
38097c40d8 [Version] Bumped to 2.20.0 (#944)
* [Version] Bumped to 2.20.0

* Updated package-lock version
2021-04-22 11:53:20 -05:00
Vincent Salucci
66b7f4d344 [Reset Password] Feature Flag (#943) 2021-04-22 09:43:51 -05:00
251 changed files with 39164 additions and 11456 deletions

View File

@@ -5,16 +5,13 @@ on:
branches-ignore:
- 'l10n_master'
- 'gh-pages'
release:
types:
- published
jobs:
cloc:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up cloc
run: |
@@ -28,9 +25,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Node
uses: actions/setup-node@v1
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
node-version: '10.x'
node-version: '14'
- name: Update NPM
run: |
npm install -g npm@7
- name: Print environment
run: |
@@ -41,18 +42,15 @@ jobs:
docker --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Login to Azure
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
with:
@@ -62,15 +60,15 @@ jobs:
dct-delegate-2-repo-passphrase,
dct-delegate-2-key"
- name: Log into docker
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
- name: Log into Docker
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
env:
DOCKER_USERNAME: ${{ steps.retrieve-secrets.outputs.docker-username }}
DOCKER_PASSWORD: ${{ steps.retrieve-secrets.outputs.docker-password }}
- name: Setup Docker Trust
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
run: |
mkdir -p ~/.docker/trust/private
@@ -80,7 +78,7 @@ jobs:
DCT_DELEGATE_KEY: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-key }}
- name: Checkout repo
uses: actions/checkout@v2
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Restore
run: dotnet tool restore
@@ -93,7 +91,7 @@ jobs:
npm install
npm run dist:selfhost
echo -e "\nBuilding docker image"
echo -e "\nBuilding Docker image"
docker --version
docker build -t bitwarden/web .
@@ -102,22 +100,11 @@ jobs:
run: docker tag bitwarden/web bitwarden/web:rc
- name: Tag dev
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
if: github.ref == 'refs/heads/master'
run: docker tag bitwarden/web bitwarden/web:dev
- name: Tag beta
if: github.event_name == 'release'
run: docker tag bitwarden/web bitwarden/web:beta
- name: Tag version
if: github.event_name == 'release'
run: docker tag bitwarden/web bitwarden/web:$($env:RELEASE_TAG_NAME.trimStart('v'))
shell: pwsh
env:
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
- name: List docker images
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
- name: List Docker images
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
run: docker images
- name: Push rc images
@@ -128,71 +115,51 @@ jobs:
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
- name: Push dev images
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
if: github.ref == 'refs/heads/master'
run: docker push bitwarden/web:dev
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
- name: Push beta images
if: github.event_name == 'release'
run: docker push bitwarden/web:beta
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
- name: Push latest images
if: github.event_name == 'release'
run: docker push bitwarden/web:latest
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
- name: Push version images
if: github.event_name == 'release'
run: docker push bitwarden/web:$($env:RELEASE_TAG_NAME.trimStart('v'))
shell: pwsh
env:
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
- name: Log out of docker
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
- name: Log out of Docker
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
run: docker logout
windows:
runs-on: windows-latest
steps:
- name: Set up NuGet
uses: nuget/setup-nuget@v1
uses: nuget/setup-nuget@04b0c2b8d1b97922f67eca497d7cf0bf17b8ffe1
with:
nuget-version: 'latest'
- name: Set up MSBuild
uses: microsoft/setup-msbuild@v1
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d
- name: Set up Node
uses: actions/setup-node@v1
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
node-version: '10.x'
node-version: '14'
- name: Update NPM
run: |
npm install -g npm@7
- name: Print environment
run: |
nuget help
nuget help | grep Version
msbuild -version
dotnet --info
node --version
npm --version
Write-Output "GitHub ref: $env:GITHUB_REF"
Write-Output "GitHub event: $env:GITHUB_EVENT"
shell: pwsh
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@v2
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: npm install
run: npm install

70
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
name: Deploy
on:
workflow_dispatch:
inputs:
release_version:
description: "Release Tag Version <vX.X.X>"
required: true
release:
types:
- published
jobs:
deploy:
name: Deploy Web Vault
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
ref: gh-pages
- name: Get release version
id: release-version
run: |
if [[ "${{ github.event_name }}" == "release" ]]; then
echo "::set-output name=version::${{ github.event.release.tag_name }}"
else
echo "::set-output name=version::${{ github.event.inputs.release_version }}"
fi
- name: Create deploy branch
run: |
git switch -c deploy-${{ steps.release-version.outputs.version }}
git push -u origin deploy-${{ steps.release-version.outputs.version }}
- name: Checkout Repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
ref: rc
- name: setup git config
run: |
git config user.name = "GitHub Action Bot"
git config user.email = "<>"
- name: Install and Build
run: |
npm ci
npm run dist
- name: Deploy GitHub Pages
uses: crazy-max/ghaction-github-pages@db4476a01402e1a7ce05f41832040eef16d14925 # v2.5.0
with:
target_branch: deploy-${{ steps.release-version.outputs.version }}
build_dir: build
keep_history: true
commit_message: "Staging deploy ${{ steps.release-version.outputs.version }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Deploy PR
run: |
gh pr create --title "Deploy $VERSION" --body "Deploying $VERSION" --base gh-pages --head "$PR_BRANCH"
env:
VERSION: ${{ steps.release-version.outputs.version }}
PR_BRANCH: deploy-${{ steps.release-version.outputs.version }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

157
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,157 @@
name: Release
on:
workflow_dispatch:
inputs:
release_tag_name_input:
description: "Release Tag Name <X.X.X>"
required: true
jobs:
setup:
runs-on: ubuntu-latest
outputs:
release_upload_url: ${{ steps.create_release.outputs.upload_url }}
release_version: ${{ steps.create_tags.outputs.package_version }}
tag_version: ${{ steps.create_tags.outputs.tag_version }}
steps:
- name: Branch check
run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]]; then
echo "==================================="
echo "[!] Can only release from rc branch"
echo "==================================="
exit 1
fi
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # 2.3.4
- name: Create Release Vars
id: create_tags
run: |
case "${RELEASE_TAG_NAME_INPUT:0:1}" in
v)
echo "RELEASE_NAME=${RELEASE_TAG_NAME_INPUT:1}" >> $GITHUB_ENV
echo "RELEASE_TAG_NAME=$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
echo "::set-output name=package_version::${RELEASE_TAG_NAME_INPUT:1}"
echo "::set-output name=tag_version::$RELEASE_TAG_NAME_INPUT"
;;
[0-9])
echo "RELEASE_NAME=$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
echo "RELEASE_TAG_NAME=v$RELEASE_TAG_NAME_INPUT" >> $GITHUB_ENV
echo "::set-output name=package_version::$RELEASE_TAG_NAME_INPUT"
echo "::set-output name=tag_version::v$RELEASE_TAG_NAME_INPUT"
;;
*)
exit 1
;;
esac
env:
RELEASE_TAG_NAME_INPUT: ${{ github.event.inputs.release_tag_name_input }}
- name: Create Draft Release
id: create_release
uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # 1.1.4 - Repo Archived
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ env.RELEASE_TAG_NAME }}
release_name: Version ${{ env.RELEASE_NAME }}
draft: true
prerelease: false
ubuntu:
runs-on: ubuntu-latest
needs: setup
env:
RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
steps:
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
node-version: '14'
- name: Update NPM
run: |
npm install -g npm@7
- name: Print environment
run: |
whoami
node --version
npm --version
gulp --version
docker --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Login to Azure
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
with:
keyvault: "bitwarden-prod-kv"
secrets: "docker-password,
docker-username,
dct-delegate-2-repo-passphrase,
dct-delegate-2-key"
- name: Log into Docker
run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
env:
DOCKER_USERNAME: ${{ steps.retrieve-secrets.outputs.docker-username }}
DOCKER_PASSWORD: ${{ steps.retrieve-secrets.outputs.docker-password }}
- name: Setup Docker Trust
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
run: |
mkdir -p ~/.docker/trust/private
echo "$DCT_DELEGATE_KEY" > ~/.docker/trust/private/$DCT_DELEGATION_KEY_ID.key
env:
DCT_DELEGATION_KEY_ID: "c9bde8ec820701516491e5e03d3a6354e7bd66d05fa3df2b0062f68b116dc59c"
DCT_DELEGATE_KEY: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-key }}
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Restore
run: dotnet tool restore
- name: Build
run: |
echo -e "# Building Web\n"
echo "Building app"
echo "npm version $(npm --version)"
npm install
npm run dist:selfhost
echo -e "\nBuilding Docker image"
docker --version
docker build -t bitwarden/web .
- name: Tag version
run: docker tag bitwarden/web bitwarden/web:$RELEASE_VERSION
- name: List Docker images
run: docker images
- name: Push latest images
run: docker push bitwarden/web:latest
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
- name: Push version images
run: docker push bitwarden/web:$RELEASE_VERSION
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
- name: Log out of Docker
run: docker logout

View File

@@ -23,7 +23,8 @@
### Requirements
- [Node.js](https://nodejs.org) v8.11 or greater
- [Node.js](https://nodejs.org) v14.17 or greater
- NPM v7
### Run the app

View File

@@ -31,6 +31,7 @@ mkhomedir_helper $USERNAME
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
cp /etc/bitwarden/web/app-id.json /app/app-id.json
cp /etc/bitwarden/web/assetlinks.json /app/assetlinks.json
chown -R $USERNAME:$GROUPNAME /app
chown -R $USERNAME:$GROUPNAME /bitwarden_server

2
jslib

Submodule jslib updated: f6d91e2d92...6f428e11c4

26899
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +1,28 @@
{
"name": "bitwarden-web",
"version": "2.20.1",
"version": "2.21.1",
"license": "GPL-3.0",
"repository": "https://github.com/bitwarden/web",
"scripts": {
"sub:init": "git submodule update --init --recursive",
"sub:update": "git submodule update --remote",
"sub:pull": "git submodule foreach git pull origin master",
"postinstall": "npm run sub:init",
"preinstall": "npm run sub:init",
"symlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib",
"symlink:mac": "npm run symlink:lin",
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
"build": "gulp prebuild && webpack",
"build:watch": "gulp prebuild && webpack-dev-server",
"build:watch": "gulp prebuild && webpack serve",
"build:dev": "gulp prebuild && cross-env ENV=development webpack",
"build:dev:watch": "gulp prebuild && cross-env ENV=development webpack-dev-server",
"build:dev:watch": "gulp prebuild && cross-env ENV=development webpack serve",
"build:qa": "gulp prebuild && cross-env NODE_ENV=production ENV=qa webpack",
"build:qa:watch": "gulp prebuild && cross-env NODE_ENV=production ENV=qa webpack-dev-server",
"build:qa:watch": "gulp prebuild && cross-env NODE_ENV=production ENV=qa webpack serve",
"build:prod": "gulp prebuild && cross-env NODE_ENV=production ENV=production webpack",
"build:prod:watch": "gulp prebuild && cross-env NODE_ENV=production ENV=production webpack-dev-server",
"build:selfhost": "gulp prebuild && cross-env SELF_HOST=true webpack-dev-server",
"build:selfhost:watch": "gulp prebuild && cross-env SELF_HOST=true webpack-dev-server",
"build:prod:watch": "gulp prebuild && cross-env NODE_ENV=production ENV=production webpack serve",
"build:selfhost": "gulp prebuild && cross-env SELF_HOST=true webpack serve",
"build:selfhost:watch": "gulp prebuild && cross-env SELF_HOST=true webpack serve",
"build:selfhost:prod": "gulp prebuild && cross-env SELF_HOST=true NODE_ENV=production webpack",
"build:selfhost:prod:watch": "gulp prebuild && cross-env SELF_HOST=true NODE_ENV=production webpack-dev-server",
"build:selfhost:prod:watch": "gulp prebuild && cross-env SELF_HOST=true NODE_ENV=production webpack serve",
"clean:l10n": "git push origin --delete l10n_master",
"dist": "npm run build:prod && gulp postdist",
"dist:selfhost": "npm run build:selfhost:prod && gulp postdist",
@@ -32,76 +32,58 @@
"lint:fix": "tslint 'src/**/*.ts' --fix"
},
"devDependencies": {
"@angular/compiler-cli": "^9.1.12",
"@ngtools/webpack": "^9.1.12",
"@angular/compiler-cli": "^11.2.11",
"@ngtools/webpack": "^11.2.10",
"@types/duo_web_sdk": "^2.7.0",
"@types/jquery": "^3.5.5",
"@types/lunr": "^2.3.3",
"@types/node": "^10.17.28",
"@types/node-forge": "^0.9.7",
"@types/papaparse": "^5.2.0",
"@types/node": "^14.17.2",
"@types/webcrypto": "^0.0.28",
"@types/webpack": "^4.4.11",
"@types/zxcvbn": "^4.4.0",
"angular2-template-loader": "^0.6.2",
"clean-webpack-plugin": "^0.1.19",
"copy-webpack-plugin": "^5.1.1",
"cross-env": "^5.2.0",
"css-loader": "^1.0.0",
"del": "^3.0.0",
"file-loader": "^2.0.0",
"gh-pages": "^1.2.0",
"gulp": "^4.0.0",
"gulp-google-webfonts": "^2.0.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.13.1",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.0",
"terser-webpack-plugin": "^1.2.3",
"ts-loader": "^7.0.5",
"@types/webpack": "^4.4.27",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^6.4.0",
"cross-env": "^7.0.3",
"css-loader": "^5.2.3",
"del": "^6.0.0",
"file-loader": "^6.2.0",
"gh-pages": "^3.1.0",
"gulp": "^4.0.2",
"gulp-google-webfonts": "^4.0.0",
"html-loader": "^1.3.2",
"html-webpack-plugin": "^4.5.1",
"mini-css-extract-plugin": "^1.5.0",
"sass": "^1.32.10",
"sass-loader": "^10.1.1",
"style-loader": "^2.0.0",
"tapable": "^1.1.3",
"terser-webpack-plugin": "^4.2.3",
"ts-loader": "^8.1.0",
"tslint": "^6.1.3",
"tslint-loader": "^3.5.4",
"typescript": "3.8.3",
"webpack": "^4.29.0",
"webpack-cli": "^3.2.1",
"webpack-dev-server": "^3.1.14"
"typescript": "4.1.5",
"webpack": "^4.46.0",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^3.11.2"
},
"dependencies": {
"@angular/animations": "9.1.12",
"@angular/cdk": "9.2.4",
"@angular/common": "9.1.12",
"@angular/compiler": "9.1.12",
"@angular/core": "9.1.12",
"@angular/forms": "9.1.12",
"@angular/platform-browser": "9.1.12",
"@angular/platform-browser-dynamic": "9.1.12",
"@angular/router": "9.1.12",
"@microsoft/signalr": "3.1.13",
"@microsoft/signalr-protocol-msgpack": "3.1.13",
"angular2-toaster": "8.0.0",
"big-integer": "1.6.48",
"bootstrap": "4.3.1",
"braintree-web-drop-in": "1.13.0",
"@bitwarden/jslib-angular": "file:jslib/angular",
"@bitwarden/jslib-common": "file:jslib/common",
"angular2-toaster": "11.0.1",
"bootstrap": "4.6.0",
"braintree-web-drop-in": "1.30.1",
"browser-hrtime": "^1.1.8",
"core-js": "2.6.2",
"core-js": "^3.11.0",
"date-input-polyfill": "^2.14.0",
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git#410a9186cc34663c4913b17d6528067cd3331f1d",
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git#5f9a1a8598d2cda494c4f5ee0e38b31474abfee9",
"font-awesome": "4.7.0",
"jquery": "3.6.0",
"lunr": "2.3.3",
"ngx-infinite-scroll": "7.0.1",
"node-forge": "0.10.0",
"papaparse": "5.2.0",
"popper.js": "1.14.4",
"popper.js": "1.16.1",
"qrious": "4.0.2",
"rxjs": "6.6.2",
"sweetalert2": "10.15.4",
"tslib": "^2.0.1",
"web-animations-js": "2.3.1",
"webcrypto-shim": "0.1.4",
"whatwg-fetch": "3.0.0",
"zone.js": "0.10.3",
"zxcvbn": "4.4.2"
"sweetalert2": "^10.16.6",
"webcrypto-shim": "0.1.7",
"whatwg-fetch": "3.6.2"
},
"engines": {
"node": "~14",
"npm": "~7"
}
}

View File

@@ -12,11 +12,11 @@ import {
ToasterService,
} from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { StateService } from 'jslib/abstractions/state.service';
import { UserService } from 'jslib/abstractions/user.service';
import { EmergencyAccessAcceptRequest } from 'jslib/models/request/emergencyAccessAcceptRequest';
import { ApiService } from 'jslib-common/abstractions/api.service';
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';
@Component({
selector: 'app-accept-emergency',

View File

@@ -2,6 +2,7 @@ import {
Component,
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
@@ -12,12 +13,18 @@ import {
ToasterService,
} from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { StateService } from 'jslib/abstractions/state.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { 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/models/request/organizationUserAcceptRequest';
import { OrganizationUserAcceptRequest } from 'jslib-common/models/request/organizationUserAcceptRequest';
import { OrganizationUserResetPasswordEnrollmentRequest } from 'jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest';
import { Utils } from 'jslib-common/misc/utils';
import { Policy } from 'jslib-common/models/domain/policy';
@Component({
selector: 'app-accept-organization',
@@ -33,7 +40,8 @@ export class AcceptOrganizationComponent implements OnInit {
constructor(private router: Router, private toasterService: ToasterService,
private i18nService: I18nService, private route: ActivatedRoute,
private apiService: ApiService, private userService: UserService,
private stateService: StateService) { }
private stateService: StateService, private cryptoService: CryptoService,
private policyService: PolicyService) { }
ngOnInit() {
let fired = false;
@@ -51,8 +59,36 @@ export class AcceptOrganizationComponent implements OnInit {
const request = new OrganizationUserAcceptRequest();
request.token = qParams.token;
try {
this.actionPromise = this.apiService.postOrganizationUserAccept(qParams.organizationId,
qParams.organizationUserId, request);
if (await this.performResetPasswordAutoEnroll(qParams)) {
this.actionPromise = this.apiService.postOrganizationUserAccept(qParams.organizationId,
qParams.organizationUserId, request).then(() => {
// Retrieve Public Key
return this.apiService.getOrganizationKeys(qParams.organizationId);
}).then(async response => {
if (response == null) {
throw new Error(this.i18nService.t('resetPasswordOrgKeysError'));
}
const publicKey = Utils.fromB64ToArray(response.publicKey);
// RSA Encrypt user's encKey.key with organization public key
const encKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
// Create request and execute enrollment
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
// Get User Id
const userId = await this.userService.getUserId();
return this.apiService.putOrganizationUserResetPasswordEnrollment(qParams.organizationId, userId, resetRequest);
});
} else {
this.actionPromise = this.apiService.postOrganizationUserAccept(qParams.organizationId,
qParams.organizationUserId, request);
}
await this.actionPromise;
const toast: Toast = {
type: 'success',
@@ -92,4 +128,21 @@ export class AcceptOrganizationComponent implements OnInit {
this.loading = false;
});
}
private async performResetPasswordAutoEnroll(qParams: any): Promise<boolean> {
let policyList: Policy[] = null;
try {
const policies = await this.apiService.getPoliciesByToken(qParams.organizationId, qParams.token,
qParams.email, qParams.organizationUserId);
policyList = this.policyService.mapPoliciesFromToken(policies);
} catch { }
if (policyList != null) {
const result = this.policyService.getResetPasswordPolicyOptions(policyList, qParams.organizationId);
// Return true if policy enabled and auto-enroll enabled
return result[1] && result[0].autoEnrollEnabled;
}
return false;
}
}

View File

@@ -1,11 +1,11 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.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({
selector: 'app-hint',

View File

@@ -1,20 +1,20 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { UserService } from 'jslib/abstractions/user.service';
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.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({
selector: 'app-lock',

View File

@@ -5,6 +5,10 @@
<p class="lead text-center mx-4 mb-4">{{'loginOrCreateNewAccount' | i18n}}</p>
<div class="card d-block">
<div class="card-body">
<app-callout type="warning" title="{{'resetPasswordPolicyAutoEnroll' | i18n}}"
*ngIf="showResetPasswordAutoEnrollWarning">
{{'resetPasswordAutoEnrollInviteWarning' | i18n}}
</app-callout>
<div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required

View File

@@ -4,27 +4,35 @@ import {
Router,
} from '@angular/router';
import { AuthService } from 'jslib/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.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 { 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';
@Component({
selector: 'app-login',
templateUrl: 'login.component.html',
})
export class LoginComponent extends BaseLoginComponent {
showResetPasswordAutoEnrollWarning = false;
constructor(authService: AuthService, router: Router,
i18nService: I18nService, private route: ActivatedRoute,
storageService: StorageService, stateService: StateService,
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService) {
passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService,
private apiService: ApiService, private policyService: PolicyService) {
super(authService, router,
platformUtilsService, i18nService,
stateService, environmentService,
@@ -49,6 +57,22 @@ export class LoginComponent extends BaseLoginComponent {
queryParamsSub.unsubscribe();
}
});
const invite = await this.stateService.get<any>('orgInvitation');
if (invite != null) {
let policyList: Policy[] = null;
try {
const policies = await this.apiService.getPoliciesByToken(invite.organizationId, invite.token,
invite.email, invite.organizationUserId);
policyList = this.policyService.mapPoliciesFromToken(policies);
} catch { }
if (policyList != null) {
const result = this.policyService.getResetPasswordPolicyOptions(policyList, invite.organizationId);
// Set to true if policy enabled and auto-enroll enabled
this.showResetPasswordAutoEnrollWarning = result[1] && result[0].autoEnrollEnabled;
}
}
}
async goAfterLogIn() {

View File

@@ -3,10 +3,10 @@ import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { DeleteRecoverRequest } from 'jslib/models/request/deleteRecoverRequest';
import { DeleteRecoverRequest } from 'jslib-common/models/request/deleteRecoverRequest';
@Component({
selector: 'app-recover-delete',

View File

@@ -3,12 +3,12 @@ import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
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 { TwoFactorRecoveryRequest } from 'jslib/models/request/twoFactorRecoveryRequest';
import { TwoFactorRecoveryRequest } from 'jslib-common/models/request/twoFactorRecoveryRequest';
@Component({
selector: 'app-recover-two-factor',

View File

@@ -4,22 +4,22 @@ import {
Router,
} from '@angular/router';
import { ApiService } from 'jslib/abstractions/api.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { StateService } from 'jslib/abstractions/state.service';
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 { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.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 { RegisterComponent as BaseRegisterComponent } from 'jslib/angular/components/register.component';
import { RegisterComponent as BaseRegisterComponent } from 'jslib-angular/components/register.component';
import { MasterPasswordPolicyOptions } from 'jslib/models/domain/masterPasswordPolicyOptions';
import { Policy } from 'jslib/models/domain/policy';
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions';
import { Policy } from 'jslib-common/models/domain/policy';
import { PolicyData } from 'jslib/models/data/policyData';
import { ReferenceEventRequest } from 'jslib/models/request/referenceEventRequest';
import { PolicyData } from 'jslib-common/models/data/policyData';
import { ReferenceEventRequest } from 'jslib-common/models/request/referenceEventRequest';
@Component({
selector: 'app-register',

View File

@@ -4,19 +4,19 @@ import {
Router,
} from '@angular/router';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { SyncService } from 'jslib/abstractions/sync.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import {
SetPasswordComponent as BaseSetPasswordComponent,
} from 'jslib/angular/components/set-password.component';
} from 'jslib-angular/components/set-password.component';
@Component({
selector: 'app-set-password',

View File

@@ -4,16 +4,16 @@ import {
Router,
} from '@angular/router';
import { ApiService } from 'jslib/abstractions/api.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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';

View File

@@ -1,5 +1,5 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="twoStepOptionsTitle">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="twoStepOptionsTitle">{{'twoStepOptions' | i18n}}</h2>
@@ -7,17 +7,19 @@
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="list-group list-group-flush">
<a href="#" appStopClick *ngFor="let p of providers" (click)="choose(p)"
class="list-group-item list-group-item-action">
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="pull-right">
<h3>{{p.name}}</h3>
{{p.description}}
</a>
<a href="#" appStopClick class="list-group-item list-group-item-action" (click)="recover()">
<h3>{{'recoveryCodeTitle' | i18n}}</h3>
{{'recoveryCodeDesc' | i18n}}
</a>
<div class="modal-body">
<div class="list-group list-group-flush">
<a href="#" appStopClick *ngFor="let p of providers" (click)="choose(p)"
class="list-group-item list-group-item-action">
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="pull-right">
<h3>{{p.name}}</h3>
{{p.description}}
</a>
<a href="#" appStopClick class="list-group-item list-group-item-action" (click)="recover()">
<h3>{{'recoveryCodeTitle' | i18n}}</h3>
{{'recoveryCodeDesc' | i18n}}
</a>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>

View File

@@ -1,13 +1,13 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from 'jslib/abstractions/auth.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import {
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
} from 'jslib/angular/components/two-factor-options.component';
} from 'jslib-angular/components/two-factor-options.component';
@Component({
selector: 'app-two-factor-options',

View File

@@ -14,17 +14,17 @@ import { TwoFactorOptionsComponent } from './two-factor-options.component';
import { ModalComponent } from '../modal.component';
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
import { ApiService } from 'jslib/abstractions/api.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/components/two-factor.component';
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib-angular/components/two-factor.component';
@Component({
selector: 'app-two-factor',

View File

@@ -9,11 +9,11 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { VerifyEmailRequest } from 'jslib/models/request/verifyEmailRequest';
import { VerifyEmailRequest } from 'jslib-common/models/request/verifyEmailRequest';
@Component({
selector: 'app-verify-email-token',

View File

@@ -9,10 +9,10 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { VerifyDeleteRecoverRequest } from 'jslib/models/request/verifyDeleteRecoverRequest';
import { VerifyDeleteRecoverRequest } from 'jslib-common/models/request/verifyDeleteRecoverRequest';
@Component({
selector: 'app-verify-recover-delete',

View File

@@ -88,11 +88,11 @@ import { VaultComponent } from './vault/vault.component';
import { OrganizationGuardService } from './services/organization-guard.service';
import { OrganizationTypeGuardService } from './services/organization-type-guard.service';
import { AuthGuardService } from 'jslib/angular/services/auth-guard.service';
import { LockGuardService } from 'jslib/angular/services/lock-guard.service';
import { UnauthGuardService } from 'jslib/angular/services/unauth-guard.service';
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service';
import { LockGuardService } from 'jslib-angular/services/lock-guard.service';
import { UnauthGuardService } from 'jslib-angular/services/unauth-guard.service';
import { Permissions } from 'jslib/enums/permissions';
import { Permissions } from 'jslib-common/enums/permissions';
import { EmergencyAccessViewComponent } from './settings/emergency-access-view.component';
import { EmergencyAccessComponent } from './settings/emergency-access.component';
@@ -200,12 +200,12 @@ const routes: Routes = [
{
path: '',
component: EmergencyAccessComponent,
data: { titleId: 'emergencyAccess'},
data: { titleId: 'emergencyAccess' },
},
{
path: ':id',
component: EmergencyAccessViewComponent,
data: { titleId: 'emergencyAccess'},
data: { titleId: 'emergencyAccess' },
},
],
},
@@ -390,7 +390,7 @@ const routes: Routes = [
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'people',
permissions: [Permissions.ManageUsers],
permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword],
},
},
{

View File

@@ -1,5 +1,5 @@
import * as jq from 'jquery';
import Swal from 'sweetalert2/dist/sweetalert2.js';
import Swal from 'sweetalert2';
import {
BodyOutputType,
@@ -21,30 +21,30 @@ import {
Router,
} from '@angular/router';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { EventService } from 'jslib/abstractions/event.service';
import { FolderService } from 'jslib/abstractions/folder.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { NotificationsService } from 'jslib/abstractions/notifications.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { SettingsService } from 'jslib/abstractions/settings.service';
import { StateService } from 'jslib/abstractions/state.service';
import { SyncService } from 'jslib/abstractions/sync.service';
import { TokenService } from 'jslib/abstractions/token.service';
import { UserService } from 'jslib/abstractions/user.service';
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { NotificationsService } from 'jslib-common/abstractions/notifications.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { SettingsService } from 'jslib-common/abstractions/settings.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { TokenService } from 'jslib-common/abstractions/token.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { ConstantsService } from 'jslib/services/constants.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { RouterService } from './services/router.service';

View File

@@ -1,5 +1,3 @@
import 'core-js';
import { ToasterModule } from 'angular2-toaster';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
@@ -40,6 +38,9 @@ import { TwoFactorComponent } from './accounts/two-factor.component';
import { VerifyEmailTokenComponent } from './accounts/verify-email-token.component';
import { VerifyRecoverDeleteComponent } from './accounts/verify-recover-delete.component';
import { BulkConfirmComponent as OrgBulkConfirmComponent } from './organizations/manage/bulk/bulk-confirm.component';
import { BulkRemoveComponent as OrgBulkRemoveComponent } from './organizations/manage/bulk/bulk-remove.component';
import { BulkStatusComponent as OrgBulkStatusComponent } from './organizations/manage/bulk/bulk-status.component';
import {
CollectionAddEditComponent as OrgCollectionAddEditComponent,
} from './organizations/manage/collection-add-edit.component';
@@ -53,6 +54,7 @@ import { ManageComponent as OrgManageComponent } from './organizations/manage/ma
import { PeopleComponent as OrgPeopleComponent } from './organizations/manage/people.component';
import { PoliciesComponent as OrgPoliciesComponent } from './organizations/manage/policies.component';
import { PolicyEditComponent as OrgPolicyEditComponent } from './organizations/manage/policy-edit.component';
import { ResetPasswordComponent as OrgResetPasswordComponent } from './organizations/manage/reset-password.component';
import { UserAddEditComponent as OrgUserAddEditComponent } from './organizations/manage/user-add-edit.component';
import { UserConfirmComponent as OrgUserConfirmComponent } from './organizations/manage/user-confirm.component';
import { UserGroupsComponent as OrgUserGroupsComponent } from './organizations/manage/user-groups.component';
@@ -169,40 +171,47 @@ import { SendInfoComponent } from './vault/send-info.component';
import { ShareComponent } from './vault/share.component';
import { VaultComponent } from './vault/vault.component';
import { CalloutComponent } from 'jslib/angular/components/callout.component';
import { IconComponent } from 'jslib/angular/components/icon.component';
import { CalloutComponent } from 'jslib-angular/components/callout.component';
import { IconComponent } from 'jslib-angular/components/icon.component';
import { A11yTitleDirective } from 'jslib/angular/directives/a11y-title.directive';
import { ApiActionDirective } from 'jslib/angular/directives/api-action.directive';
import { AutofocusDirective } from 'jslib/angular/directives/autofocus.directive';
import { BlurClickDirective } from 'jslib/angular/directives/blur-click.directive';
import { BoxRowDirective } from 'jslib/angular/directives/box-row.directive';
import { FallbackSrcDirective } from 'jslib/angular/directives/fallback-src.directive';
import { InputVerbatimDirective } from 'jslib/angular/directives/input-verbatim.directive';
import { SelectCopyDirective } from 'jslib/angular/directives/select-copy.directive';
import { StopClickDirective } from 'jslib/angular/directives/stop-click.directive';
import { StopPropDirective } from 'jslib/angular/directives/stop-prop.directive';
import { TrueFalseValueDirective } from 'jslib/angular/directives/true-false-value.directive';
import { A11yTitleDirective } from 'jslib-angular/directives/a11y-title.directive';
import { ApiActionDirective } from 'jslib-angular/directives/api-action.directive';
import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive';
import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive';
import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive';
import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive';
import { InputVerbatimDirective } from 'jslib-angular/directives/input-verbatim.directive';
import { SelectCopyDirective } from 'jslib-angular/directives/select-copy.directive';
import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive';
import { StopPropDirective } from 'jslib-angular/directives/stop-prop.directive';
import { TrueFalseValueDirective } from 'jslib-angular/directives/true-false-value.directive';
import { ColorPasswordPipe } from 'jslib/angular/pipes/color-password.pipe';
import { I18nPipe } from 'jslib/angular/pipes/i18n.pipe';
import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
import { ColorPasswordPipe } from 'jslib-angular/pipes/color-password.pipe';
import { I18nPipe } from 'jslib-angular/pipes/i18n.pipe';
import { SearchCiphersPipe } from 'jslib-angular/pipes/search-ciphers.pipe';
import { SearchPipe } from 'jslib-angular/pipes/search.pipe';
import {
DatePipe,
registerLocaleData,
} from '@angular/common';
import localeBg from '@angular/common/locales/bg';
import localeCa from '@angular/common/locales/ca';
import localeCs from '@angular/common/locales/cs';
import localeDa from '@angular/common/locales/da';
import localeDe from '@angular/common/locales/de';
import localeEl from '@angular/common/locales/el';
import localeEnGb from '@angular/common/locales/en-GB';
import localeEnIn from '@angular/common/locales/en-IN';
import localeEo from '@angular/common/locales/eo';
import localeEs from '@angular/common/locales/es';
import localeEt from '@angular/common/locales/et';
import localeFi from '@angular/common/locales/fi';
import localeFr from '@angular/common/locales/fr';
import localeHe from '@angular/common/locales/he';
import localeHr from '@angular/common/locales/hr';
import localeHu from '@angular/common/locales/hu';
import localeId from '@angular/common/locales/id';
import localeIt from '@angular/common/locales/it';
import localeJa from '@angular/common/locales/ja';
import localeKo from '@angular/common/locales/ko';
@@ -213,23 +222,33 @@ import localeNl from '@angular/common/locales/nl';
import localePl from '@angular/common/locales/pl';
import localePtBr from '@angular/common/locales/pt';
import localePtPt from '@angular/common/locales/pt-PT';
import localeRo from '@angular/common/locales/ro';
import localeRu from '@angular/common/locales/ru';
import localeSk from '@angular/common/locales/sk';
import localeSr from '@angular/common/locales/sr';
import localeSv from '@angular/common/locales/sv';
import localeTr from '@angular/common/locales/tr';
import localeUk from '@angular/common/locales/uk';
import localeZhCn from '@angular/common/locales/zh-Hans';
import localeZhTw from '@angular/common/locales/zh-Hant';
registerLocaleData(localeCa, 'ca');
registerLocaleData(localeCs, 'cs');
registerLocaleData(localeBg, 'bg');
registerLocaleData(localeDa, 'da');
registerLocaleData(localeDe, 'de');
registerLocaleData(localeEl, 'el');
registerLocaleData(localeEnGb, 'en-GB');
registerLocaleData(localeEnIn, 'en-IN');
registerLocaleData(localeEs, 'es');
registerLocaleData(localeEt, 'et');
registerLocaleData(localeEo, 'eo');
registerLocaleData(localeFi, 'fi');
registerLocaleData(localeFr, 'fr');
registerLocaleData(localeHe, 'he');
registerLocaleData(localeHr, 'hr');
registerLocaleData(localeHu, 'hu');
registerLocaleData(localeId, 'id');
registerLocaleData(localeIt, 'it');
registerLocaleData(localeJa, 'ja');
registerLocaleData(localeKo, 'ko');
@@ -240,9 +259,12 @@ registerLocaleData(localeNl, 'nl');
registerLocaleData(localePl, 'pl');
registerLocaleData(localePtBr, 'pt-BR');
registerLocaleData(localePtPt, 'pt-PT');
registerLocaleData(localeRo, 'ro');
registerLocaleData(localeRu, 'ru');
registerLocaleData(localeSk, 'sk');
registerLocaleData(localeSr, 'sr');
registerLocaleData(localeSv, 'sv');
registerLocaleData(localeTr, 'tr');
registerLocaleData(localeUk, 'uk');
registerLocaleData(localeZhCn, 'zh-CN');
registerLocaleData(localeZhTw, 'zh-TW');
@@ -330,6 +352,9 @@ registerLocaleData(localeZhTw, 'zh-TW');
OrganizationPlansComponent,
OrganizationSubscriptionComponent,
OrgAttachmentsComponent,
OrgBulkStatusComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
@@ -348,6 +373,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
OrgPeopleComponent,
OrgPolicyEditComponent,
OrgPoliciesComponent,
OrgResetPasswordComponent,
OrgReusedPasswordsReportComponent,
OrgSettingComponent,
OrgToolsComponent,
@@ -429,12 +455,16 @@ registerLocaleData(localeZhTw, 'zh-TW');
ModalComponent,
OrgAddEditComponent,
OrgAttachmentsComponent,
OrgBulkStatusComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
OrgEntityUsersComponent,
OrgGroupAddEditComponent,
OrgPolicyEditComponent,
OrgResetPasswordComponent,
OrgUserAddEditComponent,
OrgUserConfirmComponent,
OrgUserGroupsComponent,
@@ -451,7 +481,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
TwoFactorYubiKeyComponent,
UpdateKeyComponent,
],
providers: [DatePipe],
providers: [DatePipe, SearchPipe],
bootstrap: [AppComponent],
})
export class AppModule { }

View File

@@ -6,10 +6,10 @@ import {
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { StateService } from 'jslib/abstractions/state.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { Utils } from 'jslib/misc/utils';
import { Utils } from 'jslib-common/misc/utils';
@Component({
selector: 'app-avatar',

View File

@@ -4,7 +4,7 @@ import {
OnChanges,
} from '@angular/core';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
@Component({
selector: 'app-password-strength',

View File

@@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { ModalComponent } from 'jslib/angular/components/modal.component';
@NgModule({
imports: [],
declarations: [
ModalComponent,
],
})
export class DummyModule {
}

View File

@@ -3,7 +3,7 @@ import {
OnInit,
} from '@angular/core';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
@Component({
selector: 'app-footer',

View File

@@ -4,7 +4,7 @@ import {
OnInit,
} from '@angular/core';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
@Component({
selector: 'app-frontend-layout',

View File

@@ -3,9 +3,9 @@ import {
OnInit,
} from '@angular/core';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { TokenService } from 'jslib/abstractions/token.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { TokenService } from 'jslib-common/abstractions/token.service';
@Component({
selector: 'app-navbar',

View File

@@ -7,14 +7,14 @@ import {
import { ActivatedRoute } from '@angular/router';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
import { ApiService } from 'jslib/abstractions/api.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { Organization } from 'jslib/models/domain/organization';
import { Organization } from 'jslib-common/models/domain/organization';
const BroadcasterSubscriptionId = 'OrganizationLayoutComponent';

View File

@@ -7,10 +7,10 @@ import {
ViewContainerRef,
} from '@angular/core';
import { ModalComponent as BaseModalComponent } from 'jslib/angular/components/modal.component';
import { Utils } from 'jslib/misc/utils';
import { ModalComponent as BaseModalComponent } from 'jslib-angular/components/modal.component';
import { Utils } from 'jslib-common/misc/utils';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
@Component({
selector: 'app-modal',

View File

@@ -0,0 +1,100 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="bulkTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="bulkTitle">
{{'confirmUsers' | i18n}}
</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="card-body text-center" *ngIf="loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<app-callout type="danger" *ngIf="filteredUsers.length <= 0">
{{'noSelectedUsersApplicable' | i18n}}
</app-callout>
<app-callout type="error" *ngIf="error">
{{error}}
</app-callout>
<ng-container *ngIf="!loading && !done">
<p>
{{'fingerprintEnsureIntegrityVerify' | i18n}}
<a href="https://help.bitwarden.com/article/fingerprint-phrase/" target="_blank" rel="noopener">
{{'learnMore' | i18n}}</a>
</p>
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{'user' | i18n}}</th>
<th>{{'fingerprint' | i18n}}</th>
</tr>
</thead>
<tr *ngFor="let user of filteredUsers">
<td width="30">
<app-avatar [data]="user.name || user.email" [email]="user.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
{{user.email}}
<small class="text-muted d-block" *ngIf="user.name">{{user.name}}</small>
</td>
<td>
{{fingerprints.get(user.id)}}
</td>
</tr>
<tr *ngFor="let user of excludedUsers">
<td width="30">
<app-avatar [data]="user.name || user.email" [email]="user.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
{{user.email}}
<small class="text-muted d-block" *ngIf="user.name">{{user.name}}</small>
</td>
<td>
{{'bulkFilteredMessage' | i18n}}
</td>
</tr>
</table>
</ng-container>
<ng-container *ngIf="!loading && done">
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{'user' | i18n}}</th>
<th>{{'status' | i18n}}</th>
</tr>
</thead>
<tr *ngFor="let user of filteredUsers">
<td width="30">
<app-avatar [data]="user.name || user.email" [email]="user.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
{{user.email}}
<small class="text-muted d-block" *ngIf="user.name">{{user.name}}</small>
</td>
<td *ngIf="statuses.has(user.id)">
{{statuses.get(user.id)}}
</td>
<td *ngIf="!statuses.has(user.id)">
{{'bulkFilteredMessage' | i18n}}
</td>
</tr>
</table>
</ng-container>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" *ngIf="!done" [disabled]="loading" (click)="submit()">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'confirm' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,95 @@
import {
Component,
Input,
OnInit,
} from '@angular/core';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { OrganizationUserBulkConfirmRequest } from 'jslib-common/models/request/organizationUserBulkConfirmRequest';
import { OrganizationUserBulkRequest } from 'jslib-common/models/request/organizationUserBulkRequest';
import { OrganizationUserUserDetailsResponse } from 'jslib-common/models/response/organizationUserResponse';
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType';
import { Utils } from 'jslib-common/misc/utils';
@Component({
selector: 'app-bulk-confirm',
templateUrl: 'bulk-confirm.component.html',
})
export class BulkConfirmComponent implements OnInit {
@Input() organizationId: string;
@Input() users: OrganizationUserUserDetailsResponse[];
excludedUsers: OrganizationUserUserDetailsResponse[];
filteredUsers: OrganizationUserUserDetailsResponse[];
publicKeys: Map<string, Uint8Array> = new Map();
fingerprints: Map<string, string> = new Map();
statuses: Map<string, string> = new Map();
loading: boolean = true;
done: boolean = false;
error: string;
constructor(private cryptoService: CryptoService, private apiService: ApiService,
private i18nService: I18nService) { }
async ngOnInit() {
this.excludedUsers = this.users.filter(user => user.status !== OrganizationUserStatusType.Accepted);
this.filteredUsers = this.users.filter(user => user.status === OrganizationUserStatusType.Accepted);
if (this.filteredUsers.length <= 0) {
this.done = true;
}
const request = new OrganizationUserBulkRequest(this.filteredUsers.map(user => user.id));
const response = await this.apiService.postOrganizationUsersPublicKey(this.organizationId, request);
for (const entry of response.data) {
const publicKey = Utils.fromB64ToArray(entry.key);
const fingerprint = await this.cryptoService.getFingerprint(entry.userId, publicKey.buffer);
if (fingerprint != null) {
this.publicKeys.set(entry.id, publicKey);
this.fingerprints.set(entry.id, fingerprint.join('-'));
}
}
this.loading = false;
}
async submit() {
this.loading = true;
try {
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
const userIdsWithKeys: any[] = [];
for (const user of this.filteredUsers) {
const publicKey = this.publicKeys.get(user.id);
if (publicKey == null) {
continue;
}
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
userIdsWithKeys.push({
id: user.id,
key: key.encryptedString,
});
}
const request = new OrganizationUserBulkConfirmRequest(userIdsWithKeys);
const response = await this.apiService.postOrganizationUserBulkConfirm(this.organizationId, request);
response.data.forEach(entry => {
const error = entry.error !== '' ? entry.error : this.i18nService.t('bulkConfirmMessage');
this.statuses.set(entry.id, error);
});
this.done = true;
} catch (e) {
this.error = e.message;
}
this.loading = false;
}
}

View File

@@ -0,0 +1,81 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="bulkTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="bulkTitle">
{{'removeUsers' | i18n}}
</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="card-body text-center" *ngIf="loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<app-callout type="danger" *ngIf="users.length <= 0">
{{'noSelectedUsersApplicable' | i18n}}
</app-callout>
<app-callout type="error" *ngIf="error">
{{error}}
</app-callout>
<ng-container *ngIf="!loading && !done">
<app-callout type="warning" *ngIf="users.length > 0 && !error">
{{'removeUsersWarning' | i18n}}
</app-callout>
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{'user' | i18n}}</th>
</tr>
</thead>
<tr *ngFor="let user of users">
<td width="30">
<app-avatar [data]="user.name || user.email" [email]="user.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
{{user.email}}
<small class="text-muted d-block" *ngIf="user.name">{{user.name}}</small>
</td>
</tr>
</table>
</ng-container>
<ng-container *ngIf="!loading && done">
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{'user' | i18n}}</th>
<th>{{'status' | i18n}}</th>
</tr>
</thead>
<tr *ngFor="let user of users">
<td width="30">
<app-avatar [data]="user.name || user.email" [email]="user.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
{{user.email}}
<small class="text-muted d-block" *ngIf="user.name">{{user.name}}</small>
</td>
<td *ngIf="statuses.has(user.id)">
{{statuses.get(user.id)}}
</td>
<td *ngIf="!statuses.has(user.id)">
{{'bulkFilteredMessage' | i18n}}
</td>
</tr>
</table>
</ng-container>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" *ngIf="!done && users.length > 0" [disabled]="loading" (click)="submit()">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'removeUsers' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,46 @@
import {
Component,
Input,
} from '@angular/core';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { OrganizationUserBulkRequest } from 'jslib-common/models/request/organizationUserBulkRequest';
import { OrganizationUserUserDetailsResponse } from 'jslib-common/models/response/organizationUserResponse';
@Component({
selector: 'app-bulk-remove',
templateUrl: 'bulk-remove.component.html',
})
export class BulkRemoveComponent {
@Input() organizationId: string;
@Input() users: OrganizationUserUserDetailsResponse[];
statuses: Map<string, string> = new Map();
loading: boolean = false;
done: boolean = false;
error: string;
constructor(private apiService: ApiService, private i18nService: I18nService) { }
async submit() {
this.loading = true;
try {
const request = new OrganizationUserBulkRequest(this.users.map(user => user.id));
const response = await this.apiService.deleteManyOrganizationUsers(this.organizationId, request);
response.data.forEach(entry => {
const error = entry.error !== '' ? entry.error : this.i18nService.t('bulkRemovedMessage');
this.statuses.set(entry.id, error);
});
this.done = true;
} catch (e) {
this.error = e.message;
}
this.loading = false;
}
}

View File

@@ -0,0 +1,47 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="bulkTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="bulkTitle">
{{'bulkConfirmStatus' | i18n}}
</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="card-body text-center" *ngIf="loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<table class="table table-hover table-list" *ngIf="!loading">
<thead>
<tr>
<th colspan="2">{{'user' | i18n}}</th>
<th>{{'status' | i18n}}</th>
</tr>
</thead>
<tr *ngFor="let item of users">
<td width="30">
<app-avatar [data]="item.user.name || item.user.email" [email]="item.user.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
{{item.user.email}}
<small class="text-muted d-block" *ngIf="item.user.name">{{item.user.name}}</small>
</td>
<td class="text-danger" *ngIf="item.error">
{{item.message}}
</td>
<td *ngIf="!item.error">
{{item.message}}
</td>
</tr>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
import { Component } from '@angular/core';
import { OrganizationUserUserDetailsResponse } from 'jslib-common/models/response/organizationUserResponse';
type BulkStatusEntry = {
user: OrganizationUserUserDetailsResponse,
error: boolean,
message: string,
};
@Component({
selector: 'app-bulk-status',
templateUrl: 'bulk-status.component.html',
})
export class BulkStatusComponent {
users: BulkStatusEntry[];
loading: boolean = false;
}

View File

@@ -1,5 +1,5 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="collectionAddEditTitle">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="modal-header">
<h2 class="modal-title" id="collectionAddEditTitle">{{title}}</h2>

View File

@@ -8,19 +8,19 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { EncString } from 'jslib/models/domain/encString';
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
import { CollectionRequest } from 'jslib/models/request/collectionRequest';
import { SelectionReadOnlyRequest } from 'jslib/models/request/selectionReadOnlyRequest';
import { GroupResponse } from 'jslib/models/response/groupResponse';
import { EncString } from 'jslib-common/models/domain/encString';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { CollectionRequest } from 'jslib-common/models/request/collectionRequest';
import { SelectionReadOnlyRequest } from 'jslib-common/models/request/selectionReadOnlyRequest';
import { GroupResponse } from 'jslib-common/models/response/groupResponse';
import { Utils } from 'jslib/misc/utils';
import { Utils } from 'jslib-common/misc/utils';
@Component({
selector: 'app-collection-add-edit',

View File

@@ -9,21 +9,21 @@ import { ActivatedRoute } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { CollectionData } from 'jslib/models/data/collectionData';
import { Collection } from 'jslib/models/domain/collection';
import { CollectionData } from 'jslib-common/models/data/collectionData';
import { Collection } from 'jslib-common/models/domain/collection';
import {
CollectionDetailsResponse,
CollectionResponse,
} from 'jslib/models/response/collectionResponse';
import { ListResponse } from 'jslib/models/response/listResponse';
import { CollectionView } from 'jslib/models/view/collectionView';
} from 'jslib-common/models/response/collectionResponse';
import { ListResponse } from 'jslib-common/models/response/listResponse';
import { CollectionView } from 'jslib-common/models/view/collectionView';
import { ModalComponent } from '../../modal.component';
import { CollectionAddEditComponent } from './collection-add-edit.component';

View File

@@ -1,5 +1,5 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="eventLogsTitle">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="eventLogsTitle">

View File

@@ -6,13 +6,13 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { EventService } from '../../services/event.service';
import { EventResponse } from 'jslib/models/response/eventResponse';
import { ListResponse } from 'jslib/models/response/listResponse';
import { EventResponse } from 'jslib-common/models/response/eventResponse';
import { ListResponse } from 'jslib-common/models/response/listResponse';
@Component({
selector: 'app-entity-events',
@@ -94,9 +94,9 @@ export class EntityEventsComponent implements OnInit {
} catch { }
this.continuationToken = response.continuationToken;
const events = response.data.map(r => {
const events = await Promise.all(response.data.map(async r => {
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
const eventInfo = this.eventService.getEventInfo(r);
const eventInfo = await this.eventService.getEventInfo(r);
const user = this.showUser && userId != null && this.orgUsersUserIdMap.has(userId) ?
this.orgUsersUserIdMap.get(userId) : null;
return {
@@ -110,7 +110,7 @@ export class EntityEventsComponent implements OnInit {
ip: r.ipAddress,
type: r.type,
};
});
}));
if (!clearExisting && this.events != null && this.events.length > 0) {
this.events = this.events.concat(events);

View File

@@ -1,5 +1,5 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="userAccessTitle">
<div class="modal-dialog 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>
<div class="modal-header">
<h2 class="modal-title" id="userAccessTitle">

View File

@@ -8,15 +8,15 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { OrganizationUserStatusType } from 'jslib/enums/organizationUserStatusType';
import { OrganizationUserType } from 'jslib/enums/organizationUserType';
import { SelectionReadOnlyRequest } from 'jslib/models/request/selectionReadOnlyRequest';
import { OrganizationUserUserDetailsResponse } from 'jslib/models/response/organizationUserResponse';
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType';
import { OrganizationUserType } from 'jslib-common/enums/organizationUserType';
import { SelectionReadOnlyRequest } from 'jslib-common/models/request/selectionReadOnlyRequest';
import { OrganizationUserUserDetailsResponse } from 'jslib-common/models/response/organizationUserResponse';
import { Utils } from 'jslib/misc/utils';
import { Utils } from 'jslib-common/misc/utils';
@Component({
selector: 'app-entity-users',

View File

@@ -4,17 +4,29 @@
<div class="form-inline">
<label class="sr-only" for="start">{{'startDate' | i18n}}</label>
<input type="datetime-local" class="form-control form-control-sm" id="start"
placeholder="{{'startDate' | i18n}}" [(ngModel)]="start" placeholder="YYYY-MM-DDTHH:MM">
placeholder="{{'startDate' | i18n}}" [(ngModel)]="start" placeholder="YYYY-MM-DDTHH:MM"
(change)="dirtyDates = true">
<span class="mx-2">-</span>
<label class="sr-only" for="end">{{'endDate' | i18n}}</label>
<input type="datetime-local" class="form-control form-control-sm" id="end"
placeholder="{{'endDate' | i18n}}" [(ngModel)]="end" placeholder="YYYY-MM-DDTHH:MM">
placeholder="{{'endDate' | i18n}}" [(ngModel)]="end" placeholder="YYYY-MM-DDTHH:MM"
(change)="dirtyDates = true">
</div>
<button #refreshBtn [appApiAction]="refreshPromise" type="button" class="btn btn-sm btn-outline-primary ml-3"
(click)="loadEvents(true)" [disabled]="loaded && refreshBtn.loading">
<i class="fa fa-refresh fa-fw" aria-hidden="true" [ngClass]="{'fa-spin': loaded && refreshBtn.loading}"></i>
{{'refresh' | i18n}}
</button>
<form #refreshForm [appApiAction]="refreshPromise" class="d-inline">
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="loadEvents(true)"
[disabled]="loaded && refreshForm.loading">
<i class="fa fa-refresh fa-fw" aria-hidden="true" [ngClass]="{'fa-spin': loaded && refreshForm.loading}"></i>
{{'refresh' | i18n}}
</button>
</form>
<form #exportForm [appApiAction]="exportPromise" class="d-inline">
<button type="button" class="btn btn-sm btn-outline-primary btn-submit manual ml-3"
[ngClass]="{loading:exportForm.loading}" (click)="exportEvents()"
[disabled]="loaded && exportForm.loading || dirtyDates">
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
<span>{{'export' | i18n}}</span>
</button>
</form>
</div>
</div>
<ng-container *ngIf="!loaded">

View File

@@ -6,15 +6,18 @@ import { ActivatedRoute, Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { ExportService } from 'jslib-common/abstractions/export.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { EventResponse } from 'jslib-common/models/response/eventResponse';
import { ListResponse } from 'jslib-common/models/response/listResponse';
import { EventView } from 'jslib-common/models/view/eventView';
import { EventService } from '../../services/event.service';
import { EventResponse } from 'jslib/models/response/eventResponse';
import { ListResponse } from 'jslib/models/response/listResponse';
@Component({
selector: 'app-org-events',
templateUrl: 'events.component.html',
@@ -23,19 +26,21 @@ export class EventsComponent implements OnInit {
loading = true;
loaded = false;
organizationId: string;
events: any[];
events: EventView[];
start: string;
end: string;
dirtyDates: boolean = true;
continuationToken: string;
refreshPromise: Promise<any>;
exportPromise: Promise<any>;
morePromise: Promise<any>;
private orgUsersUserIdMap = new Map<string, any>();
private orgUsersIdMap = new Map<string, any>();
constructor(private apiService: ApiService, private route: ActivatedRoute,
private eventService: EventService, private i18nService: I18nService,
private toasterService: ToasterService, private userService: UserService,
constructor(private apiService: ApiService, private route: ActivatedRoute, private eventService: EventService,
private i18nService: I18nService, private toasterService: ToasterService, private userService: UserService,
private exportService: ExportService, private platformUtilsService: PlatformUtilsService,
private router: Router) { }
async ngOnInit() {
@@ -64,41 +69,91 @@ export class EventsComponent implements OnInit {
this.loaded = true;
}
async loadEvents(clearExisting: boolean) {
if (this.refreshPromise != null || this.morePromise != null) {
return;
}
let dates: string[] = null;
try {
dates = this.eventService.formatDateFilters(this.start, this.end);
} catch (e) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidDateRange'));
async exportEvents() {
if (this.appApiPromiseUnfulfilled() || this.dirtyDates) {
return;
}
this.loading = true;
let response: ListResponse<EventResponse>;
const dates = this.parseDates();
if (dates == null) {
return;
}
try {
const promise = this.apiService.getEventsOrganization(this.organizationId, dates[0], dates[1],
clearExisting ? null : this.continuationToken);
this.exportPromise = this.export(dates[0], dates[1]);
await this.exportPromise;
} catch { }
this.exportPromise = null;
this.loading = false;
}
async loadEvents(clearExisting: boolean) {
if (this.appApiPromiseUnfulfilled()) {
return;
}
const dates = this.parseDates();
if (dates == null) {
return;
}
this.loading = true;
let events: EventView[] = [];
try {
const promise = this.loadAndParseEvents(dates[0], dates[1], clearExisting ? null : this.continuationToken);
if (clearExisting) {
this.refreshPromise = promise;
} else {
this.morePromise = promise;
}
response = await promise;
const result = await promise;
this.continuationToken = result.continuationToken;
events = result.events;
} catch { }
this.continuationToken = response.continuationToken;
const events = response.data.map(r => {
if (!clearExisting && this.events != null && this.events.length > 0) {
this.events = this.events.concat(events);
} else {
this.events = events;
}
this.dirtyDates = false;
this.loading = false;
this.morePromise = null;
this.refreshPromise = null;
}
private async export(start: string, end: string) {
let continuationToken = this.continuationToken;
let events = [].concat(this.events);
while (continuationToken != null) {
const result = await this.loadAndParseEvents(start, end, continuationToken);
continuationToken = result.continuationToken;
events = events.concat(result.events);
}
const data = await this.exportService.getEventExport(events);
const fileName = this.exportService.getFileName('org-events', 'csv');
this.platformUtilsService.saveFile(window, data, { type: 'text/plain' }, fileName);
}
private async loadAndParseEvents(startDate: string, endDate: string, continuationToken: string) {
const response = await this.apiService.getEventsOrganization(this.organizationId, startDate, endDate,
continuationToken);
const events = await Promise.all(response.data.map(async r => {
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
const eventInfo = this.eventService.getEventInfo(r);
const eventInfo = await this.eventService.getEventInfo(r);
const user = userId != null && this.orgUsersUserIdMap.has(userId) ?
this.orgUsersUserIdMap.get(userId) : null;
return {
return new EventView({
message: eventInfo.message,
humanReadableMessage: eventInfo.humanReadableMessage,
appIcon: eventInfo.appIcon,
appName: eventInfo.appName,
userId: userId,
@@ -107,17 +162,24 @@ export class EventsComponent implements OnInit {
date: r.date,
ip: r.ipAddress,
type: r.type,
};
});
});
}));
return { continuationToken: response.continuationToken, events: events };
}
if (!clearExisting && this.events != null && this.events.length > 0) {
this.events = this.events.concat(events);
} else {
this.events = events;
private parseDates() {
let dates: string[] = null;
try {
dates = this.eventService.formatDateFilters(this.start, this.end);
} catch (e) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidDateRange'));
return null;
}
return dates;
}
this.loading = false;
this.morePromise = null;
this.refreshPromise = null;
private appApiPromiseUnfulfilled() {
return this.refreshPromise != null || this.morePromise != null || this.exportPromise != null;
}
}

View File

@@ -1,5 +1,5 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="groupAddEditTitle">
<div class="modal-dialog 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>
<div class="modal-header">
<h2 class="modal-title" id="groupAddEditTitle">{{title}}</h2>

View File

@@ -8,17 +8,17 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CollectionData } from 'jslib/models/data/collectionData';
import { Collection } from 'jslib/models/domain/collection';
import { GroupRequest } from 'jslib/models/request/groupRequest';
import { SelectionReadOnlyRequest } from 'jslib/models/request/selectionReadOnlyRequest';
import { CollectionDetailsResponse } from 'jslib/models/response/collectionResponse';
import { CollectionView } from 'jslib/models/view/collectionView';
import { CollectionData } from 'jslib-common/models/data/collectionData';
import { Collection } from 'jslib-common/models/domain/collection';
import { GroupRequest } from 'jslib-common/models/request/groupRequest';
import { SelectionReadOnlyRequest } from 'jslib-common/models/request/selectionReadOnlyRequest';
import { CollectionDetailsResponse } from 'jslib-common/models/response/collectionResponse';
import { CollectionView } from 'jslib-common/models/view/collectionView';
@Component({
selector: 'app-group-add-edit',

View File

@@ -12,15 +12,15 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { GroupResponse } from 'jslib/models/response/groupResponse';
import { GroupResponse } from 'jslib-common/models/response/groupResponse';
import { Utils } from 'jslib/misc/utils';
import { Utils } from 'jslib-common/misc/utils';
import { ModalComponent } from '../../modal.component';
import { EntityUsersComponent } from './entity-users.component';

View File

@@ -4,9 +4,9 @@ import {
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { UserService } from 'jslib/abstractions/user.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { Organization } from 'jslib/models/domain/organization';
import { Organization } from 'jslib-common/models/domain/organization';
@Component({
selector: 'app-org-manage',

View File

@@ -25,6 +25,36 @@
<input type="search" class="form-control form-control-sm" id="search" placeholder="{{'search' | i18n}}"
[(ngModel)]="searchText">
</div>
<div class="dropdown ml-3" appListDropdown>
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" id="bulkActionsButton"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" appA11yTitle="{{'options' | i18n}}">
<i class="fa fa-cog" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
<button class="dropdown-item" appStopClick (click)="bulkReinvite()">
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i>
{{'reinviteSelected' | i18n}}
</button>
<button class="dropdown-item text-success" appStopClick (click)="bulkConfirm()"
*ngIf="showBulkConfirmUsers">
<i class="fa fa-fw fa-check" aria-hidden="true"></i>
{{'confirmSelected' | i18n}}
</button>
<button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
{{'remove' | i18n}}
</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item" appStopClick (click)="selectAll(true)">
<i class="fa fa-fw fa-check-square-o" aria-hidden="true"></i>
{{'selectAll' | i18n}}
</button>
<button class="dropdown-item" appStopClick (click)="selectAll(false)">
<i class="fa fa-fw fa-minus-square-o" aria-hidden="true"></i>
{{'unselectAll' | i18n}}
</button>
</div>
</div>
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="invite()">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
{{'inviteUser' | i18n}}
@@ -46,6 +76,9 @@
[infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
<tbody>
<tr *ngFor="let u of searchedUsers">
<td (click)="checkUser(u)" class="table-list-checkbox">
<input type="checkbox" [(ngModel)]="u.checked" appStopProp>
</td>
<td width="30">
<app-avatar [data]="u.name || u.email" [email]="u.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
@@ -63,6 +96,10 @@
<i class="fa fa-lock" title="{{'userUsingTwoStep' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'userUsingTwoStep' | i18n}}</span>
</ng-container>
<ng-container *ngIf="showEnrolledStatus(u)">
<i class="fa fa-key" title="{{'enrolledPasswordReset' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'enrolledPasswordReset' | i18n}}</span>
</ng-container>
</td>
<td>
<span *ngIf="u.type === organizationUserType.Owner">{{'owner' | i18n}}</span>
@@ -98,6 +135,11 @@
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>
{{'eventLogs' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick (click)="resetPassword(u)"
*ngIf="allowResetPassword(u)">
<i class="fa fa-fw fa-key" aria-hidden="true"></i>
{{'resetPassword' | i18n}}
</a>
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
{{'remove' | i18n}}
@@ -114,3 +156,7 @@
<ng-template #groupsTemplate></ng-template>
<ng-template #eventsTemplate></ng-template>
<ng-template #confirmTemplate></ng-template>
<ng-template #resetPasswordTemplate></ng-template>
<ng-template #bulkStatusTemplate></ng-template>
<ng-template #bulkConfirmTemplate></ng-template>
<ng-template #bulkRemoveTemplate></ng-template>

View File

@@ -5,6 +5,7 @@ import {
ViewChild,
ViewContainerRef,
} from '@angular/core';
import {
ActivatedRoute,
Router,
@@ -12,32 +13,47 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ValidationService } from 'jslib/angular/services/validation.service';
import { ConstantsService } from 'jslib/services/constants.service';
import { ValidationService } from 'jslib-angular/services/validation.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { OrganizationUserConfirmRequest } from 'jslib/models/request/organizationUserConfirmRequest';
import { OrganizationKeysRequest } from 'jslib-common/models/request/organizationKeysRequest';
import { OrganizationUserBulkRequest } from 'jslib-common/models/request/organizationUserBulkRequest';
import { OrganizationUserConfirmRequest } from 'jslib-common/models/request/organizationUserConfirmRequest';
import { OrganizationUserUserDetailsResponse } from 'jslib/models/response/organizationUserResponse';
import { ListResponse } from 'jslib-common/models/response/listResponse';
import { OrganizationUserBulkResponse } from 'jslib-common/models/response/organizationUserBulkResponse';
import { OrganizationUserUserDetailsResponse } from 'jslib-common/models/response/organizationUserResponse';
import { OrganizationUserStatusType } from 'jslib/enums/organizationUserStatusType';
import { OrganizationUserType } from 'jslib/enums/organizationUserType';
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType';
import { OrganizationUserType } from 'jslib-common/enums/organizationUserType';
import { PolicyType } from 'jslib-common/enums/policyType';
import { Utils } from 'jslib/misc/utils';
import { SearchPipe } from 'jslib-angular/pipes/search.pipe';
import { Utils } from 'jslib-common/misc/utils';
import { ModalComponent } from '../../modal.component';
import { BulkConfirmComponent } from './bulk/bulk-confirm.component';
import { BulkRemoveComponent } from './bulk/bulk-remove.component';
import { BulkStatusComponent } from './bulk/bulk-status.component';
import { EntityEventsComponent } from './entity-events.component';
import { ResetPasswordComponent } from './reset-password.component';
import { UserAddEditComponent } from './user-add-edit.component';
import { UserConfirmComponent } from './user-confirm.component';
import { UserGroupsComponent } from './user-groups.component';
const MaxCheckedCount = 500;
@Component({
selector: 'app-org-people',
templateUrl: 'people.component.html',
@@ -47,6 +63,10 @@ export class PeopleComponent implements OnInit {
@ViewChild('groupsTemplate', { read: ViewContainerRef, static: true }) groupsModalRef: ViewContainerRef;
@ViewChild('eventsTemplate', { read: ViewContainerRef, static: true }) eventsModalRef: ViewContainerRef;
@ViewChild('confirmTemplate', { read: ViewContainerRef, static: true }) confirmModalRef: ViewContainerRef;
@ViewChild('resetPasswordTemplate', { read: ViewContainerRef, static: true }) resetPasswordModalRef: 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;
loading = true;
organizationId: string;
@@ -60,6 +80,11 @@ export class PeopleComponent implements OnInit {
actionPromise: Promise<any>;
accessEvents = false;
accessGroups = false;
canResetPassword = false; // User permission (admin/custom)
orgUseResetPassword = false; // Org plan ability
orgHasKeys = false; // Org public/private keys
orgResetPasswordPolicyEnabled = false;
callingUserType: OrganizationUserType = null;
protected didScroll = false;
protected pageSize = 100;
@@ -73,7 +98,8 @@ export class PeopleComponent implements OnInit {
private platformUtilsService: PlatformUtilsService, private toasterService: ToasterService,
private cryptoService: CryptoService, private userService: UserService, private router: Router,
private storageService: StorageService, private searchService: SearchService,
private validationService: ValidationService) { }
private validationService: ValidationService, private policyService: PolicyService,
private searchPipe: SearchPipe, private syncService: SyncService) { }
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
@@ -85,6 +111,25 @@ export class PeopleComponent implements OnInit {
}
this.accessEvents = organization.useEvents;
this.accessGroups = organization.useGroups;
this.canResetPassword = organization.canManageUsersPassword;
this.orgUseResetPassword = organization.useResetPassword;
this.callingUserType = organization.type;
this.orgHasKeys = organization.hasPublicAndPrivateKeys;
// Backfill pub/priv key if necessary
if (this.canResetPassword && !this.orgHasKeys) {
const orgShareKey = await this.cryptoService.getOrgKey(this.organizationId);
const orgKeys = await this.cryptoService.makeKeyPair(orgShareKey);
const request = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
const response = await this.apiService.postOrganizationKeys(this.organizationId, request);
if (response != null) {
this.orgHasKeys = response.publicKey != null && response.privateKey != null;
await this.syncService.fullSync(true); // Replace oganizations with new data
} else {
throw new Error(this.i18nService.t('resetPasswordOrgKeysError'));
}
}
await this.load();
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
@@ -115,9 +160,38 @@ export class PeopleComponent implements OnInit {
}
});
this.filter(this.status);
const policies = await this.policyService.getAll(PolicyType.ResetPassword);
this.orgResetPasswordPolicyEnabled = policies.some(p => p.organizationId === this.organizationId && p.enabled);
this.loading = false;
}
allowResetPassword(orgUser: OrganizationUserUserDetailsResponse): boolean {
// Hierarchy check
let callingUserHasPermission = false;
switch (this.callingUserType) {
case OrganizationUserType.Owner:
callingUserHasPermission = true;
break;
case OrganizationUserType.Admin:
callingUserHasPermission = orgUser.type !== OrganizationUserType.Owner;
break;
case OrganizationUserType.Custom:
callingUserHasPermission = orgUser.type !== OrganizationUserType.Owner
&& orgUser.type !== OrganizationUserType.Admin;
break;
}
// Final
return this.canResetPassword && callingUserHasPermission && this.orgUseResetPassword && this.orgHasKeys
&& orgUser.resetPasswordEnrolled && this.orgResetPasswordPolicyEnabled
&& orgUser.status === OrganizationUserStatusType.Confirmed;
}
showEnrolledStatus(orgUser: OrganizationUserUserDetailsResponse): boolean {
return this.orgUseResetPassword && orgUser.resetPasswordEnrolled && this.orgResetPasswordPolicyEnabled;
}
filter(status: OrganizationUserStatusType) {
this.status = status;
if (this.status != null) {
@@ -125,6 +199,8 @@ export class PeopleComponent implements OnInit {
} else {
this.users = this.allUsers;
}
// Reset checkbox selecton
this.selectAll(false);
this.resetPaging();
}
@@ -168,6 +244,10 @@ export class PeopleComponent implements OnInit {
this.confirmedCount > 0 && this.confirmedCount < 3 && this.acceptedCount > 0;
}
get showBulkConfirmUsers(): boolean {
return this.acceptedCount > 0;
}
edit(user: OrganizationUserUserDetailsResponse) {
if (this.modal != null) {
this.modal.close();
@@ -229,23 +309,101 @@ export class PeopleComponent implements OnInit {
return false;
}
this.actionPromise = this.apiService.deleteOrganizationUser(this.organizationId, user.id);
try {
await this.apiService.deleteOrganizationUser(this.organizationId, user.id);
await this.actionPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('removedUserId', user.name || user.email));
this.removeUser(user);
} catch { }
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
async reinvite(user: OrganizationUserUserDetailsResponse) {
if (this.actionPromise != null) {
return;
}
this.actionPromise = this.apiService.postOrganizationUserReinvite(this.organizationId, user.id);
await this.actionPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenReinvited', user.name || user.email));
try {
await this.actionPromise;
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenReinvited', user.name || user.email));
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
async bulkRemove() {
if (this.actionPromise != null) {
return;
}
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.bulkRemoveModalRef.createComponent(factory).instance;
const childComponent = this.modal.show(BulkRemoveComponent, this.bulkRemoveModalRef);
childComponent.organizationId = this.organizationId;
childComponent.users = this.getCheckedUsers();
this.modal.onClosed.subscribe(async () => {
await this.load();
this.modal = null;
});
}
async bulkReinvite() {
if (this.actionPromise != null) {
return;
}
const users = this.getCheckedUsers();
const filteredUsers = users.filter(u => u.status === OrganizationUserStatusType.Invited);
if (filteredUsers.length <= 0) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('noSelectedUsersApplicable'));
return;
}
try {
const request = new OrganizationUserBulkRequest(filteredUsers.map(user => user.id));
const response = this.apiService.postManyOrganizationUserReinvite(this.organizationId, request);
this.showBulkStatus(users, filteredUsers, response, this.i18nService.t('bulkReinviteMessage'));
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
async bulkConfirm() {
if (this.actionPromise != null) {
return;
}
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.bulkConfirmModalRef.createComponent(factory).instance;
const childComponent = this.modal.show(BulkConfirmComponent, this.bulkConfirmModalRef);
childComponent.organizationId = this.organizationId;
childComponent.users = this.getCheckedUsers();
this.modal.onClosed.subscribe(async () => {
await this.load();
this.modal = null;
});
}
async confirm(user: OrganizationUserUserDetailsResponse) {
function updateUser(self: PeopleComponent) {
user.status = OrganizationUserStatusType.Confirmed;
@@ -358,6 +516,101 @@ export class PeopleComponent implements OnInit {
return !searching && this.users && this.users.length > this.pageSize;
}
async resetPassword(user: OrganizationUserUserDetailsResponse) {
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.resetPasswordModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<ResetPasswordComponent>(
ResetPasswordComponent, this.resetPasswordModalRef);
childComponent.name = user != null ? user.name || user.email : null;
childComponent.email = user != null ? user.email : null;
childComponent.organizationId = this.organizationId;
childComponent.id = user != null ? user.id : null;
childComponent.onPasswordReset.subscribe(() => {
this.modal.close();
this.load();
});
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
}
checkUser(user: OrganizationUserUserDetailsResponse, select?: boolean) {
(user as any).checked = select == null ? !(user as any).checked : select;
}
selectAll(select: boolean) {
if (select) {
this.selectAll(false);
}
const filteredUsers = this.searchPipe.transform(this.users, this.searchText, 'name', 'email', 'id');
const selectCount = select && filteredUsers.length > MaxCheckedCount
? MaxCheckedCount
: filteredUsers.length;
for (let i = 0; i < selectCount; i++) {
this.checkUser(filteredUsers[i], select);
}
}
private async showBulkStatus(users: OrganizationUserUserDetailsResponse[], filteredUsers: OrganizationUserUserDetailsResponse[],
request: Promise<ListResponse<OrganizationUserBulkResponse>>, successfullMessage: string) {
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.bulkStatusModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<BulkStatusComponent>(
BulkStatusComponent, this.bulkStatusModalRef);
childComponent.loading = true;
// Workaround to handle closing the modal shortly after it has been opened
let close = false;
this.modal.onShown.subscribe(() => {
if (close) {
this.modal.close();
}
});
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
try {
const response = await request;
if (this.modal) {
const keyedErrors: any = response.data.filter(r => r.error !== '').reduce((a, x) => ({ ...a, [x.id]: x.error }), {});
const keyedFilteredUsers: any = filteredUsers.reduce((a, x) => ({ ...a, [x.id]: x }), {});
childComponent.users = users.map(user => {
let message = keyedErrors[user.id] ?? successfullMessage;
if (!keyedFilteredUsers.hasOwnProperty(user.id)) {
message = this.i18nService.t('bulkFilteredMessage');
}
return {
user: user,
error: keyedErrors.hasOwnProperty(user.id),
message: message,
};
});
childComponent.loading = false;
}
} catch {
close = true;
if (this.modal) {
this.modal.close();
}
}
}
private async doConfirmation(user: OrganizationUserUserDetailsResponse, publicKey: Uint8Array) {
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
@@ -391,4 +644,8 @@ export class PeopleComponent implements OnInit {
}
}
}
private getCheckedUsers() {
return this.users.filter(u => (u as any).checked);
}
}

View File

@@ -10,15 +10,15 @@ import {
Router,
} from '@angular/router';
import { PolicyType } from 'jslib/enums/policyType';
import { PolicyType } from 'jslib-common/enums/policyType';
import { EnvironmentService } from 'jslib/abstractions';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { UserService } from 'jslib/abstractions/user.service';
import { EnvironmentService } from 'jslib-common/abstractions';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { PolicyResponse } from 'jslib/models/response/policyResponse';
import { PolicyResponse } from 'jslib-common/models/response/policyResponse';
import { ModalComponent } from '../../modal.component';
@@ -115,6 +115,12 @@ export class PoliciesComponent implements OnInit {
type: PolicyType.SendOptions,
enabled: false,
display: true,
}, {
name: this.i18nService.t('resetPasswordPolicy'),
description: this.i18nService.t('resetPasswordPolicyDescription'),
type: PolicyType.ResetPassword,
enabled: false,
display: organization.useResetPassword,
},
];
await this.load();

View File

@@ -1,5 +1,5 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="policiesEditTitle">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="modal-header">
<h2 class="modal-title" id="policiesEditTitle">{{'editPolicy' | i18n}} - {{name}}</h2>
@@ -38,6 +38,9 @@
<app-callout type="warning" *ngIf="type === policyType.SendOptions">
{{'sendOptionsExemption' | i18n}}
</app-callout>
<app-callout type="warning" *ngIf="type === policyType.ResetPassword">
{{'resetPasswordPolicyWarning' | i18n}}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [(ngModel)]="enabled"
@@ -153,19 +156,33 @@
<ng-container *ngIf="type === policyType.SendOptions">
<h3 class="mt-4">{{'options' | i18n}}</h3>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="sendDisableHideEmail" [(ngModel)]="sendDisableHideEmail"
name="SendDisableHideEmail">
<input class="form-check-input" type="checkbox" id="sendDisableHideEmail"
[(ngModel)]="sendDisableHideEmail" name="SendDisableHideEmail">
<label class="form-check-label" for="sendDisableHideEmail">{{'disableHideEmail' | i18n}}</label>
</div>
</ng-container>
<ng-container *ngIf="type === policyType.ResetPassword">
<h3 class="mt-4">{{'resetPasswordPolicyAutoEnroll' | i18n}}</h3>
<p>{{'resetPasswordPolicyAutoEnrollDescription' | i18n}}</p>
<app-callout type="warning">
{{'resetPasswordPolicyAutoEnrollWarning' | i18n}}
</app-callout>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="autoEnrollEnabled"
[(ngModel)]="resetPasswordAutoEnroll" name="AutoEnrollEnabled">
<label class="form-check-label"
for="autoEnrollEnabled">{{'resetPasswordPolicyAutoEnrollCheckbox' | i18n }}</label>
</div>
</ng-container>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'save' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary"
data-dismiss="modal">{{'cancel' | i18n}}</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{'cancel' | i18n}}
</button>
</div>
</form>
</div>

View File

@@ -8,14 +8,14 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PolicyType } from 'jslib/enums/policyType';
import { PolicyType } from 'jslib-common/enums/policyType';
import { PolicyRequest } from 'jslib/models/request/policyRequest';
import { PolicyRequest } from 'jslib-common/models/request/policyRequest';
import { PolicyResponse } from 'jslib/models/response/policyResponse';
import { PolicyResponse } from 'jslib-common/models/response/policyResponse';
@Component({
selector: 'app-policy-edit',
@@ -60,6 +60,9 @@ export class PolicyEditComponent implements OnInit {
// Send options
sendDisableHideEmail?: boolean;
// Reset Password
resetPasswordAutoEnroll?: boolean;
private policy: PolicyResponse;
constructor(private apiService: ApiService, private i18nService: I18nService,
@@ -116,6 +119,9 @@ export class PolicyEditComponent implements OnInit {
case PolicyType.SendOptions:
this.sendDisableHideEmail = this.policy.data.disableHideEmail;
break;
case PolicyType.ResetPassword:
this.resetPasswordAutoEnroll = this.policy.data.autoEnrollEnabled;
break;
default:
break;
}
@@ -167,6 +173,11 @@ export class PolicyEditComponent implements OnInit {
disableHideEmail: this.sendDisableHideEmail,
};
break;
case PolicyType.ResetPassword:
request.data = {
autoEnrollEnabled: this.resetPasswordAutoEnroll,
};
break;
default:
break;
}

View File

@@ -0,0 +1,78 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="resetPasswordTitle">
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<h2 class="modal-title" id="resetPasswordTitle">
{{'resetPassword' | i18n}}
<small class="text-muted" *ngIf="name">{{name}}</small>
</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<app-callout type="warning">{{'resetPasswordLoggedOutWarning' | i18n: loggedOutWarningName}}
</app-callout>
<app-callout type="info" *ngIf="enforcedPolicyOptions">
{{'resetPasswordMasterPasswordPolicyInEffect' | i18n}}
<ul class="mb-0">
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
{{'policyInEffectMinComplexity' | i18n : getPasswordScoreAlertDisplay()}}
</li>
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
{{'policyInEffectMinLength' | i18n : enforcedPolicyOptions?.minLength.toString()}}
</li>
<li *ngIf="enforcedPolicyOptions?.requireUpper">
{{'policyInEffectUppercase' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireLower">
{{'policyInEffectLowercase' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireNumbers">
{{'policyInEffectNumbers' | i18n}}</li>
<li *ngIf="enforcedPolicyOptions?.requireSpecial">
{{'policyInEffectSpecial' | i18n : '!@#$%^&*'}}</li>
</ul>
</app-callout>
<div class="row">
<div class="col form-group">
<div class="d-flex">
<label for="newPassword">{{'newPassword' | i18n}}</label>
<div class="ml-auto d-flex">
<a href="#" class="d-block mr-2 fa-icon-above-input" appStopClick
appA11yTitle="{{'generatePassword' | i18n}}" (click)="generatePassword()">
<i class="fa fa-lg fa-fw fa-refresh" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="input-group mb-1">
<input id="newPassword" class="form-control text-monospace" appAutofocus
type="{{showPassword ? 'text' : 'password'}}" name="NewPassword"
[(ngModel)]="newPassword" required appInputVerbatim autocomplete="new-password"
(input)="updatePasswordStrength()">
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
<button type="button" class="btn btn-outline-secondary"
appA11yTitle="{{'copyPassword' | i18n}}" (click)="copy(newPassword)">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button>
</div>
</div>
<app-password-strength [score]="masterPasswordScore" [showText]="true">
</app-password-strength>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'save' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'cancel' |
i18n}}</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,191 @@
import {
AfterViewInit,
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { EncString } from 'jslib-common/models/domain/encString';
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { OrganizationUserResetPasswordRequest } from 'jslib-common/models/request/organizationUserResetPasswordRequest';
@Component({
selector: 'app-reset-password',
templateUrl: 'reset-password.component.html',
})
export class ResetPasswordComponent implements OnInit {
@Input() name: string;
@Input() email: string;
@Input() id: string;
@Input() organizationId: string;
@Output() onPasswordReset = new EventEmitter();
enforcedPolicyOptions: MasterPasswordPolicyOptions;
newPassword: string = null;
showPassword: boolean = false;
masterPasswordScore: number;
formPromise: Promise<any>;
private newPasswordStrengthTimeout: any;
constructor(private apiService: ApiService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private passwordGenerationService: PasswordGenerationService,
private policyService: PolicyService, private cryptoService: CryptoService) { }
async ngOnInit() {
// Get Enforced Policy Options
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
}
get loggedOutWarningName() {
return this.name != null ? this.name : this.i18nService.t('thisUser');
}
getPasswordScoreAlertDisplay() {
if (this.enforcedPolicyOptions == null) {
return '';
}
let str: string;
switch (this.enforcedPolicyOptions.minComplexity) {
case 4:
str = this.i18nService.t('strong');
break;
case 3:
str = this.i18nService.t('good');
break;
default:
str = this.i18nService.t('weak');
break;
}
return str + ' (' + this.enforcedPolicyOptions.minComplexity + ')';
}
async generatePassword() {
const options = (await this.passwordGenerationService.getOptions())[0];
this.newPassword = await this.passwordGenerationService.generatePassword(options);
this.updatePasswordStrength();
}
togglePassword() {
this.showPassword = !this.showPassword;
document.getElementById('newPassword').focus();
}
copy(value: string) {
if (value == null) {
return;
}
this.platformUtilsService.copyToClipboard(value, { window: window });
this.platformUtilsService.showToast('info', null,
this.i18nService.t('valueCopied', this.i18nService.t('password')));
}
async submit() {
// Validation
if (this.newPassword == null || this.newPassword === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
return false;
}
if (this.newPassword.length < 8) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassLength'));
return false;
}
if (this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(this.masterPasswordScore, this.newPassword,
this.enforcedPolicyOptions)) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
return;
}
if (this.masterPasswordScore < 3) {
const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'),
this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'),
'warning');
if (!result) {
return false;
}
}
// Get user Information (kdf type, kdf iterations, resetPasswordKey, private key) and change password
try {
this.formPromise = this.apiService.getOrganizationUserResetPasswordDetails(this.organizationId, this.id)
.then(async response => {
if (response == null) {
throw new Error(this.i18nService.t('resetPasswordDetailsError'));
}
const kdfType = response.kdf;
const kdfIterations = response.kdfIterations;
const resetPasswordKey = response.resetPasswordKey;
const encryptedPrivateKey = response.encryptedPrivateKey;
// Decrypt Organization's encrypted Private Key with org key
const orgSymKey = await this.cryptoService.getOrgKey(this.organizationId);
const decPrivateKey = await this.cryptoService.decryptToBytes(new EncString(encryptedPrivateKey), orgSymKey);
// Decrypt User's Reset Password Key to get EncKey
const decValue = await this.cryptoService.rsaDecrypt(resetPasswordKey, decPrivateKey);
const userEncKey = new SymmetricCryptoKey(decValue);
// Create new key and hash new password
const newKey = await this.cryptoService.makeKey(this.newPassword, this.email.trim().toLowerCase(),
kdfType, kdfIterations);
const newPasswordHash = await this.cryptoService.hashPassword(this.newPassword, newKey);
// Create new encKey for the User
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
// Create request
const request = new OrganizationUserResetPasswordRequest();
request.key = newEncKey[1].encryptedString;
request.newMasterPasswordHash = newPasswordHash;
// Change user's password
return this.apiService.putOrganizationUserResetPassword(this.organizationId, this.id, request);
});
await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('resetPasswordSuccess'));
this.onPasswordReset.emit();
} catch { }
}
updatePasswordStrength() {
if (this.newPasswordStrengthTimeout != null) {
clearTimeout(this.newPasswordStrengthTimeout);
}
this.newPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(this.newPassword,
this.getPasswordStrengthUserInput());
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}, 300);
}
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf('@');
if (atPosition > -1) {
userInput = userInput.concat(this.email.substr(0, atPosition).trim().toLowerCase().split(/[^A-Za-z0-9]/));
}
if (this.name != null && this.name !== '') {
userInput = userInput.concat(this.name.trim().toLowerCase().split(' '));
}
return userInput;
}
}

View File

@@ -1,5 +1,5 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="userAddEditTitle">
<div class="modal-dialog 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>
<div class="modal-header">
<h2 class="modal-title" id="userAddEditTitle">
@@ -172,7 +172,8 @@
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageUsers"
id="manageUsers" [(ngModel)]="permissions.manageUsers">
id="manageUsers" [(ngModel)]="permissions.manageUsers"
(change)="handleDependentPermissions()">
<label class="form-check-label font-weight-normal" for="manageUsers">
{{'manageUsers' | i18n}}
</label>
@@ -181,7 +182,8 @@
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageResetPassword"
id="manageResetPassword" [(ngModel)]="permissions.manageResetPassword">
id="manageResetPassword" [(ngModel)]="permissions.manageResetPassword"
(change)="handleDependentPermissions()">
<label class="form-check-label font-weight-normal" for="manageResetPassword">
{{'manageResetPassword' | i18n}}
</label>

View File

@@ -8,21 +8,21 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CollectionData } from 'jslib/models/data/collectionData';
import { Collection } from 'jslib/models/domain/collection';
import { OrganizationUserInviteRequest } from 'jslib/models/request/organizationUserInviteRequest';
import { OrganizationUserUpdateRequest } from 'jslib/models/request/organizationUserUpdateRequest';
import { SelectionReadOnlyRequest } from 'jslib/models/request/selectionReadOnlyRequest';
import { CollectionDetailsResponse } from 'jslib/models/response/collectionResponse';
import { CollectionView } from 'jslib/models/view/collectionView';
import { CollectionData } from 'jslib-common/models/data/collectionData';
import { Collection } from 'jslib-common/models/domain/collection';
import { OrganizationUserInviteRequest } from 'jslib-common/models/request/organizationUserInviteRequest';
import { OrganizationUserUpdateRequest } from 'jslib-common/models/request/organizationUserUpdateRequest';
import { SelectionReadOnlyRequest } from 'jslib-common/models/request/selectionReadOnlyRequest';
import { CollectionDetailsResponse } from 'jslib-common/models/response/collectionResponse';
import { CollectionView } from 'jslib-common/models/view/collectionView';
import { OrganizationUserType } from 'jslib/enums/organizationUserType';
import { PermissionsApi } from 'jslib/models/api/permissionsApi';
import { OrganizationUserType } from 'jslib-common/enums/organizationUserType';
import { PermissionsApi } from 'jslib-common/models/api/permissionsApi';
@Component({
selector: 'app-user-add-edit',
@@ -143,6 +143,15 @@ export class UserAddEditComponent implements OnInit {
return p;
}
handleDependentPermissions() {
// Manage Password Reset must have Manage Users enabled
if (this.permissions.manageResetPassword && !this.permissions.manageUsers) {
this.permissions.manageUsers = true;
(document.getElementById('manageUsers') as HTMLInputElement).checked = true;
this.platformUtilsService.showToast('info', null, this.i18nService.t('resetPasswordManageUsers'));
}
}
async submit() {
let collections: SelectionReadOnlyRequest[] = null;
if (this.access !== 'all') {

View File

@@ -1,5 +1,5 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-header">
<h2 class="modal-title" id="confirmUserTitle">

View File

@@ -6,13 +6,13 @@ import {
Output,
} from '@angular/core';
import { ConstantsService } from 'jslib/services/constants.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { Utils } from 'jslib/misc/utils';
import { Utils } from 'jslib-common/misc/utils';
@Component({
selector: 'app-user-confirm',

View File

@@ -1,5 +1,5 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="groupAccessTitle">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<h2 class="modal-title" id="groupAccessTitle">

View File

@@ -8,13 +8,13 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { OrganizationUserUpdateGroupsRequest } from 'jslib/models/request/organizationUserUpdateGroupsRequest';
import { GroupResponse } from 'jslib/models/response/groupResponse';
import { OrganizationUserUpdateGroupsRequest } from 'jslib-common/models/request/organizationUserUpdateGroupsRequest';
import { GroupResponse } from 'jslib-common/models/response/groupResponse';
import { Utils } from 'jslib/misc/utils';
import { Utils } from 'jslib-common/misc/utils';
@Component({
selector: 'app-user-groups',

View File

@@ -4,22 +4,28 @@ import {
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SyncService } from 'jslib/abstractions/sync.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { OrganizationUpdateRequest } from 'jslib/models/request/organizationUpdateRequest';
import { OrganizationResponse } from 'jslib/models/response/organizationResponse';
import { OrganizationKeysRequest } from 'jslib-common/models/request/organizationKeysRequest';
import { OrganizationUpdateRequest } from 'jslib-common/models/request/organizationUpdateRequest';
import { OrganizationResponse } from 'jslib-common/models/response/organizationResponse';
import { ModalComponent } from '../../modal.component';
import { ApiKeyComponent } from '../../settings/api-key.component';
import { PurgeVaultComponent } from '../../settings/purge-vault.component';
import { TaxInfoComponent } from '../../settings/tax-info.component';
import { DeleteOrganizationComponent } from './delete-organization.component';
@Component({
@@ -46,7 +52,8 @@ export class AccountComponent {
constructor(private componentFactoryResolver: ComponentFactoryResolver,
private apiService: ApiService, private i18nService: I18nService,
private toasterService: ToasterService, private route: ActivatedRoute,
private syncService: SyncService, private platformUtilsService: PlatformUtilsService) { }
private syncService: SyncService, private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService) { }
async ngOnInit() {
this.selfHosted = this.platformUtilsService.isSelfHost();
@@ -67,6 +74,14 @@ export class AccountComponent {
request.businessName = this.org.businessName;
request.billingEmail = this.org.billingEmail;
request.identifier = this.org.identifier;
// Backfill pub/priv key if necessary
if (!this.org.hasPublicAndPrivateKeys) {
const orgShareKey = await this.cryptoService.getOrgKey(this.organizationId);
const orgKeys = await this.cryptoService.makeKeyPair(orgShareKey);
request.keys = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
}
this.formPromise = this.apiService.putOrganization(this.organizationId, request).then(() => {
return this.syncService.fullSync(true);
});

View File

@@ -13,10 +13,10 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { SeatRequest } from 'jslib/models/request/seatRequest';
import { SeatRequest } from 'jslib-common/models/request/seatRequest';
import { PaymentComponent } from '../../settings/payment.component';

View File

@@ -5,11 +5,11 @@ import {
Output,
} from '@angular/core';
import { ApiService } from 'jslib/abstractions/api.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PlanType } from 'jslib/enums/planType';
import { ProductType } from 'jslib/enums/productType';
import { PlanType } from 'jslib-common/enums/planType';
import { ProductType } from 'jslib-common/enums/productType';
@Component({
selector: 'app-change-plan',

View File

@@ -1,5 +1,5 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="deleteOrganizationTitle">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="modal-header">
<h2 class="modal-title" id="deleteOrganizationTitle">{{'deleteOrganization' | i18n}}</h2>

View File

@@ -3,11 +3,11 @@ import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordVerificationRequest } from 'jslib/models/request/passwordVerificationRequest';
import { PasswordVerificationRequest } from 'jslib-common/models/request/passwordVerificationRequest';
@Component({
selector: 'app-delete-organization',

View File

@@ -5,8 +5,8 @@ import {
Output,
} from '@angular/core';
import { ApiService } from 'jslib/abstractions/api.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
@Component({
selector: 'app-download-license',

View File

@@ -6,9 +6,9 @@ import { ActivatedRoute } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserBillingComponent } from '../../settings/user-billing.component';

View File

@@ -6,14 +6,14 @@ import { ActivatedRoute } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { OrganizationSubscriptionResponse } from 'jslib/models/response/organizationSubscriptionResponse';
import { OrganizationSubscriptionResponse } from 'jslib-common/models/response/organizationSubscriptionResponse';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PlanType } from 'jslib/enums/planType';
import { PlanType } from 'jslib-common/enums/planType';
@Component({
selector: 'app-org-subscription',

View File

@@ -1,8 +1,8 @@
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { UserService } from 'jslib/abstractions/user.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserService } from 'jslib-common/abstractions/user.service';
@Component({
selector: 'app-org-settings',

View File

@@ -4,12 +4,12 @@ import {
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ApiService } from 'jslib/abstractions/api.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
import { TwoFactorDuoComponent } from '../../settings/two-factor-duo.component';
import { TwoFactorSetupComponent as BaseTwoFactorSetupComponent } from '../../settings/two-factor-setup.component';

View File

@@ -1,15 +1,15 @@
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { EventService } from 'jslib/abstractions/event.service';
import { ExportService } from 'jslib/abstractions/export.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { ExportService } from 'jslib-common/abstractions/export.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ExportComponent as BaseExportComponent } from '../../tools/export.component';
import { EventType } from 'jslib/enums/eventType';
import { EventType } from 'jslib-common/enums/eventType';
@Component({
selector: 'app-org-export',

View File

@@ -4,17 +4,17 @@ import {
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AuditService } from 'jslib/abstractions/audit.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { UserService } from 'jslib/abstractions/user.service';
import { AuditService } from 'jslib-common/abstractions/audit.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import {
ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent,
} from '../../tools/exposed-passwords-report.component';
import { Cipher } from 'jslib/models/domain/cipher';
import { CipherView } from 'jslib/models/view/cipherView';
import { Cipher } from 'jslib-common/models/domain/cipher';
import { CipherView } from 'jslib-common/models/view/cipherView';
@Component({
selector: 'app-exposed-passwords-report',

View File

@@ -6,10 +6,10 @@ import {
import { ToasterService } from 'angular2-toaster';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { ImportService } from 'jslib/abstractions/import.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { UserService } from 'jslib/abstractions/user.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { ImportService } from 'jslib-common/abstractions/import.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { ImportComponent as BaseImportComponent } from '../../tools/import.component';

View File

@@ -4,15 +4,15 @@ import {
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { UserService } from 'jslib/abstractions/user.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import {
InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent,
} from '../../tools/inactive-two-factor-report.component';
import { CipherView } from 'jslib/models/view/cipherView';
import { CipherView } from 'jslib-common/models/view/cipherView';
@Component({
selector: 'app-inactive-two-factor-report',

View File

@@ -4,13 +4,13 @@ import {
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { UserService } from 'jslib/abstractions/user.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { Cipher } from 'jslib/models/domain/cipher';
import { Cipher } from 'jslib-common/models/domain/cipher';
import { CipherView } from 'jslib/models/view/cipherView';
import { CipherView } from 'jslib-common/models/view/cipherView';
import {
ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent,

View File

@@ -1,10 +1,10 @@
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Organization } from 'jslib/models/domain/organization';
import { Organization } from 'jslib-common/models/domain/organization';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { UserService } from 'jslib/abstractions/user.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { UserService } from 'jslib-common/abstractions/user.service';
@Component({
selector: 'app-org-tools',

View File

@@ -4,15 +4,15 @@ import {
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { UserService } from 'jslib/abstractions/user.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import {
UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponent,
} from '../../tools/unsecured-websites-report.component';
import { CipherView } from 'jslib/models/view/cipherView';
import { CipherView } from 'jslib-common/models/view/cipherView';
@Component({
selector: 'app-unsecured-websites-report',

View File

@@ -4,14 +4,14 @@ import {
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { UserService } from 'jslib/abstractions/user.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { Cipher } from 'jslib/models/domain/cipher';
import { Cipher } from 'jslib-common/models/domain/cipher';
import { CipherView } from 'jslib/models/view/cipherView';
import { CipherView } from 'jslib-common/models/view/cipherView';
import {
WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent,

View File

@@ -1,25 +1,25 @@
import { Component } from '@angular/core';
import { ApiService } from 'jslib/abstractions/api.service';
import { AuditService } from 'jslib/abstractions/audit.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { EventService } from 'jslib/abstractions/event.service';
import { FolderService } from 'jslib/abstractions/folder.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { StateService } from 'jslib/abstractions/state.service';
import { TotpService } from 'jslib/abstractions/totp.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuditService } from 'jslib-common/abstractions/audit.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.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 { TotpService } from 'jslib-common/abstractions/totp.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { CipherData } from 'jslib/models/data/cipherData';
import { Cipher } from 'jslib/models/domain/cipher';
import { Organization } from 'jslib/models/domain/organization';
import { CipherCreateRequest } from 'jslib/models/request/cipherCreateRequest';
import { CipherRequest } from 'jslib/models/request/cipherRequest';
import { CipherData } from 'jslib-common/models/data/cipherData';
import { Cipher } from 'jslib-common/models/domain/cipher';
import { Organization } from 'jslib-common/models/domain/organization';
import { CipherCreateRequest } from 'jslib-common/models/request/cipherCreateRequest';
import { CipherRequest } from 'jslib-common/models/request/cipherRequest';
import { AddEditComponent as BaseAddEditComponent } from '../../vault/add-edit.component';

View File

@@ -1,17 +1,17 @@
import { Component } from '@angular/core';
import { ApiService } from 'jslib/abstractions/api.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { CipherData } from 'jslib/models/data/cipherData';
import { Cipher } from 'jslib/models/domain/cipher';
import { Organization } from 'jslib/models/domain/organization';
import { CipherData } from 'jslib-common/models/data/cipherData';
import { Cipher } from 'jslib-common/models/domain/cipher';
import { Organization } from 'jslib-common/models/domain/organization';
import { AttachmentView } from 'jslib/models/view/attachmentView';
import { AttachmentView } from 'jslib-common/models/view/attachmentView';
import { AttachmentsComponent as BaseAttachmentsComponent } from '../../vault/attachments.component';

View File

@@ -6,17 +6,18 @@ import {
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { EventService } from 'jslib/abstractions/event.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { TotpService } from 'jslib/abstractions/totp.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { TotpService } from 'jslib-common/abstractions/totp.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { Organization } from 'jslib/models/domain/organization';
import { CipherView } from 'jslib/models/view/cipherView';
import { Organization } from 'jslib-common/models/domain/organization';
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CiphersComponent as BaseCiphersComponent } from '../../vault/ciphers.component';
@@ -34,18 +35,19 @@ export class CiphersComponent extends BaseCiphersComponent {
constructor(searchService: SearchService, toasterService: ToasterService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, cipherService: CipherService,
private apiService: ApiService, eventService: EventService, totpService: TotpService, userService: UserService) {
private apiService: ApiService, eventService: EventService, totpService: TotpService,
userService: UserService, passwordRepromptService: PasswordRepromptService) {
super(searchService, toasterService, i18nService, platformUtilsService, cipherService,
eventService, totpService, userService);
eventService, totpService, userService, passwordRepromptService);
}
async load(filter: (cipher: CipherView) => boolean = null) {
if (!this.organization.canManageAllCollections) {
await super.load(filter, this.deleted);
return;
if (this.organization.canManageAllCollections) {
this.accessEvents = this.organization.useEvents;
this.allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id);
} else {
this.allCiphers = (await this.cipherService.getAllDecrypted()).filter(c => c.organizationId === this.organization.id);
}
this.accessEvents = this.organization.useEvents;
this.allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id);
await this.searchService.indexCiphers(this.organization.id, this.allCiphers);
await this.applyFilter(filter);
this.loaded = true;
@@ -61,7 +63,7 @@ export class CiphersComponent extends BaseCiphersComponent {
}
async search(timeout: number = null) {
super.search(timeout, this.allCiphers);
await super.search(timeout, this.allCiphers);
}
events(c: CipherView) {
this.onEventsClicked.emit(c);

View File

@@ -1,15 +1,15 @@
import { Component } from '@angular/core';
import { ApiService } from 'jslib/abstractions/api.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CipherData } from 'jslib/models/data/cipherData';
import { Cipher } from 'jslib/models/domain/cipher';
import { Organization } from 'jslib/models/domain/organization';
import { CipherCollectionsRequest } from 'jslib/models/request/cipherCollectionsRequest';
import { CipherData } from 'jslib-common/models/data/cipherData';
import { Cipher } from 'jslib-common/models/domain/cipher';
import { Organization } from 'jslib-common/models/domain/organization';
import { CipherCollectionsRequest } from 'jslib-common/models/request/cipherCollectionsRequest';
import { CollectionsComponent as BaseCollectionsComponent } from '../../vault/collections.component';

View File

@@ -1,17 +1,17 @@
import { Component } from '@angular/core';
import { ApiService } from 'jslib/abstractions/api.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { FolderService } from 'jslib/abstractions/folder.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { CollectionData } from 'jslib/models/data/collectionData';
import { Collection } from 'jslib/models/domain/collection';
import { Organization } from 'jslib/models/domain/organization';
import { CollectionDetailsResponse } from 'jslib/models/response/collectionResponse';
import { CollectionView } from 'jslib/models/view/collectionView';
import { CollectionData } from 'jslib-common/models/data/collectionData';
import { Collection } from 'jslib-common/models/domain/collection';
import { Organization } from 'jslib-common/models/domain/organization';
import { CollectionDetailsResponse } from 'jslib-common/models/response/collectionResponse';
import { CollectionView } from 'jslib-common/models/view/collectionView';
import { GroupingsComponent as BaseGroupingsComponent } from '../../vault/groupings.component';

View File

@@ -13,18 +13,18 @@ import {
Router,
} from '@angular/router';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SyncService } from 'jslib/abstractions/sync.service';
import { UserService } from 'jslib/abstractions/user.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
import { Organization } from 'jslib/models/domain/organization';
import { CipherView } from 'jslib/models/view/cipherView';
import { Organization } from 'jslib-common/models/domain/organization';
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherType } from 'jslib/enums/cipherType';
import { CipherType } from 'jslib-common/enums/cipherType';
import { ModalComponent } from '../../modal.component';

View File

@@ -1,6 +1,5 @@
/* tslint:disable */
import 'core-js/es6';
import 'core-js/es7/reflect';
import 'core-js/stable';
require('zone.js/dist/zone');
// IE11 fix, ref: https://github.com/angular/angular/issues/24769

View File

@@ -5,24 +5,24 @@ import {
import { ActivatedRoute } from '@angular/router';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { Utils } from 'jslib/misc/utils';
import { Utils } from 'jslib-common/misc/utils';
import { SendAccess } from 'jslib/models/domain/sendAccess';
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
import { SendAccess } from 'jslib-common/models/domain/sendAccess';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { SendAccessView } from 'jslib/models/view/sendAccessView';
import { SendAccessView } from 'jslib-common/models/view/sendAccessView';
import { SendType } from 'jslib/enums/sendType';
import { SendAccessRequest } from 'jslib/models/request/sendAccessRequest';
import { ErrorResponse } from 'jslib/models/response/errorResponse';
import { SendType } from 'jslib-common/enums/sendType';
import { SendAccessRequest } from 'jslib-common/models/request/sendAccessRequest';
import { ErrorResponse } from 'jslib-common/models/response/errorResponse';
import { SendAccessResponse } from 'jslib/models/response/sendAccessResponse';
import { SendAccessResponse } from 'jslib-common/models/response/sendAccessResponse';
@Component({
selector: 'app-send-access',

View File

@@ -1,5 +1,5 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="sendAddEditTitle">
<div class="modal-dialog 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
autocomplete="off">
<div class="modal-header">
@@ -232,10 +232,8 @@
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary disabled" disabled=true *ngIf="disableSend">
<span>{{'save' | i18n}}</span>
</button>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading" *ngIf="!disableSend">
<button type="submit" class="btn btn-primary btn-submit manual" [ngClass]="{loading:form.loading}"
[disabled]="form.loading || disableSend">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'save' | i18n}}</span>
</button>

View File

@@ -2,15 +2,15 @@ import { DatePipe } from '@angular/common';
import { Component } from '@angular/core';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { SendService } from 'jslib/abstractions/send.service';
import { UserService } from 'jslib/abstractions/user.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/send/add-edit.component';
import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/components/send/add-edit.component';
@Component({
selector: 'app-send-add-edit',
@@ -25,9 +25,11 @@ export class AddEditComponent extends BaseAddEditComponent {
messagingService, policyService);
}
copyLinkToClipboard(link: string) {
async copyLinkToClipboard(link: string): Promise<void | boolean> {
// Copy function on web depends on the modal being open or not. Since this event occurs during a transition
// of the modal closing we need to add a small delay to make sure state of the DOM is consistent.
window.setTimeout(() => super.copyLinkToClipboard(link), 500);
return new Promise(resolve => {
window.setTimeout(() => resolve(super.copyLinkToClipboard(link)), 500);
});
}
}

View File

@@ -6,23 +6,23 @@ import {
ViewContainerRef,
} from '@angular/core';
import { SendView } from 'jslib/models/view/sendView';
import { SendView } from 'jslib-common/models/view/sendView';
import { SendComponent as BaseSendComponent } from 'jslib/angular/components/send/send.component';
import { SendComponent as BaseSendComponent } from 'jslib-angular/components/send/send.component';
import { AddEditComponent } from './add-edit.component';
import { ModalComponent } from '../modal.component';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { SendService } from 'jslib/abstractions/send.service';
import { UserService } from 'jslib/abstractions/user.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
const BroadcasterSubscriptionId = 'SendComponent';

View File

@@ -1,15 +1,18 @@
import { Injectable } from '@angular/core';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { DeviceType } from 'jslib/enums/deviceType';
import { EventType } from 'jslib/enums/eventType';
import { DeviceType } from 'jslib-common/enums/deviceType';
import { EventType } from 'jslib-common/enums/eventType';
import { PolicyType } from 'jslib-common/enums/policyType';
import { EventResponse } from 'jslib-common/models/response/eventResponse';
import { EventResponse } from 'jslib/models/response/eventResponse';
@Injectable()
export class EventService {
constructor(private i18nService: I18nService) { }
constructor(private i18nService: I18nService, private policyService: PolicyService) { }
getDefaultDateFilters() {
const d = new Date();
@@ -28,146 +31,184 @@ export class EventService {
return [start.toISOString(), end.toISOString()];
}
getEventInfo(ev: EventResponse, options = new EventOptions()): EventInfo {
async getEventInfo(ev: EventResponse, options = new EventOptions()): Promise<EventInfo> {
const appInfo = this.getAppInfo(ev.deviceType);
const { message, humanReadableMessage } = await this.getEventMessage(ev, options);
return {
message: this.getEventMessage(ev, options),
message: message,
humanReadableMessage: humanReadableMessage,
appIcon: appInfo[0],
appName: appInfo[1],
};
}
private getEventMessage(ev: EventResponse, options: EventOptions) {
private async getEventMessage(ev: EventResponse, options: EventOptions) {
let msg = '';
let humanReadableMsg = '';
switch (ev.type) {
// User
case EventType.User_LoggedIn:
msg = this.i18nService.t('loggedIn');
msg = humanReadableMsg = this.i18nService.t('loggedIn');
break;
case EventType.User_ChangedPassword:
msg = this.i18nService.t('changedPassword');
msg = humanReadableMsg = this.i18nService.t('changedPassword');
break;
case EventType.User_Updated2fa:
msg = this.i18nService.t('enabledUpdated2fa');
msg = humanReadableMsg = this.i18nService.t('enabledUpdated2fa');
break;
case EventType.User_Disabled2fa:
msg = this.i18nService.t('disabled2fa');
msg = humanReadableMsg = this.i18nService.t('disabled2fa');
break;
case EventType.User_Recovered2fa:
msg = this.i18nService.t('recovered2fa');
msg = humanReadableMsg = this.i18nService.t('recovered2fa');
break;
case EventType.User_FailedLogIn:
msg = this.i18nService.t('failedLogin');
msg = humanReadableMsg = this.i18nService.t('failedLogin');
break;
case EventType.User_FailedLogIn2fa:
msg = this.i18nService.t('failedLogin2fa');
msg = humanReadableMsg = this.i18nService.t('failedLogin2fa');
break;
case EventType.User_ClientExportedVault:
msg = this.i18nService.t('exportedVault');
msg = humanReadableMsg = this.i18nService.t('exportedVault');
break;
// Cipher
case EventType.Cipher_Created:
msg = this.i18nService.t('createdItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('createdItemId', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_Updated:
msg = this.i18nService.t('editedItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('editedItemId', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_Deleted:
msg = this.i18nService.t('permanentlyDeletedItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('permanentlyDeletedItemId', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_SoftDeleted:
msg = this.i18nService.t('deletedItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('deletedItemId', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_Restored:
msg = this.i18nService.t('restoredItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('restoredItemId', this.formatCipherId(ev, options));
break;
case EventType.Cipher_AttachmentCreated:
msg = this.i18nService.t('createdAttachmentForItem', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('createdAttachmentForItem', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_AttachmentDeleted:
msg = this.i18nService.t('deletedAttachmentForItem', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('deletedAttachmentForItem', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_Shared:
msg = this.i18nService.t('sharedItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('sharedItemId', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_ClientViewed:
msg = this.i18nService.t('viewedItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('viewedItemId', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_ClientToggledPasswordVisible:
msg = this.i18nService.t('viewedPasswordItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('viewedPasswordItemId', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_ClientToggledHiddenFieldVisible:
msg = this.i18nService.t('viewedHiddenFieldItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('viewedHiddenFieldItemId', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_ClientToggledCardCodeVisible:
msg = this.i18nService.t('viewedSecurityCodeItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('viewedSecurityCodeItemId', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_ClientCopiedHiddenField:
msg = this.i18nService.t('copiedHiddenFieldItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('copiedHiddenFieldItemId', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_ClientCopiedPassword:
msg = this.i18nService.t('copiedPasswordItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('copiedPasswordItemId', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_ClientCopiedCardCode:
msg = this.i18nService.t('copiedSecurityCodeItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('copiedSecurityCodeItemId', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_ClientAutofilled:
msg = this.i18nService.t('autofilledItemId', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('autofilledItemId', this.getShortId(ev.cipherId));
break;
case EventType.Cipher_UpdatedCollections:
msg = this.i18nService.t('editedCollectionsForItem', this.formatCipherId(ev, options));
humanReadableMsg = this.i18nService.t('editedCollectionsForItem', this.getShortId(ev.cipherId));
break;
// Collection
case EventType.Collection_Created:
msg = this.i18nService.t('createdCollectionId', this.formatCollectionId(ev));
humanReadableMsg = this.i18nService.t('createdCollectionId', this.getShortId(ev.collectionId));
break;
case EventType.Collection_Updated:
msg = this.i18nService.t('editedCollectionId', this.formatCollectionId(ev));
humanReadableMsg = this.i18nService.t('editedCollectionId', this.getShortId(ev.collectionId));
break;
case EventType.Collection_Deleted:
msg = this.i18nService.t('deletedCollectionId', this.formatCollectionId(ev));
humanReadableMsg = this.i18nService.t('deletedCollectionId', this.getShortId(ev.collectionId));
break;
// Group
case EventType.Group_Created:
msg = this.i18nService.t('createdGroupId', this.formatGroupId(ev));
humanReadableMsg = this.i18nService.t('createdGroupId', this.getShortId(ev.groupId));
break;
case EventType.Group_Updated:
msg = this.i18nService.t('editedGroupId', this.formatGroupId(ev));
humanReadableMsg = this.i18nService.t('editedGroupId', this.getShortId(ev.groupId));
break;
case EventType.Group_Deleted:
msg = this.i18nService.t('deletedGroupId', this.formatGroupId(ev));
humanReadableMsg = this.i18nService.t('deletedGroupId', this.getShortId(ev.groupId));
break;
// Org user
case EventType.OrganizationUser_Invited:
msg = this.i18nService.t('invitedUserId', this.formatOrgUserId(ev));
humanReadableMsg = this.i18nService.t('invitedUserId', this.getShortId(ev.organizationUserId));
break;
case EventType.OrganizationUser_Confirmed:
msg = this.i18nService.t('confirmedUserId', this.formatOrgUserId(ev));
humanReadableMsg = this.i18nService.t('confirmedUserId', this.getShortId(ev.organizationUserId));
break;
case EventType.OrganizationUser_Updated:
msg = this.i18nService.t('editedUserId', this.formatOrgUserId(ev));
humanReadableMsg = this.i18nService.t('editedUserId', this.getShortId(ev.organizationUserId));
break;
case EventType.OrganizationUser_Removed:
msg = this.i18nService.t('removedUserId', this.formatOrgUserId(ev));
humanReadableMsg = this.i18nService.t('removedUserId', this.getShortId(ev.organizationUserId));
break;
case EventType.OrganizationUser_UpdatedGroups:
msg = this.i18nService.t('editedGroupsForUser', this.formatOrgUserId(ev));
humanReadableMsg = this.i18nService.t('editedGroupsForUser', this.getShortId(ev.organizationUserId));
break;
case EventType.OrganizationUser_UnlinkedSso:
msg = this.i18nService.t('unlinkedSsoUser', this.formatOrgUserId(ev));
humanReadableMsg = this.i18nService.t('unlinkedSsoUser', this.getShortId(ev.organizationUserId));
break;
case EventType.OrganizationUser_ResetPassword_Enroll:
msg = this.i18nService.t('eventEnrollPasswordReset', this.formatOrgUserId(ev));
humanReadableMsg = this.i18nService.t('eventEnrollPasswordReset', this.getShortId(ev.organizationUserId));
break;
case EventType.OrganizationUser_ResetPassword_Withdraw:
msg = this.i18nService.t('eventWithdrawPasswordReset', this.formatOrgUserId(ev));
humanReadableMsg = this.i18nService.t('eventWithdrawPasswordReset', this.getShortId(ev.organizationUserId));
break;
case EventType.OrganizationUser_AdminResetPassword:
msg = this.i18nService.t('eventAdminPasswordReset', this.formatOrgUserId(ev));
humanReadableMsg = this.i18nService.t('eventAdminPasswordReset', this.getShortId(ev.organizationUserId));
break;
// Org
case EventType.Organization_Updated:
msg = this.i18nService.t('editedOrgSettings');
msg = humanReadableMsg = this.i18nService.t('editedOrgSettings');
break;
case EventType.Organization_PurgedVault:
msg = this.i18nService.t('purgedOrganizationVault');
msg = humanReadableMsg = this.i18nService.t('purgedOrganizationVault');
break;
/*
case EventType.Organization_ClientExportedVault:
@@ -176,13 +217,25 @@ export class EventService {
*/
// Policies
case EventType.Policy_Updated:
msg = this.i18nService.t('modifiedPolicy', this.formatPolicyId(ev));
msg = this.i18nService.t('modifiedPolicyId', this.formatPolicyId(ev));
const policies = await this.policyService.getAll();
const policy = policies.filter(p => p.id === ev.policyId)[0];
let p1 = this.getShortId(ev.policyId);
if (policy !== null) {
p1 = PolicyType[policy.type];
}
humanReadableMsg = this.i18nService.t('modifiedPolicyId', p1);
break;
default:
break;
}
return msg === '' ? null : msg;
return {
message: msg === '' ? null : msg,
humanReadableMessage: humanReadableMsg === '' ? null : humanReadableMsg,
};
}
private getAppInfo(deviceType: DeviceType): [string, string] {
@@ -299,6 +352,7 @@ export class EventService {
export class EventInfo {
message: string;
humanReadableMessage: string;
appIcon: string;
appName: string;
}

View File

@@ -7,8 +7,8 @@ import {
import { ToasterService } from 'angular2-toaster';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { UserService } from 'jslib/abstractions/user.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { UserService } from 'jslib-common/abstractions/user.service';
@Injectable()
export class OrganizationGuardService implements CanActivate {

View File

@@ -5,9 +5,9 @@ import {
Router,
} from '@angular/router';
import { UserService } from 'jslib/abstractions/user.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { Permissions } from 'jslib/enums/permissions';
import { Permissions } from 'jslib-common/enums/permissions';
@Injectable()
export class OrganizationTypeGuardService implements CanActivate {
@@ -27,7 +27,8 @@ export class OrganizationTypeGuardService implements CanActivate {
(permissions.indexOf(Permissions.ManageGroups) !== -1 && org.canManageGroups) ||
(permissions.indexOf(Permissions.ManageOrganization) !== -1 && org.isOwner) ||
(permissions.indexOf(Permissions.ManagePolicies) !== -1 && org.canManagePolicies) ||
(permissions.indexOf(Permissions.ManageUsers) !== -1 && org.canManageUsers)
(permissions.indexOf(Permissions.ManageUsers) !== -1 && org.canManageUsers) ||
(permissions.indexOf(Permissions.ManageUsersPassword) !== -1 && org.canManageUsersPassword)
) {
return true;
}

View File

@@ -6,7 +6,7 @@ import {
Router,
} from '@angular/router';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
@Injectable()
export class RouterService {

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