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

Compare commits

...

127 Commits

Author SHA1 Message Date
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
vachan-maker
a1b77dc9ef Update change-password.component.html (#941) 2021-04-22 09:34:16 -04:00
Thomas Rittson
9b38095aba Use jslib unauthGuard, add lockGuard support (#939)
* Use jslib unauthGuard, add lockGuard support

* bump jslib
2021-04-22 18:13:43 +10:00
Thomas Rittson
714a574028 Do not show free trial wording if upgrading plan (#940) 2021-04-22 07:15:37 +10:00
Matt Gibson
3e8194a3f7 Update jslib (#942)
* Update jslib

* stub out new platformUtilsService method

* Throw not implemented

* Update jslib

* This interface method was reverted
2021-04-21 14:20:20 -05:00
Joseph Flinn
6e4782784c New client configuration pattern (#937)
* adding in initial config files

* working config files

* updating the client config pattern to default to dev instead of prod

* updating the npm script commands and docs

* Adding a helpful debugging log for the webpack build

* adding in more supporting documentation for running against production

* updating README.md and removing the unneeded ENV var
2021-04-21 11:29:33 -07:00
Joseph Flinn
ad40c38ca3 Build pipeline fix (#938)
* updating the build pipeline for the QA env

* changing the docker build context

* removed commented code

* moving commands to single line

* fixing typo

* removing unneeded build script
2021-04-20 13:27:09 -07:00
Thomas Rittson
68f2de171e Don't use tokenService to manage emailVerified (#932)
* update send add edit component dependencies
2021-04-15 16:28:21 +02:00
Oscar Hinton
a9ef011cf3 Remove dead code (#930)
* Remove last remnants of old analytics code
2021-04-14 23:43:40 +02:00
Matt Gibson
53bd9a3b14 Update jslib (#931) 2021-04-14 11:09:01 -05:00
Matt Gibson
1466933e2c update jslib (#928) 2021-04-12 11:36:00 -05:00
Kyle Spearrin
be515dc6a6 update types 2021-04-12 11:05:33 -04:00
Kyle Spearrin
83859230cd update some libs mentioned in npm audit 2021-04-09 12:52:41 -04:00
Kyle Spearrin
ec7a40df0b update jslib 2021-04-09 12:20:10 -04:00
Thomas Rittson
aba98ba944 Require user to verify email to use file Send (#915)
* Require user to verify email to use file Send

* bump jslib
2021-04-09 12:19:16 -04:00
Kyle Spearrin
f6e8c7152e npm audit fixes 2021-04-09 12:14:44 -04:00
Kyle Spearrin
3a1fd5ba83 update lib refs 2021-04-09 12:12:11 -04:00
Snyk bot
e43f816a8d fix: upgrade big-integer from 1.6.36 to 1.6.48 (#906)
Snyk has created this PR to upgrade big-integer from 1.6.36 to 1.6.48.

See this package in npm:
https://www.npmjs.com/package/big-integer

See this project in Snyk:
https://app.snyk.io/org/kspearrin/project/b65e7285-af54-4885-9245-af6852f0790a?utm_source=github&utm_medium=upgrade-pr
2021-04-09 12:08:04 -04:00
Snyk bot
9e61dbd512 fix: upgrade @microsoft/signalr-protocol-msgpack from 3.1.0 to 3.1.13 (#907)
Snyk has created this PR to upgrade @microsoft/signalr-protocol-msgpack from 3.1.0 to 3.1.13.

See this package in npm:
https://www.npmjs.com/package/@microsoft/signalr-protocol-msgpack

See this project in Snyk:
https://app.snyk.io/org/kspearrin/project/b65e7285-af54-4885-9245-af6852f0790a?utm_source=github&utm_medium=upgrade-pr

Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
2021-04-09 12:07:32 -04:00
Snyk bot
8734d028d3 fix: upgrade @microsoft/signalr from 3.1.0 to 3.1.13 (#905)
Snyk has created this PR to upgrade @microsoft/signalr from 3.1.0 to 3.1.13.

See this package in npm:
https://www.npmjs.com/package/@microsoft/signalr

See this project in Snyk:
https://app.snyk.io/org/kspearrin/project/b65e7285-af54-4885-9245-af6852f0790a?utm_source=github&utm_medium=upgrade-pr
2021-04-09 12:06:40 -04:00
Oscar Hinton
58850821ba Add proxies for notifications and portal. Simplify environment service (#919) 2021-04-09 09:57:25 +02:00
Oscar Hinton
f81ad479dd Resolve org user confirm not showing error when hide fingerprint is enabled (#918) 2021-04-09 00:46:16 +02:00
Vincent Salucci
133d30ba97 [Reset Password] Rotate encryption key (#916)
* [Reset Password] Rotate encryption key

* Added logic for updating reset password key only if necessary

* Updated user's resetPasswordKey for each confirmed organization on key rotation
2021-04-08 11:09:06 -05:00
Oscar Hinton
09fba343fc [Chore] Bump jslib (#917)
* Bump jslib
2021-04-07 20:42:57 +02:00
Vincent Salucci
ba3d4a2390 [Reset Password] Manage Reset Password permission (#902)
* [Reset Password] Manage Reset Password permission

* Update formatting

* Update jslib (f4f00b1 -> 97ece68)
2021-04-06 22:40:17 -05:00
Matt Gibson
b1c59f3dc1 Create index of cipher orgs and use advanced search with org ciphers (#903)
* Create index of cipher orgs and use advanced search with org ciphers

* Update jslib
2021-04-05 18:55:20 -05:00
Naoaki Iwakiri
89dc3b70e1 Sort weak passwords by severity (#446)
* Sort weak passwords by weakness

* Move static methods into local const
2021-04-05 18:23:48 -04:00
Oscar Hinton
769c247832 Configure webpack to proxy requests which avoids CORS issues (#914) 2021-04-05 22:38:21 +02:00
Oscar Hinton
12e4b614f5 Fix WebAuthn dialog not resetting on success (#910) 2021-04-05 22:37:02 +02:00
Thomas Rittson
b28eaa1aae Fix warning banner logic and open link in new tab (#909) 2021-04-06 06:26:04 +10:00
Matt Gibson
cd20b1c102 Update jslib (#913) 2021-04-05 15:19:46 -05:00
Erik Sennema
d6f80378eb Update README.md (#912) 2021-04-05 11:07:54 -04:00
Vincent Salucci
32e9124b9c [Reset Password] Enrollment actions (#900)
* [Reset Password] Enrollment actions

* Update jslib (0951424 -> f4f00b1)

* Added status icon
2021-04-05 09:48:46 -05:00
Matt Gibson
0aee3b7370 Update jslib (#901) 2021-03-30 19:01:16 -05:00
Thomas Rittson
6bb6a674ec Hide email address in Sends (#895)
* Let organizations disable anonymous sends only

* Add hide email option to send

* Display warning for anonymous Sends

* Enforce new Send policy, fix naming conventions

* Minor UI improvements

* Fix linting

* Fully disable editing anonymous Sends per policy

* Revert "Let organizations disable anonymous sends only"

This reverts commit 7877cb7751.

* Revert disableSendPolicy, add sendOptionsPolicy

* Rework UI for enforcing DisableHideEmail

* Fix typo

* Minor UI tweaks

* Minor UI tweaks

* Tweaks to UI copy

* Apply suggestions from code review

Minor changes to UI text

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

* style fixes

* update jslib

* Move SendOptionsExemptions warning banner

* updated service params

* Remove whitespace

* updated jslib

* Revert "updated jslib"

This reverts commit 8fd141c5b7.

* updated jslib

* Attachment azure upload blobs (#898)

* Upload and download attachments using direct urls

* Include FileUploadService dependency

* Update max file size message to current max

* Update jslib

* Update jslib

* updated service params

Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>
Co-authored-by: addison <addisonbeck1@gmail.com>
Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
2021-03-30 07:47:45 +10:00
Matt Gibson
29d7a5e37e Attachment azure upload blobs (#898)
* Upload and download attachments using direct urls

* Include FileUploadService dependency

* Update max file size message to current max

* Update jslib

* Update jslib
2021-03-29 09:44:08 -05:00
Matt Gibson
6067c1610c Azure upload blobs (#875)
* Include AzureStorageService in SendService

* Provide DI for abstrace AzureStorageService

* Use file upload service

* Update jslib
2021-03-26 16:54:13 -05:00
Daniel James Smith
1b74d22b46 Removed appveyor.yml and replaced badge in README.md (#896)
* Deleted appveyor.yml

* Replaced appveyor badge with GitHub workflow badge
2021-03-24 10:07:34 -04:00
cwille97
85a973afd4 Add locale info for hint equals password (#701)
Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>
2021-03-23 17:11:24 -04:00
Oscar Hinton
1ea8762eeb WebAuthn (#633) 2021-03-16 17:44:31 +01:00
Matt Gibson
35ecbcc11a Open pdf in same tab for Safari (#888)
Safari blocks sharing objectURL data between tabs.
Just use the same tab.
2021-03-11 18:05:24 -06:00
Kyle Spearrin
3e988a741b New Crowdin updates (#887)
* New translations messages.json (Romanian)

* New translations messages.json (Russian)

* New translations messages.json (Latvian)

* New translations messages.json (Estonian)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (Chinese Simplified)

* New translations messages.json (Ukrainian)

* New translations messages.json (Polish)

* New translations messages.json (Catalan)

* New translations messages.json (Dutch)

* New translations messages.json (Korean)

* New translations messages.json (Japanese)

* New translations messages.json (Hungarian)

* New translations messages.json (Finnish)

* New translations messages.json (German)

* New translations messages.json (Danish)

* New translations messages.json (English, United Kingdom)
2021-03-11 13:53:16 -05:00
Kyle Spearrin
db8f13d92f update jslib 2021-03-11 10:49:39 -05:00
Thomas Rittson
1a5885d6b4 Minor release version bump to 2.19.0 (#883)
* Minor release version bump to 2.19.0

* bump package-lock.json

Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>
2021-03-10 11:24:30 -05:00
Kyle Spearrin
31d2a09416 New Crowdin updates (#882)
* New translations messages.json (French)

* New translations messages.json (German)

* New translations messages.json (Slovak)

* New translations messages.json (Serbian (Cyrillic))
2021-03-09 13:18:52 -05:00
Kyle Spearrin
8ae96a6f88 New Crowdin updates (#881)
* 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-03-09 11:48:27 -05:00
Addison Beck
d8aae1358b Send Date Fallback QA Fixes (#879)
* added expiration date time autopopulation and new messages

* updated jslib
2021-03-09 11:00:45 -05:00
Matt Gibson
ed53c3b8f6 Fix how it works link Send Info Card in Vault (#878) 2021-03-08 16:06:41 -06:00
Matt Gibson
79ffafcc17 Inform send in vault (#876)
* Add first pass info card to Vault view

* Make send-info title a link

* Make access links open new tab

* Separate Vault card message from access message

* Add period to card end

* Final wording. Change Bitwarden Send links to point to Send tab
2021-03-08 15:17:42 -06:00
Addison Beck
bdf6dcd8cd Safari date time fix (#877)
* date/time fixes for safari

* cleanup

* updated jslib

* whitespace
2021-03-08 16:10:16 -05:00
Thomas Rittson
ec3154ea46 Fix cross-origin issues on 404 page (#873)
* Fix cross-origin issues on 404 page

* Add integrity hashes and use stable boostrap

* Restore absolute paths for css
2021-03-08 08:05:12 +10:00
Thomas Rittson
08fc18192d update jslib (#872) 2021-03-05 09:55:59 +10:00
Joseph Flinn
b01c71f579 adding docker to the rc branch workflow (#870) 2021-03-04 13:28:32 -08:00
280 changed files with 47278 additions and 25871 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,135 +42,124 @@ jobs:
docker --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Log into docker
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
- name: Login to Azure
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.ref == 'refs/heads/rc'
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
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: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
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'
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
run: |
mkdir -p ~/.docker/trust/private
echo "${{ secrets.DOCKER_DELEGATION_KEY }}" > ~/.docker/trust/private/$DOCKER_DELEGATION_KEY_ID.key
echo "${{ secrets.DOCKER_REPO_WEB_KEY }}" > ~/.docker/trust/private/$DOCKER_WEB_KEY_ID.key
echo "$DCT_DELEGATE_KEY" > ~/.docker/trust/private/$DCT_DELEGATION_KEY_ID.key
env:
DOCKER_DELEGATION_KEY_ID: "5702b22123e058cbd96a7a43000cb981ae98ef3f2f4aa34138ab3dc1d011e446"
DOCKER_WEB_KEY_ID: "0f88641697187f42a31b584897cd4edfe80360a5209122d9e7f71af17a6422e4"
DCT_DELEGATION_KEY_ID: "c9bde8ec820701516491e5e03d3a6354e7bd66d05fa3df2b0062f68b116dc59c"
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
- name: Build
run: |
chmod +x ./build.sh
./build.sh
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 rc branch
if: github.ref == 'refs/heads/rc'
run: ./build.sh tag rc
run: docker tag bitwarden/web bitwarden/web:rc
- name: Tag dev
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./build.sh tag dev
if: github.ref == 'refs/heads/master'
run: docker tag bitwarden/web bitwarden/web:dev
- name: Tag beta
if: github.event_name == 'release'
run: ./build.sh tag beta
- name: Tag version
if: github.event_name == 'release'
run: ./build.sh tag $($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'
- name: List Docker images
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc'
run: docker images
- name: Push rc images
if: github.ref == 'refs/heads/rc'
run: ./build.sh push rc
run: docker push bitwarden/web:rc
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
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'
run: ./build.sh push dev
if: github.ref == 'refs/heads/master'
run: docker push bitwarden/web:dev
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
- name: Push beta images
if: github.event_name == 'release'
run: ./build.sh push beta
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
- name: Push latest images
if: github.event_name == 'release'
run: ./build.sh push latest
env:
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
- name: Push version images
if: github.event_name == 'release'
run: ./build.sh push $($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: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
- name: Log out of docker
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
- 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 install
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

1
.gitignore vendored
View File

@@ -11,3 +11,4 @@ dist/
*.zip
build/
!dev-server.shared.pem
config/development.json

View File

@@ -5,8 +5,8 @@
The Bitwarden web project is an Angular application that powers the web vault (https://vault.bitwarden.com/).
</p>
<p align="center">
<a href="https://ci.appveyor.com/project/bitwarden/web/branch/master" target="_blank">
<img src="https://ci.appveyor.com/api/projects/status/github/bitwarden/web?branch=master&svg=true" alt="appveyor build" />
<a href="https://github.com/bitwarden/web/actions?query=branch:master" target="_blank">
<img src="https://github.com/bitwarden/web/actions/workflows/build.yml/badge.svg?branch=master" alt="Github Workflow build on master" />
</a>
<a href="https://crowdin.com/project/bitwarden-web" target="_blank">
<img src="https://d322cqt584bo4o.cloudfront.net/bitwarden-web/localized.svg" alt="Crowdin" />
@@ -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
@@ -34,52 +35,36 @@ npm install
npm run build:watch
```
You can now access the web vault in your browser at `https://localhost:8080`. You can adjust your API endpoint settings in `src/app/services/services.module.ts` by altering the `apiService.setUrls` call. For example:
You can now access the web vault in your browser at `https://localhost:8080`.
```typescript
await apiService.setUrls({
base: isDev ? null : window.location.origin,
api: isDev ? 'http://mylocalapi' : null,
identity: isDev ? 'http://mylocalidentity' : null,
events: isDev ? 'http://mylocalevents' : null,
});
```
If you want to point the development web vault to the production APIs, you can set:
```typescript
await apiService.setUrls({
base: null,
api: 'https://api.bitwarden.com',
identity: 'https://identity.bitwarden.com',
events: 'https://events.bitwarden.com',
});
```
And note to run the app with:
If you want to point the development web vault to the production APIs, you can run using:
```
npm install
npm run build:prod:watch
ENV=production npm run build:watch
```
## Common Issues:
You can also manually adjusting your API endpoint settings by adding `config/development.js` overriding any of the values in `config/base.json`. For example:
### CORS
```typescript
{
"proxyApi": "http://your-api-url",
"proxyIdentity": "http://your-identity-url",
"proxyEvents": "http://your-events-url",
"proxyNotifications": "http://your-notifications-url",
"proxyPortal": "http://your-portal-url",
"allowedHosts": ["hostnames-to-allow-in-webpack"]
}
```
To pick up the overrides in the newly created `config/development.js` file, run the app with:
If you run the frontend and receive a notification after attempting to login that says:
```
An error has occurred.
NetworkError when attempting to fetch resource.
npm run build:dev:watch
```
And in the console:
```
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://api.bitwarden.com/accounts/prelogin. (Reason: CORS header Access-Control-Allow-Origin missing).
```
This means that you are having a CORS header issue. This can be mitigated by using a CORS header changing extension in your browser such as [this one.](https://mybrowseraddon.com/access-control-allow-origin.html)
## Contribute
Code contributions are welcome! Please commit any pull requests against the `master` branch.
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.

View File

@@ -1,83 +0,0 @@
image:
- Visual Studio 2017
- Ubuntu1804
branches:
except:
- l10n_master
- gh-pages
services:
- docker
stack: node 10
init:
- ps: |
if($isWindows) {
Install-Product node 10
}
install:
- ps: |
$env:PACKAGE_VERSION = (Get-Content -Raw -Path .\package.json | ConvertFrom-Json).version
$env:PUSH_DOCKER = "false"
$env:PROD_DEPLOY = "false"
$env:TAG_NAME = ""
if($env:APPVEYOR_REPO_TAG -eq "true" -and $env:APPVEYOR_RE_BUILD -eq "True") {
$env:PROD_DEPLOY = "true"
$env:TAG_NAME = $env:APPVEYOR_REPO_TAG_NAME.TrimStart("v")
echo "This is a production deployment for ${env:TAG_NAME}."
}
if("${env:DOCKER_USERNAME}" -ne "" -and "${env:DOCKER_PASSWORD}" -ne "") {
$env:PUSH_DOCKER = "true"
}
if($isWindows) {
choco install cloc --no-progress
cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
}
before_build:
- node --version
- npm --version
- sh: |
if [ "${PUSH_DOCKER}" == "true" ]
then
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
fi
- cmd: set "GIT_PATH=C:\Program Files\Git\mingw64\libexec\git-core"
- cmd: set "PATH=%GIT_PATH%;%PATH%"
build_script:
- sh: chmod +x ./build.sh
- ps: |
if($isLinux) {
./build.sh
./build.sh tag dev
if($env:PROD_DEPLOY -eq "true") {
./build.sh tag beta
./build.sh tag $env:TAG_NAME
}
docker images
if($env:PUSH_DOCKER -eq "true") {
./build.sh push dev
if($env:PROD_DEPLOY -eq "true") {
./build.sh push beta
./build.sh push latest
./build.sh push $env:TAG_NAME
}
}
}
- cmd: npm install
- cmd: npm run build:prod
after_build:
- sh: |
if [ "${PUSH_DOCKER}" == "true" ]
then
docker logout
fi

View File

@@ -1,33 +0,0 @@
#!/usr/bin/env bash
set -e
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
echo ""
if [ $# -gt 1 -a "$1" == "push" ]
then
TAG=$2
echo "# Pushing Web ($TAG)"
echo ""
docker push bitwarden/web:$TAG
elif [ $# -gt 1 -a "$1" == "tag" ]
then
TAG=$2
echo "Tagging Web as '$TAG'"
docker tag bitwarden/web bitwarden/web:$TAG
else
echo "# Building Web"
echo ""
echo "Building app"
echo "npm version $(npm --version)"
npm install
npm run sub:update
npm run dist:selfhost
echo ""
echo "Building docker image"
docker --version
docker build -t bitwarden/web $DIR/.
fi

29
config.js Normal file
View File

@@ -0,0 +1,29 @@
function load(envName) {
const envOverrides = {
'production': () => require('./config/production.json'),
'qa': () => require('./config/qa.json'),
'development': () => require('./config/development.json'),
};
const baseConfig = require('./config/base.json');
const overrideConfig = envOverrides.hasOwnProperty(envName) ? envOverrides[envName]() : {};
return {
...baseConfig,
...overrideConfig
};
}
function log(configObj) {
const repeatNum = 50
console.log(`${"=".repeat(repeatNum)}\nenvConfig`)
Object.entries(configObj).map(([key, value]) => {
console.log(` ${key}: ${value}`)
})
console.log(`${"=".repeat(repeatNum)}`)
}
module.exports = {
load,
log
};

8
config/base.json Normal file
View File

@@ -0,0 +1,8 @@
{
"proxyApi": "http://localhost:4000",
"proxyIdentity": "http://localhost:33656",
"proxyEvents": "http://localhost:46273",
"proxyNotifications": "http://localhost:61840",
"proxyPortal": "http://localhost:52313",
"allowedHosts": []
}

7
config/production.json Normal file
View File

@@ -0,0 +1,7 @@
{
"proxyApi": "https://api.bitwarden.com",
"proxyIdentity": "https://identity.bitwarden.com",
"proxyEvents": "https://events.bitwarden.com",
"proxyNotifications": "https://notifications.bitwarden.com",
"proxyPortal": "https://portal.bitwarden.com"
}

7
config/qa.json Normal file
View File

@@ -0,0 +1,7 @@
{
"proxyApi": "https://api.qa.bitwarden.com",
"proxyIdentity": "https://identity.qa.bitwarden.com",
"proxyEvents": "https://events.qa.bitwarden.com",
"proxyNotifications": "https://notifications.qa.bitwarden.com",
"proxyPortal": "https://portal.qa.bitwarden.com"
}

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: 729ab4f20b...31fe643511

29384
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +1,28 @@
{
"name": "bitwarden-web",
"version": "2.18.0",
"version": "2.21.0",
"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:prod": "gulp prebuild && cross-env NODE_ENV=production webpack",
"build:prod:watch": "gulp prebuild && cross-env NODE_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:watch": "gulp prebuild && webpack serve",
"build:dev": "gulp prebuild && cross-env ENV=development webpack",
"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 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 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",
@@ -28,77 +32,58 @@
"lint:fix": "tslint 'src/**/*.ts' --fix"
},
"devDependencies": {
"@angular/compiler-cli": "^9.1.12",
"@ngtools/webpack": "^9.1.12",
"@types/jquery": "^3.5.1",
"@types/lunr": "^2.3.3",
"@types/node": "^10.17.28",
"@types/node-forge": "^0.7.5",
"@types/papaparse": "^4.5.3",
"@angular/compiler-cli": "^11.2.11",
"@ngtools/webpack": "^11.2.10",
"@types/duo_web_sdk": "^2.7.0",
"@types/jquery": "^3.5.5",
"@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": "^4.5.2",
"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.0",
"@microsoft/signalr-protocol-msgpack": "3.1.0",
"angular2-toaster": "8.0.0",
"angulartics2": "9.1.0",
"big-integer": "1.6.36",
"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.28.0",
"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.4.1",
"lunr": "2.3.3",
"ngx-infinite-scroll": "7.0.1",
"node-forge": "0.7.6",
"papaparse": "4.6.0",
"popper.js": "1.14.4",
"jquery": "3.6.0",
"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

@@ -1,35 +0,0 @@
html, body, .row {
height: 100%;
-webkit-font-smoothing: antialiased;
}
h2 {
font-size: 25px;
margin-bottom: 12.5px;
font-weight: 500;
line-height: 1.1;
}
.brand {
font-size: 23px;
line-height: 25px;
color: #fff;
font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
}
.banner {
background-color: #175DDC;
height: 56px;
}
.content {
padding-top: 20px;
padding-bottom: 20px;
padding-left: 15px;
padding-right: 15px;
}
.footer {
padding: 40px 0 40px 0;
border-top: 1px solid #dee2e6;
}

View File

@@ -5,15 +5,11 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<link rel="stylesheet" crossorigin="anonymous"
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0= sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw==">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700,300italic,400italic,600italic"
rel="stylesheet" type="text/css">
<link href="/404.css" rel="stylesheet" type="text/css">
<link href="/404/bootstrap.min.css" rel="stylesheet" type="text/css"
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l">
<link href="/404/font-awesome.min.css" rel="stylesheet" type="text/css"
integrity="sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw==">
<link href="/404/styles.css" rel="stylesheet" type="text/css">
<link rel="apple-touch-icon" sizes="180x180" href="/images/icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/icons/favicon-32x32.png">

7
src/404/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

4
src/404/font-awesome.min.css vendored Normal file

File diff suppressed because one or more lines are too long

119
src/404/styles.css Normal file
View File

@@ -0,0 +1,119 @@
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
src: url(../fonts/Open_Sans-italic-300.woff) format('woff');
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
src: url(../fonts/Open_Sans-italic-400.woff) format('woff');
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
src: url(../fonts/Open_Sans-italic-600.woff) format('woff');
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
src: url(../fonts/Open_Sans-italic-700.woff) format('woff');
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 800;
src: url(../fonts/Open_Sans-italic-800.woff) format('woff');
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: url(../fonts/Open_Sans-normal-300.woff) format('woff');
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: url(../fonts/Open_Sans-normal-400.woff) format('woff');
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: url(../fonts/Open_Sans-normal-600.woff) format('woff');
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
src: url(../fonts/Open_Sans-normal-700.woff) format('woff');
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
src: url(../fonts/Open_Sans-normal-800.woff) format('woff');
unicode-range: U+0-10FFFF;
}
body {
font-family: 'Open Sans';
}
html, body, .row {
height: 100%;
-webkit-font-smoothing: antialiased;
}
h2 {
font-size: 25px;
margin-bottom: 12.5px;
font-weight: 500;
line-height: 1.1;
}
.brand {
font-size: 23px;
line-height: 25px;
color: #fff;
font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
}
.banner {
background-color: #175DDC;
height: 56px;
}
.content {
padding-top: 20px;
padding-bottom: 20px;
padding-left: 15px;
padding-right: 15px;
}
.footer {
padding: 40px 0 40px 0;
border-top: 1px solid #dee2e6;
}

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',
@@ -33,13 +33,6 @@ export class LockComponent extends BaseLockComponent {
async ngOnInit() {
await super.ngOnInit();
const authed = await this.userService.isAuthenticated();
if (!authed) {
this.router.navigate(['/']);
} else if (await this.cryptoService.hasKey()) {
this.router.navigate(['vault']);
}
this.onSuccessfulSubmit = () => {
const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl !== '/' && previousUrl.indexOf('lock') === -1) {

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

@@ -2,12 +2,11 @@ import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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',
@@ -18,8 +17,7 @@ export class RecoverDeleteComponent {
formPromise: Promise<any>;
constructor(private router: Router, private apiService: ApiService,
private analytics: Angulartics2, private toasterService: ToasterService,
private i18nService: I18nService) {
private toasterService: ToasterService, private i18nService: I18nService) {
}
async submit() {
@@ -28,7 +26,6 @@ export class RecoverDeleteComponent {
request.email = this.email.trim().toLowerCase();
this.formPromise = this.apiService.postAccountRecoverDelete(request);
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Started Delete Recovery' });
this.toasterService.popAsync('success', null, this.i18nService.t('deleteRecoverEmailSent'));
this.router.navigate(['/']);
} catch { }

View File

@@ -2,14 +2,13 @@ import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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',
@@ -22,9 +21,8 @@ export class RecoverTwoFactorComponent {
formPromise: Promise<any>;
constructor(private router: Router, private apiService: ApiService,
private analytics: Angulartics2, private toasterService: ToasterService,
private i18nService: I18nService, private cryptoService: CryptoService,
private authService: AuthService) { }
private toasterService: ToasterService, private i18nService: I18nService,
private cryptoService: CryptoService, private authService: AuthService) { }
async submit() {
try {
@@ -35,7 +33,6 @@ export class RecoverTwoFactorComponent {
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
this.formPromise = this.apiService.postTwoFactorRecover(request);
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Recovered 2FA' });
this.toasterService.popAsync('success', null, this.i18nService.t('twoStepRecoverDisabled'));
this.router.navigate(['/']);
} catch { }

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

@@ -33,16 +33,10 @@
required appAutofocus appInputVerbatim autocomplete="new-password">
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.U2f">
<p class="text-center" *ngIf="!u2fReady">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"
aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span>
</p>
<ng-container *ngIf="u2fReady">
<p class="text-center">{{'insertU2f' | i18n}}</p>
<img src="../../images/u2fkey.jpg" alt="" class="rounded img-fluid mb-3">
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
<div id="web-authn-frame" class="mb-3">
<iframe id="webauthn_iframe"></iframe>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo">
@@ -51,7 +45,7 @@
</div>
</ng-container>
<i class="fa fa-spinner text-muted fa-spin pull-right" title="{{'loading' | i18n}}"
*ngIf="form.loading && selectedProviderType === providerType.U2f" aria-hidden="true"></i>
*ngIf="form.loading && selectedProviderType === providerType.WebAuthn" aria-hidden="true"></i>
<div class="form-check" *ngIf="selectedProviderType != null">
<input id="remember" type="checkbox" name="Remember" class="form-check-input"
[(ngModel)]="remember">
@@ -65,7 +59,7 @@
<div class="d-flex mb-3">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading"
*ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo && selectedProviderType !== providerType.U2f">
selectedProviderType !== providerType.OrganizationDuo && selectedProviderType !== providerType.WebAuthn">
<span>
<i class="fa fa-sign-in" aria-hidden="true"></i> {{'continue' | i18n}}
</span>
@@ -84,4 +78,3 @@
</div>
</form>
<ng-template #twoFactorOptions></ng-template>
<iframe id="u2f_iframe" hidden></iframe>

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

@@ -8,12 +8,11 @@ import {
} from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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',
@@ -27,8 +26,8 @@ export class VerifyRecoverDeleteComponent implements OnInit {
private token: string;
constructor(private router: Router, private apiService: ApiService,
private analytics: Angulartics2, private toasterService: ToasterService,
private i18nService: I18nService, private route: ActivatedRoute) {
private toasterService: ToasterService, private i18nService: I18nService,
private route: ActivatedRoute) {
}
ngOnInit() {
@@ -53,7 +52,6 @@ export class VerifyRecoverDeleteComponent implements OnInit {
const request = new VerifyDeleteRecoverRequest(this.userId, this.token);
this.formPromise = this.apiService.postAccountRecoverDeleteToken(request);
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Recovered Delete' });
this.toasterService.popAsync('success', this.i18nService.t('accountDeleted'),
this.i18nService.t('accountDeletedDesc'));
this.router.navigate(['/']);

View File

@@ -87,11 +87,12 @@ import { VaultComponent } from './vault/vault.component';
import { OrganizationGuardService } from './services/organization-guard.service';
import { OrganizationTypeGuardService } from './services/organization-type-guard.service';
import { UnauthGuardService } from './services/unauth-guard.service';
import { AuthGuardService } from 'jslib/angular/services/auth-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';
@@ -122,7 +123,11 @@ const routes: Routes = [
canActivate: [UnauthGuardService],
data: { titleId: 'passwordHint' },
},
{ path: 'lock', component: LockComponent },
{
path: 'lock',
component: LockComponent,
canActivate: [LockGuardService],
},
{ path: 'verify-email', component: VerifyEmailTokenComponent },
{
path: 'accept-organization',
@@ -195,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' },
},
],
},
@@ -385,7 +390,7 @@ const routes: Routes = [
canActivate: [OrganizationTypeGuardService],
data: {
titleId: 'people',
permissions: [Permissions.ManageUsers],
permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword],
},
},
{

View File

@@ -1,15 +1,12 @@
import * as jq from 'jquery';
import Swal from 'sweetalert2/dist/sweetalert2.js';
import Swal from 'sweetalert2';
import {
BodyOutputType,
Toast,
ToasterConfig,
ToasterContainerComponent,
ToasterService,
} from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import {
Component,
@@ -24,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';
@@ -70,12 +67,12 @@ export class AppComponent implements OnDestroy, OnInit {
private idleTimer: number = null;
private isIdle = false;
constructor(private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
constructor(
private broadcasterService: BroadcasterService, private userService: UserService,
private tokenService: TokenService, private folderService: FolderService,
private settingsService: SettingsService, private syncService: SyncService,
private passwordGenerationService: PasswordGenerationService, private cipherService: CipherService,
private authService: AuthService, private router: Router, private analytics: Angulartics2,
private authService: AuthService, private router: Router,
private toasterService: ToasterService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private ngZone: NgZone,
private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService,
@@ -139,15 +136,18 @@ export class AppComponent implements OnDestroy, OnInit {
this.router.navigate(['settings/premium']);
}
break;
case 'emailVerificationRequired':
const emailVerificationConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('emailVerificationRequiredDesc'),
this.i18nService.t('emailVerificationRequired'),
this.i18nService.t('learnMore'), this.i18nService.t('cancel'));
if (emailVerificationConfirmed) {
this.platformUtilsService.launchUri('https://bitwarden.com/help/article/create-bitwarden-account/');
}
break;
case 'showToast':
this.showToast(message);
break;
case 'analyticsEventTrack':
this.analytics.eventTrack.next({
action: message.action,
properties: { label: message.label },
});
break;
case 'setFullWidth':
this.setFullWidth();
break;
@@ -198,7 +198,6 @@ export class AppComponent implements OnDestroy, OnInit {
this.searchService.clearIndex();
this.authService.logOut(async () => {
this.analytics.eventTrack.next({ action: 'Logged Out' });
if (expired) {
this.toasterService.popAsync('warning', this.i18nService.t('loggedOut'),
this.i18nService.t('loginExpired'));

View File

@@ -1,8 +1,4 @@
import 'core-js';
import { ToasterModule } from 'angular2-toaster';
import { Angulartics2Module } from 'angulartics2';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { AppRoutingModule } from './app-routing.module';
@@ -42,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';
@@ -55,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';
@@ -135,8 +135,8 @@ import { TwoFactorDuoComponent } from './settings/two-factor-duo.component';
import { TwoFactorEmailComponent } from './settings/two-factor-email.component';
import { TwoFactorRecoveryComponent } from './settings/two-factor-recovery.component';
import { TwoFactorSetupComponent } from './settings/two-factor-setup.component';
import { TwoFactorU2fComponent } from './settings/two-factor-u2f.component';
import { TwoFactorVerifyComponent } from './settings/two-factor-verify.component';
import { TwoFactorWebAuthnComponent } from './settings/two-factor-webauthn.component';
import { TwoFactorYubiKeyComponent } from './settings/two-factor-yubikey.component';
import { UpdateKeyComponent } from './settings/update-key.component';
import { UpdateLicenseComponent } from './settings/update-license.component';
@@ -167,43 +167,51 @@ import { CiphersComponent } from './vault/ciphers.component';
import { CollectionsComponent } from './vault/collections.component';
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
import { GroupingsComponent } from './vault/groupings.component';
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';
@@ -214,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');
@@ -241,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');
@@ -255,11 +276,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
FormsModule,
AppRoutingModule,
ServicesModule,
Angulartics2Module.forRoot({
pageTracking: {
clearQueryParams: true,
},
}),
ToasterModule.forRoot(),
InfiniteScrollModule,
DragDropModule,
@@ -336,6 +352,9 @@ registerLocaleData(localeZhTw, 'zh-TW');
OrganizationPlansComponent,
OrganizationSubscriptionComponent,
OrgAttachmentsComponent,
OrgBulkStatusComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
@@ -354,6 +373,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
OrgPeopleComponent,
OrgPolicyEditComponent,
OrgPoliciesComponent,
OrgResetPasswordComponent,
OrgReusedPasswordsReportComponent,
OrgSettingComponent,
OrgToolsComponent,
@@ -382,6 +402,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
SelectCopyDirective,
SendAddEditComponent,
SendComponent,
SendInfoComponent,
SettingsComponent,
ShareComponent,
SsoComponent,
@@ -397,8 +418,8 @@ registerLocaleData(localeZhTw, 'zh-TW');
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorU2fComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
@@ -434,12 +455,16 @@ registerLocaleData(localeZhTw, 'zh-TW');
ModalComponent,
OrgAddEditComponent,
OrgAttachmentsComponent,
OrgBulkStatusComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
OrgEntityUsersComponent,
OrgGroupAddEditComponent,
OrgPolicyEditComponent,
OrgResetPasswordComponent,
OrgUserAddEditComponent,
OrgUserConfirmComponent,
OrgUserGroupsComponent,
@@ -452,11 +477,11 @@ registerLocaleData(localeZhTw, 'zh-TW');
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorU2fComponent,
TwoFactorWebAuthnComponent,
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',
@@ -15,8 +15,8 @@ export class FooterComponent implements OnInit {
constructor(private platformUtilsService: PlatformUtilsService) { }
ngOnInit() {
async ngOnInit() {
this.year = new Date().getFullYear().toString();
this.version = this.platformUtilsService.getApplicationVersion();
this.version = await this.platformUtilsService.getApplicationVersion();
}
}

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',
@@ -16,9 +16,9 @@ export class FrontendLayoutComponent implements OnInit, OnDestroy {
constructor(private platformUtilsService: PlatformUtilsService) { }
ngOnInit() {
async ngOnInit() {
this.year = new Date().getFullYear().toString();
this.version = this.platformUtilsService.getApplicationVersion();
this.version = await this.platformUtilsService.getApplicationVersion();
document.body.classList.add('layout_frontend');
}

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.id, 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

@@ -7,21 +7,20 @@ import {
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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 { CipherString } from 'jslib/models/domain/cipherString';
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',
@@ -46,9 +45,8 @@ export class CollectionAddEditComponent implements OnInit {
private orgKey: SymmetricCryptoKey;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private platformUtilsService: PlatformUtilsService, private cryptoService: CryptoService,
private userService: UserService) { }
private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService, private userService: UserService) { }
async ngOnInit() {
const organization = await this.userService.getOrganization(this.organizationId);
@@ -65,7 +63,7 @@ export class CollectionAddEditComponent implements OnInit {
this.title = this.i18nService.t('editCollection');
try {
const collection = await this.apiService.getCollectionDetails(this.organizationId, this.collectionId);
this.name = await this.cryptoService.decryptToUtf8(new CipherString(collection.name), this.orgKey);
this.name = await this.cryptoService.decryptToUtf8(new EncString(collection.name), this.orgKey);
this.externalId = collection.externalId;
if (collection.groups != null && this.groups.length > 0) {
collection.groups.forEach(s => {
@@ -124,7 +122,6 @@ export class CollectionAddEditComponent implements OnInit {
this.formPromise = this.apiService.postCollection(this.organizationId, request);
}
await this.formPromise;
this.analytics.eventTrack.next({ action: this.editMode ? 'Edited Collection' : 'Created Collection' });
this.toasterService.popAsync('success', null,
this.i18nService.t(this.editMode ? 'editedCollectionId' : 'createdCollectionId', this.name));
this.onSavedCollection.emit();
@@ -146,7 +143,6 @@ export class CollectionAddEditComponent implements OnInit {
try {
this.deletePromise = this.apiService.deleteCollection(this.organizationId, this.collectionId);
await this.deletePromise;
this.analytics.eventTrack.next({ action: 'Deleted Collection' });
this.toasterService.popAsync('success', null, this.i18nService.t('deletedCollectionId', this.name));
this.onDeletedCollection.emit();
} catch { }

View File

@@ -8,23 +8,22 @@ import {
import { ActivatedRoute } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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';
@@ -52,9 +51,9 @@ export class CollectionsComponent implements OnInit {
constructor(private apiService: ApiService, private route: ActivatedRoute,
private collectionService: CollectionService, private componentFactoryResolver: ComponentFactoryResolver,
private analytics: Angulartics2, private toasterService: ToasterService,
private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
private userService: UserService, private searchService: SearchService) { }
private toasterService: ToasterService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private userService: UserService,
private searchService: SearchService) { }
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
@@ -141,7 +140,6 @@ export class CollectionsComponent implements OnInit {
try {
await this.apiService.deleteCollection(this.organizationId, collection.id);
this.analytics.eventTrack.next({ action: 'Deleted Collection' });
this.toasterService.popAsync('success', null, this.i18nService.t('deletedCollectionId', collection.name));
this.removeCollection(collection);
} catch { }

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

@@ -7,17 +7,16 @@ import {
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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',
@@ -42,7 +41,7 @@ export class EntityUsersComponent implements OnInit {
private allUsers: OrganizationUserUserDetailsResponse[] = [];
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService) { }
private toasterService: ToasterService) { }
async ngOnInit() {
await this.loadUsers();
@@ -129,9 +128,6 @@ export class EntityUsersComponent implements OnInit {
this.formPromise = this.apiService.putCollectionUsers(this.organizationId, this.entityId, selections);
}
await this.formPromise;
this.analytics.eventTrack.next({
action: this.entity === 'group' ? 'Edited Group Users' : 'Edited Collection Users',
});
this.toasterService.popAsync('success', null, this.i18nService.t('updatedUsers'));
this.onEditedUsers.emit();
} catch { }

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

@@ -7,19 +7,18 @@ import {
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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',
@@ -42,8 +41,8 @@ export class GroupAddEditComponent implements OnInit {
deletePromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private collectionService: CollectionService, private platformUtilsService: PlatformUtilsService) { }
private toasterService: ToasterService, private collectionService: CollectionService,
private platformUtilsService: PlatformUtilsService) { }
async ngOnInit() {
this.editMode = this.loading = this.groupId != null;
@@ -110,7 +109,6 @@ export class GroupAddEditComponent implements OnInit {
this.formPromise = this.apiService.postGroup(this.organizationId, request);
}
await this.formPromise;
this.analytics.eventTrack.next({ action: this.editMode ? 'Edited Group' : 'Created Group' });
this.toasterService.popAsync('success', null,
this.i18nService.t(this.editMode ? 'editedGroupId' : 'createdGroupId', this.name));
this.onSavedGroup.emit();
@@ -132,7 +130,6 @@ export class GroupAddEditComponent implements OnInit {
try {
this.deletePromise = this.apiService.deleteGroup(this.organizationId, this.groupId);
await this.deletePromise;
this.analytics.eventTrack.next({ action: 'Deleted Group' });
this.toasterService.popAsync('success', null, this.i18nService.t('deletedGroupId', this.name));
this.onDeletedGroup.emit();
} catch { }

View File

@@ -11,17 +11,16 @@ import {
} from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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';
@@ -49,9 +48,9 @@ export class GroupsComponent implements OnInit {
constructor(private apiService: ApiService, private route: ActivatedRoute,
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
private analytics: Angulartics2, private toasterService: ToasterService,
private platformUtilsService: PlatformUtilsService, private userService: UserService,
private router: Router, private searchService: SearchService) { }
private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService,
private userService: UserService, private router: Router,
private searchService: SearchService) { }
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
@@ -136,7 +135,6 @@ export class GroupsComponent implements OnInit {
try {
await this.apiService.deleteGroup(this.organizationId, group.id);
this.analytics.eventTrack.next({ action: 'Deleted Group' });
this.toasterService.popAsync('success', null, this.i18nService.t('deletedGroupId', group.name));
this.removeGroup(group);
} catch { }

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,39 +5,54 @@ import {
ViewChild,
ViewContainerRef,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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 { 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 +62,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 +79,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;
@@ -70,10 +94,11 @@ export class PeopleComponent implements OnInit {
constructor(private apiService: ApiService, private route: ActivatedRoute,
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
private platformUtilsService: PlatformUtilsService, private analytics: Angulartics2,
private toasterService: ToasterService, private cryptoService: CryptoService,
private userService: UserService, private router: Router,
private storageService: StorageService, private searchService: SearchService) { }
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 policyService: PolicyService,
private searchPipe: SearchPipe) { }
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
@@ -85,6 +110,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;
// Backfill pub/priv key if necessary
if (!organization.hasPublicAndPrivateKeys) {
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;
} else {
throw new Error(this.i18nService.t('resetPasswordOrgKeysError'));
}
} else {
this.orgHasKeys = true;
}
await this.load();
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
@@ -115,9 +159,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 +198,8 @@ export class PeopleComponent implements OnInit {
} else {
this.users = this.allUsers;
}
// Reset checkbox selecton
this.selectAll(false);
this.resetPaging();
}
@@ -168,6 +243,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,25 +308,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);
this.analytics.eventTrack.next({ action: 'Deleted User' });
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.analytics.eventTrack.next({ action: 'Reinvited User' });
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;
@@ -258,6 +413,20 @@ export class PeopleComponent implements OnInit {
}
}
const confirmUser = async (publicKey: Uint8Array) => {
try {
this.actionPromise = this.doConfirmation(user, publicKey);
await this.actionPromise;
updateUser(this);
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenConfirmed', user.name || user.email));
} catch (e) {
this.validationService.showError(e);
throw e;
} finally {
this.actionPromise = null;
}
};
if (this.actionPromise != null) {
return;
}
@@ -277,9 +446,14 @@ export class PeopleComponent implements OnInit {
childComponent.organizationId = this.organizationId;
childComponent.organizationUserId = user != null ? user.id : null;
childComponent.userId = user != null ? user.userId : null;
childComponent.onConfirmedUser.subscribe(() => {
this.modal.close();
updateUser(this);
childComponent.onConfirmedUser.subscribe(async (publicKey: Uint8Array) => {
try {
await confirmUser(publicKey);
this.modal.close();
} catch (e) {
// tslint:disable-next-line
console.error('Handled exception:', e);
}
});
this.modal.onClosed.subscribe(() => {
@@ -288,12 +462,19 @@ export class PeopleComponent implements OnInit {
return;
}
this.actionPromise = this.doConfirmation(user);
await this.actionPromise;
updateUser(this);
this.analytics.eventTrack.next({ action: 'Confirmed User' });
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenConfirmed', user.name || user.email));
this.actionPromise = null;
try {
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
try {
// tslint:disable-next-line
console.log('User\'s fingerprint: ' +
(await this.cryptoService.getFingerprint(user.userId, publicKey.buffer)).join('-'));
} catch { }
await confirmUser(publicKey);
} catch (e) {
// tslint:disable-next-line
console.error('Handled exception:', e);
}
}
async events(user: OrganizationUserUserDetailsResponse) {
@@ -334,15 +515,103 @@ export class PeopleComponent implements OnInit {
return !searching && this.users && this.users.length > this.pageSize;
}
private async doConfirmation(user: OrganizationUserUserDetailsResponse) {
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
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 {
// tslint:disable-next-line
console.log('User\'s fingerprint: ' +
(await this.cryptoService.getFingerprint(user.userId, publicKey.buffer)).join('-'));
} catch { }
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);
const request = new OrganizationUserConfirmRequest();
request.key = key.encryptedString;
@@ -374,4 +643,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';
@@ -109,6 +109,19 @@ export class PoliciesComponent implements OnInit {
enabled: false,
display: true,
},
{
name: this.i18nService.t('sendOptions'),
description: this.i18nService.t('sendOptionsPolicyDesc'),
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>
@@ -35,6 +35,12 @@
<app-callout type="warning" *ngIf="type === policyType.DisableSend">
{{'disableSendExemption' | i18n}}
</app-callout>
<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"
@@ -147,14 +153,36 @@
<label class="form-check-label" for="passGenIncludeNumber">{{'includeNumber' | i18n}}</label>
</div>
</ng-container>
<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">
<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

@@ -7,16 +7,15 @@ import {
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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',
@@ -38,7 +37,6 @@ export class PolicyEditComponent implements OnInit {
defaultTypes: any[];
// Master password
masterPassMinComplexity?: number = null;
masterPassMinLength?: number;
masterPassRequireUpper?: number;
@@ -47,7 +45,6 @@ export class PolicyEditComponent implements OnInit {
masterPassRequireSpecial?: number;
// Password generator
passGenDefaultType?: string;
passGenMinLength?: number;
passGenUseUpper?: boolean;
@@ -60,10 +57,16 @@ export class PolicyEditComponent implements OnInit {
passGenCapitalize?: boolean;
passGenIncludeNumber?: boolean;
// Send options
sendDisableHideEmail?: boolean;
// Reset Password
resetPasswordAutoEnroll?: boolean;
private policy: PolicyResponse;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService) {
private toasterService: ToasterService) {
this.passwordScores = [
{ name: '-- ' + i18nService.t('select') + ' --', value: null },
{ name: i18nService.t('weak') + ' (0)', value: 0 },
@@ -113,6 +116,12 @@ export class PolicyEditComponent implements OnInit {
this.masterPassRequireNumbers = this.policy.data.requireNumbers;
this.masterPassRequireSpecial = this.policy.data.requireSpecial;
break;
case PolicyType.SendOptions:
this.sendDisableHideEmail = this.policy.data.disableHideEmail;
break;
case PolicyType.ResetPassword:
this.resetPasswordAutoEnroll = this.policy.data.autoEnrollEnabled;
break;
default:
break;
}
@@ -159,13 +168,22 @@ export class PolicyEditComponent implements OnInit {
requireSpecial: this.masterPassRequireSpecial,
};
break;
case PolicyType.SendOptions:
request.data = {
disableHideEmail: this.sendDisableHideEmail,
};
break;
case PolicyType.ResetPassword:
request.data = {
autoEnrollEnabled: this.resetPasswordAutoEnroll,
};
break;
default:
break;
}
try {
this.formPromise = this.apiService.putPolicy(this.organizationId, this.type, request);
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Edited Policy' });
this.toasterService.popAsync('success', null, this.i18nService.t('editedPolicyId', this.name));
this.onSavedPolicy.emit();
} catch { }

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,12 +172,23 @@
<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>
</div>
</div>
<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"
(change)="handleDependentPermissions()">
<label class="form-check-label font-weight-normal" for="manageResetPassword">
{{'manageResetPassword' | i18n}}
</label>
</div>
</div>
</div>
</div>
</div>
@@ -255,8 +266,9 @@
<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 class="ml-auto">
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"

View File

@@ -7,23 +7,22 @@ import {
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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',
@@ -54,8 +53,8 @@ export class UserAddEditComponent implements OnInit {
}
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private collectionService: CollectionService, private platformUtilsService: PlatformUtilsService) { }
private toasterService: ToasterService, private collectionService: CollectionService,
private platformUtilsService: PlatformUtilsService) { }
async ngOnInit() {
this.editMode = this.loading = this.organizationUserId != null;
@@ -138,9 +137,21 @@ export class UserAddEditComponent implements OnInit {
p.manageUsers = clearPermissions ?
false :
this.permissions.manageUsers;
p.manageResetPassword = clearPermissions ?
false :
this.permissions.manageResetPassword;
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') {
@@ -167,7 +178,6 @@ export class UserAddEditComponent implements OnInit {
this.formPromise = this.apiService.postOrganizationUserInvite(this.organizationId, request);
}
await this.formPromise;
this.analytics.eventTrack.next({ action: this.editMode ? 'Edited User' : 'Invited User' });
this.toasterService.popAsync('success', null,
this.i18nService.t(this.editMode ? 'editedUserId' : 'invitedUsers', this.name));
this.onSavedUser.emit();
@@ -189,7 +199,6 @@ export class UserAddEditComponent implements OnInit {
try {
this.deletePromise = this.apiService.deleteOrganizationUser(this.organizationId, this.organizationUserId);
await this.deletePromise;
this.analytics.eventTrack.next({ action: 'Deleted User' });
this.toasterService.popAsync('success', null, this.i18nService.t('removedUserId', this.name));
this.onDeletedUser.emit();
} catch { }

View File

@@ -1,6 +1,6 @@
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<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">
{{'confirmUser' | i18n}}

View File

@@ -6,19 +6,13 @@ import {
Output,
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { ConstantsService } from 'jslib/services/constants.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 { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { OrganizationUserConfirmRequest } from 'jslib/models/request/organizationUserConfirmRequest';
import { Utils } from 'jslib/misc/utils';
import { Utils } from 'jslib-common/misc/utils';
@Component({
selector: 'app-user-confirm',
@@ -34,13 +28,11 @@ export class UserConfirmComponent implements OnInit {
dontAskAgain = false;
loading = true;
fingerprint: string;
formPromise: Promise<any>;
private publicKey: Uint8Array = null;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private cryptoService: CryptoService, private storageService: StorageService) { }
constructor(private apiService: ApiService, private cryptoService: CryptoService,
private storageService: StorageService) { }
async ngOnInit() {
try {
@@ -65,20 +57,6 @@ export class UserConfirmComponent implements OnInit {
await this.storageService.save(ConstantsService.autoConfirmFingerprints, true);
}
try {
this.formPromise = this.doConfirmation();
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Confirmed User' });
this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenConfirmed', this.name));
this.onConfirmedUser.emit();
} catch { }
}
private async doConfirmation() {
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, this.publicKey.buffer);
const request = new OrganizationUserConfirmRequest();
request.key = key.encryptedString;
await this.apiService.postOrganizationUserConfirm(this.organizationId, this.organizationUserId, request);
this.onConfirmedUser.emit(this.publicKey);
}
}

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

@@ -7,15 +7,14 @@ import {
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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',
@@ -32,7 +31,7 @@ export class UserGroupsComponent implements OnInit {
formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService) { }
private toasterService: ToasterService) { }
async ngOnInit() {
const groupsResponse = await this.apiService.getGroups(this.organizationId);
@@ -75,7 +74,6 @@ export class UserGroupsComponent implements OnInit {
this.formPromise = this.apiService.putOrganizationUserGroups(this.organizationId, this.organizationUserId,
request);
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Edited User Groups' });
this.toasterService.popAsync('success', null, this.i18nService.t('editedGroupsForUser', this.name));
this.onSavedUser.emit();
} catch { }

View File

@@ -4,23 +4,28 @@ import {
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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,9 +51,9 @@ export class AccountComponent {
constructor(private componentFactoryResolver: ComponentFactoryResolver,
private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private route: ActivatedRoute, private syncService: SyncService,
private platformUtilsService: PlatformUtilsService) { }
private toasterService: ToasterService, private route: ActivatedRoute,
private syncService: SyncService, private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService) { }
async ngOnInit() {
this.selfHosted = this.platformUtilsService.isSelfHost();
@@ -69,11 +74,18 @@ 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);
});
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Updated Organization Settings' });
this.toasterService.popAsync('success', null, this.i18nService.t('organizationUpdated'));
} catch { }
}
@@ -81,7 +93,6 @@ export class AccountComponent {
async submitTaxInfo() {
this.taxFormPromise = this.taxInfo.submitTaxInfo();
await this.taxFormPromise;
this.analytics.eventTrack.next({ action: 'Updated Organization Tax Info' });
this.toasterService.popAsync('success', null, this.i18nService.t('taxInfoUpdated'));
}

View File

@@ -12,12 +12,11 @@ import {
} from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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';
@@ -39,8 +38,8 @@ export class AdjustSeatsComponent {
formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private router: Router, private activatedRoute: ActivatedRoute) { }
private toasterService: ToasterService, private router: Router,
private activatedRoute: ActivatedRoute) { }
async submit() {
try {
@@ -63,7 +62,6 @@ export class AdjustSeatsComponent {
};
this.formPromise = action();
await this.formPromise;
this.analytics.eventTrack.next({ action: this.add ? 'Added Seats' : 'Removed Seats' });
this.onAdjusted.emit(this.seatAdjustment);
if (paymentFailed) {
this.toasterService.popAsync({

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',
@@ -28,7 +28,6 @@ export class ChangePlanComponent {
async submit() {
try {
this.platformUtilsService.eventTrack('Changed Plan');
this.onChanged.emit();
} catch { }
}

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

@@ -2,13 +2,12 @@ import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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',
@@ -21,8 +20,8 @@ export class DeleteOrganizationComponent {
formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private cryptoService: CryptoService, private router: Router) { }
private toasterService: ToasterService, private cryptoService: CryptoService,
private router: Router) { }
async submit() {
if (this.masterPassword == null || this.masterPassword === '') {
@@ -36,7 +35,6 @@ export class DeleteOrganizationComponent {
try {
this.formPromise = this.apiService.deleteOrganization(this.organizationId, request);
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Deleted Organization' });
this.toasterService.popAsync('success', this.i18nService.t('organizationDeleted'),
this.i18nService.t('organizationDeletedDesc'));
this.router.navigate(['/']);

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',
@@ -32,7 +32,6 @@ export class DownloadLicenseComponent {
const license = await this.formPromise;
const licenseString = JSON.stringify(license, null, 2);
this.platformUtilsService.saveFile(window, licenseString, null, 'bitwarden_organization_license.json');
this.platformUtilsService.eventTrack('Downloaded License');
this.onDownloaded.emit();
} catch { }
}

View File

@@ -5,11 +5,10 @@ import {
import { ActivatedRoute } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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';
@@ -18,10 +17,9 @@ import { UserBillingComponent } from '../../settings/user-billing.component';
templateUrl: '../../settings/user-billing.component.html',
})
export class OrganizationBillingComponent extends UserBillingComponent implements OnInit {
constructor(apiService: ApiService, i18nService: I18nService,
analytics: Angulartics2, toasterService: ToasterService,
constructor(apiService: ApiService, i18nService: I18nService, toasterService: ToasterService,
private route: ActivatedRoute, platformUtilsService: PlatformUtilsService) {
super(apiService, i18nService, analytics, toasterService, platformUtilsService);
super(apiService, i18nService, toasterService, platformUtilsService);
}
async ngOnInit() {

View File

@@ -5,16 +5,15 @@ import {
import { ActivatedRoute } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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',
@@ -38,9 +37,8 @@ export class OrganizationSubscriptionComponent implements OnInit {
reinstatePromise: Promise<any>;
constructor(private apiService: ApiService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService, private analytics: Angulartics2,
private toasterService: ToasterService, private messagingService: MessagingService,
private route: ActivatedRoute) {
private i18nService: I18nService, private toasterService: ToasterService,
private messagingService: MessagingService, private route: ActivatedRoute) {
this.selfHosted = platformUtilsService.isSelfHost();
}
@@ -75,7 +73,6 @@ export class OrganizationSubscriptionComponent implements OnInit {
try {
this.reinstatePromise = this.apiService.postOrganizationReinstate(this.organizationId);
await this.reinstatePromise;
this.analytics.eventTrack.next({ action: 'Reinstated Plan' });
this.toasterService.popAsync('success', null, this.i18nService.t('reinstated'));
this.load();
} catch { }
@@ -95,7 +92,6 @@ export class OrganizationSubscriptionComponent implements OnInit {
try {
this.cancelPromise = this.apiService.postOrganizationCancel(this.organizationId);
await this.cancelPromise;
this.analytics.eventTrack.next({ action: 'Canceled Plan' });
this.toasterService.popAsync('success', null, this.i18nService.t('canceledSubscription'));
this.load();
} catch { }

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

@@ -5,12 +5,11 @@ import {
} from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
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';
@@ -21,12 +20,11 @@ import { ImportComponent as BaseImportComponent } from '../../tools/import.compo
export class ImportComponent extends BaseImportComponent {
organizationName: string;
constructor(i18nService: I18nService, analytics: Angulartics2,
toasterService: ToasterService, importService: ImportService,
router: Router, private route: ActivatedRoute,
constructor(i18nService: I18nService, toasterService: ToasterService,
importService: ImportService, router: Router, private route: ActivatedRoute,
platformUtilsService: PlatformUtilsService,
private userService: UserService) {
super(i18nService, analytics, toasterService, importService, router, platformUtilsService);
super(i18nService, toasterService, importService, router, platformUtilsService);
}
async ngOnInit() {

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';
@@ -25,8 +25,8 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
constructor(cipherService: CipherService, i18nService: I18nService,
cryptoService: CryptoService, userService: UserService,
platformUtilsService: PlatformUtilsService, private apiService: ApiService) {
super(cipherService, i18nService, cryptoService, userService, platformUtilsService);
platformUtilsService: PlatformUtilsService, apiService: ApiService) {
super(cipherService, i18nService, cryptoService, userService, platformUtilsService, apiService);
}
protected async reupload(attachment: AttachmentView) {

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