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

Compare commits

...

115 Commits

Author SHA1 Message Date
Vince Grassia
165944624a Fix docker tag version in workflow (#973) 2021-05-12 14:28:28 -07:00
Chad Scharf
11b96728f2 Version bump, 2.20.1 (#971) 2021-05-12 13:00:12 -04:00
Oscar Hinton
62e22ff149 Change WebAuthn connectors from using inline onclick to external (#969)
(cherry picked from commit a3506e833a)
2021-05-12 17:20:21 +02:00
Daniel James Smith
09968a18d0 Fix typo in webAuthnAuthenticate (#964) 2021-05-12 09:10:08 -04:00
Joseph Flinn
cc59d08a69 fixing docker push (#965)
* fixing docker push

* adding in the the missed vars
2021-05-11 12:51:02 -07:00
Joseph Flinn
ac45ac2eca 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:05:58 -07:00
Kyle Spearrin
95c0e211c7 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:49 -04:00
Oscar Hinton
fed8990a6a Add auto delete warning to trash page (#953)
* Add warning to trash page

(cherry picked from commit dd56c9bc87)
2021-04-27 19:24:34 +02:00
Matt Gibson
b8431f0101 Update jslib 2021-04-26 15:24:32 -05:00
Matt Gibson
af47e25ead Update jslib 2021-04-23 14:09:14 -05:00
Matt Gibson
a315370e5d update jslib 2021-04-23 10:32:22 -05:00
Matt Gibson
5a64b22190 Specify organization id as the indexing entity (#945)
* Specify organization id as the indexing entity

* Update jslib

(cherry picked from commit f6eec08b70)
2021-04-23 09:53:33 -05:00
Vincent Salucci
4b05bc981c [Version] Bumped to 2.20.0 (#944)
* [Version] Bumped to 2.20.0

* Updated package-lock version
2021-04-22 11:57:30 -05:00
Vincent Salucci
00f0dc3499 [Reset Password] Feature Flag (#943) 2021-04-22 09:49:27 -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
Matt Gibson
a6c98f462a Show spinner when downloading file for Send Accesses (#869) 2021-03-04 14:58:45 -06:00
Thomas Rittson
473dd8739a Expand encrypted export warning (#866)
* Add additional warnings about encrypted export

* Allow html body in showDialog
2021-03-05 06:56:25 +10:00
Joseph Flinn
722bcfc31b Add rc auto (#868)
* adding new rc docker tag

* adding rc docker push

* updating task name
2021-03-04 10:07:59 -08:00
Thomas Rittson
929c3d7662 Disable save button and add spinner after submit (#867) 2021-03-04 11:23:32 -05:00
Addison Beck
e25a8e051a updated jslib (#865) 2021-03-03 16:25:13 -05:00
Vincent Salucci
4a1b46dd41 [Send] Hide identifier if unavailable (#864) 2021-03-03 12:30:19 -06:00
Thomas Rittson
16877521e7 Exclude owners and admins from single organization policy when creating new org (#855)
* Fix single org policy when creating organization

Exclude owners and admins from policy when creating new org

* Remove looping async calls and fix linting
2021-03-03 08:16:04 +10:00
Addison Beck
a16abb94cd Date time fallback fixes (#863)
* changed required state of expiration date for edit vs create modes

* updated jslib
2021-03-02 17:04:04 -05:00
Addison Beck
5c8e9a990c changed send access creator identifier string (#862) 2021-03-02 16:17:56 -05:00
Addison Beck
c2515ed3ae added date/time fallbacks for safari/ff (#861)
* added date/time fallbacks for safari/ff

* updated jslib
2021-03-02 14:02:15 -05:00
Matt Gibson
227f457409 Use download link requests (#859)
* Use download link requests

* Update jslib

* Update jslib
2021-03-02 11:11:27 -06:00
vachan-maker
2e4a3501a2 Update change-kdf.component.html (#860) 2021-03-02 11:21:34 -05:00
Addison Beck
fade7f1713 updated jslib (#858) 2021-03-01 12:06:33 -05:00
Vincent Salucci
de84468ad8 [Send] Updated current access input type to text (#857) 2021-03-01 09:41:46 -06:00
Addison Beck
2e20978cee updated send access component (#852)
* updated send access component

* updated jslib

* bump jslib version (#851)

* code review fixes

* updated send access component

* updated jslib

* code review fixes

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2021-02-25 17:25:53 -05:00
Thomas Rittson
2cc24335ef bump jslib version (#851) 2021-02-26 08:11:13 +10:00
Vincent Salucci
721a9f5f69 [Send] Web cleanup (#850) 2021-02-25 11:23:52 -06:00
Jens Spanier
4ebbefa181 Use v2 of 2fa.directory API (#849) 2021-02-25 11:25:48 -05:00
Addison Beck
6ad930c609 Web send updates (#848)
* updated send link copy toast message

* added a show/hide toggle for Send options
2021-02-24 13:27:07 -05:00
Thomas Rittson
85856d8390 Improve import error messages (#841)
* Display server import errors in modal

* Fix UI text and modal appearance

* Fix loading spinner behaviour

* Fix linting

* Update jslib version
2021-02-24 05:48:30 +10:00
Michael Loßin
a975f6df2b Change all "twofactorauth.org" references to "2fa.directory" (#840) 2021-02-22 12:45:23 -06:00
Limon Monte
d2f1e39a9b chore: bump sweetalert2 to latest (#835) 2021-02-18 16:48:58 -05:00
vachan-maker
8ef7944077 Add Emergency Access to Get Premium (#800)
* Add Emergency Access to Get Premium

* Added Emergency Access to messages.json

* Update premium.component.html

* Added opening tag

* Update messages.json
2021-02-17 10:22:51 -05:00
Thomas Rittson
2a19189f04 Show grantee email in modal if name is null (#831) 2021-02-17 12:07:13 +10:00
Vincent Salucci
cb4f318419 [Send] Update jslib and Send component (#826)
* changes made affected by jslib update

* Update jslib (380b28d -> 0951424)
2021-02-12 10:38:55 -06:00
Thomas Rittson
f239b0cd34 Improved handling of grantor access to organizations after takeover (#820)
* Add emergency access warning for Owners of orgs

* Add master password policy enforcement

* Only show password policy if taking over an Owner

* Fix linting errors

* Fix code style and typos

* Fix implicit 'any' type

* Get grantor policies in separate api call

* Update jslib
2021-02-12 09:58:22 +10:00
Thomas Rittson
9d1b2b9f60 Add warning when importing to organization (#825) 2021-02-12 08:13:29 +10:00
Vincent Salucci
168f9a5525 [Send] Update jslib and init function (#823)
* Update jslib to ee164bebc65aa56e41a122eb4ece8971eb23119b

* Overloaded ngOnInit to call this.load

* Updated import groupings/order based on lint warnings
2021-02-08 16:53:48 -06:00
Kyle Spearrin
13a04976fd send UX improvements (#822)
* send UX improvements

* typo
2021-02-08 16:07:40 -05:00
Matt Gibson
84d03158b5 Add show hide password to send (#821)
* Add password toggle to add-edit

* Fix remove password accessible from disabled send

* Update jslib
2021-02-05 13:23:40 -06:00
Matt Gibson
af7e2edbf0 Implement disable send policy (#819)
* Implement disable send policy

* Update jslib reference

* PR review

* Lower case enterprise policy
2021-02-04 13:08:16 -06:00
Matt Gibson
2e7b88f149 Fix glob processing in npm. Ban single param parens (#818) 2021-02-03 11:41:33 -06:00
Oscar Hinton
5010736ca3 Add support for viewing attachments in emergency access (#814) 2021-02-01 17:37:32 +01:00
Vincent Salucci
986f27294a [Send] Port web base components (#817)
* Initial commit of send base components

* update jslib (9ddec9b -> 859f317)
2021-02-01 10:30:27 -06:00
Matt Gibson
1b8cddede8 Set WebVaultUrl if dev (#813)
* Set WebVaultUrl if dev

* Add new jslib dependency
2021-01-29 13:07:55 -05:00
Addison Beck
66c814296b Lunr search bug (#810)
* changed hrtime library

* updated jslib
2021-01-25 15:11:51 -05:00
Kyle Spearrin
b14cdfcc72 New translations messages.json (Polish) (#809) 2021-01-25 13:10:05 -05:00
Kyle Spearrin
aba2c70ad7 New Crowdin updates (#807)
* New translations messages.json (Romanian)

* New translations messages.json (Polish)

* New translations messages.json (Malayalam)

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

* 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 (Swedish)

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

* New translations messages.json (Russian)

* New translations messages.json (Dutch)

* New translations messages.json (French)

* New translations messages.json (Italian)

* New translations messages.json (Hungarian)

* New translations messages.json (Finnish)

* New translations messages.json (Greek)

* New translations messages.json (German)

* New translations messages.json (Danish)

* New translations messages.json (Czech)

* New translations messages.json (Catalan)

* New translations messages.json (Afrikaans)

* New translations messages.json (Spanish)

* New translations messages.json (English, India)
2021-01-25 12:54:23 -05:00
Chad Scharf
46e9158323 Revert "Correct URL to emergency access doc (#805)" (#808)
This reverts commit 8449cdca75.
2021-01-25 11:59:10 -05:00
Gjermund Jensvoll
8449cdca75 Correct URL to emergency access doc (#805) 2021-01-25 09:49:27 -05:00
Thomas Rittson
8c0bc023b7 Allow U2F on Edge (#804) 2021-01-25 10:21:03 +10:00
Thomas Rittson
bcd488bb87 Clearer keyboard focus on input elements (#780)
* Add visible border/shadow if buttons have :focus

* Fix obscured outlines when elements have :focus
2021-01-25 06:24:09 +10:00
Thomas Rittson
a7b7c716d4 Add branded 404 page to replace Github Pages 404 (#798) 2021-01-25 06:23:18 +10:00
Addison Beck
6b29bb8468 Send Sync Notifications (#799)
* enabled send and added send sync notifications

* updated jslib
2021-01-22 17:03:55 -05:00
Thomas Rittson
3ffc035db3 make inline buttons accessible with tab button (#779) 2021-01-22 09:00:03 +10:00
Oscar Hinton
d93392ba8b Update emergency access user-access link (#797)
The help link for user-access incorrectly linked to the wrong page. Changed to the correct link.
2021-01-21 17:52:20 +01:00
vachan-maker
137b3b3490 Update learn more link for Emergency Access (#796) 2021-01-21 11:35:08 -05:00
Matt Gibson
99c8082866 Fix context copy buttons work only with TOTP present (#794) 2021-01-20 15:15:19 -06:00
Oscar Hinton
24af5aca55 Fix emergency access confirm not working with two-factor enabled (#792) 2021-01-20 17:33:03 +01:00
213 changed files with 23787 additions and 8534 deletions

View File

@@ -45,23 +45,39 @@ jobs:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Login to Azure
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
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.event_name == 'release'
run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || 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.event_name == 'release' || 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
@@ -71,60 +87,78 @@ jobs:
- 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: 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
run: docker tag bitwarden/web bitwarden/web:dev
- name: Tag beta
if: github.event_name == 'release'
run: ./build.sh tag beta
run: docker tag bitwarden/web bitwarden/web:beta
- name: Tag version
if: github.event_name == 'release'
run: ./build.sh tag $($env:RELEASE_TAG_NAME.trimStart('v'))
run: docker tag bitwarden/web bitwarden/web:$($env:RELEASE_TAG_NAME.trimStart('v'))
shell: pwsh
env:
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
- name: List docker images
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
run: docker images
- name: Push rc images
if: github.ref == 'refs/heads/rc'
run: docker push bitwarden/web:rc
env:
DOCKER_CONTENT_TRUST: 1
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
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
run: docker push bitwarden/web:beta
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 latest images
if: github.event_name == 'release'
run: ./build.sh push latest
run: docker push bitwarden/web:latest
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 version images
if: github.event_name == 'release'
run: ./build.sh push $($env:RELEASE_TAG_NAME.trimStart('v'))
run: docker push bitwarden/web:$($env:RELEASE_TAG_NAME.trimStart('v'))
shell: pwsh
env:
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
DOCKER_CONTENT_TRUST: 1
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
- name: Log out of docker
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
if: github.ref == 'refs/heads/master' || github.event_name == 'release' || github.ref == 'refs/heads/rc'
run: docker logout
windows:

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" />
@@ -34,52 +34,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"
}

2
jslib

Submodule jslib updated: 6ac6df75d7...f6d91e2d92

1935
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "bitwarden-web",
"version": "2.18.0",
"version": "2.20.1",
"license": "GPL-3.0",
"repository": "https://github.com/bitwarden/web",
"scripts": {
@@ -13,8 +13,12 @@
"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:dev": "gulp prebuild && cross-env ENV=development webpack",
"build:dev:watch": "gulp prebuild && cross-env ENV=development webpack-dev-server",
"build:qa": "gulp prebuild && cross-env NODE_ENV=production ENV=qa webpack",
"build:qa:watch": "gulp prebuild && cross-env NODE_ENV=production ENV=qa webpack-dev-server",
"build:prod": "gulp prebuild && cross-env NODE_ENV=production ENV=production webpack",
"build:prod:watch": "gulp prebuild && cross-env NODE_ENV=production ENV=production webpack-dev-server",
"build:selfhost": "gulp prebuild && cross-env SELF_HOST=true webpack-dev-server",
"build:selfhost:watch": "gulp prebuild && cross-env SELF_HOST=true webpack-dev-server",
"build:selfhost:prod": "gulp prebuild && cross-env SELF_HOST=true NODE_ENV=production webpack",
@@ -24,23 +28,23 @@
"dist:selfhost": "npm run build:selfhost:prod && gulp postdist",
"deploy": "npm run dist && gh-pages -d build",
"deploy:dev": "npm run dist && gh-pages -d build -r git@github.com:kspearrin/bitwarden-web-dev.git",
"lint": "tslint src/**/*.ts || true",
"lint:fix": "tslint src/**/*.ts --fix"
"lint": "tslint 'src/**/*.ts' || true",
"lint:fix": "tslint 'src/**/*.ts' --fix"
},
"devDependencies": {
"@angular/compiler-cli": "^9.1.12",
"@ngtools/webpack": "^9.1.12",
"@types/jquery": "^3.5.1",
"@types/jquery": "^3.5.5",
"@types/lunr": "^2.3.3",
"@types/node": "^10.17.28",
"@types/node-forge": "^0.7.5",
"@types/papaparse": "^4.5.3",
"@types/node-forge": "^0.9.7",
"@types/papaparse": "^5.2.0",
"@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",
"copy-webpack-plugin": "^5.1.1",
"cross-env": "^5.2.0",
"css-loader": "^1.0.0",
"del": "^3.0.0",
@@ -73,26 +77,26 @@
"@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",
"@microsoft/signalr": "3.1.13",
"@microsoft/signalr-protocol-msgpack": "3.1.13",
"angular2-toaster": "8.0.0",
"angulartics2": "9.1.0",
"big-integer": "1.6.36",
"big-integer": "1.6.48",
"bootstrap": "4.3.1",
"braintree-web-drop-in": "1.13.0",
"browser-process-hrtime": "1.0.0",
"browser-hrtime": "^1.1.8",
"core-js": "2.6.2",
"date-input-polyfill": "^2.14.0",
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git#410a9186cc34663c4913b17d6528067cd3331f1d",
"font-awesome": "4.7.0",
"jquery": "3.4.1",
"jquery": "3.6.0",
"lunr": "2.3.3",
"ngx-infinite-scroll": "7.0.1",
"node-forge": "0.7.6",
"papaparse": "4.6.0",
"node-forge": "0.10.0",
"papaparse": "5.2.0",
"popper.js": "1.14.4",
"qrious": "4.0.2",
"rxjs": "6.6.2",
"sweetalert2": "9.8.1",
"sweetalert2": "10.15.4",
"tslib": "^2.0.1",
"web-animations-js": "2.3.1",
"webcrypto-shim": "0.1.4",

50
src/404.html Normal file
View File

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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">
<link rel="icon" type="image/png" sizes="16x16" href="/images/icons/favicon-16x16.png">
<link rel="mask-icon" href="/images/icons/safari-pinned-tab.svg" color="#175DDC">
<link rel="manifest" href="/manifest.json">
<title>Page not found!</title>
<meta name="description" content="404 Page Not Found">
</head>
<body>
<div class="banner">
<div class="container inner banner">
<div class="row align-items-center">
<div class="col brand">
<i class="fa fa-shield"></i>&nbsp;
<strong>bit</strong>warden</span>
</div>
</div>
</div>
</div>
<div class="container inner content">
<h2>Page not found!</h2>
<p>Sorry, but the page you were looking for could not be found.</p>
<p>
<a href="/">
<img src="/images/404.png" class="img-fluid" alt="404 image" width="80%"/>
</a>
</p>
<p>You can <a href="/">return to the web vault</a>, check our <a href="https://status.bitwarden.com/">status page</a>
or <a href="https://bitwarden.com/contact/">contact us</a>.</p>
</div>
<div class="container footer text-muted content">
© Copyright 2021 Bitwarden, Inc.
</div>
</body>
</html>

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

@@ -36,7 +36,7 @@ export class AcceptEmergencyComponent implements OnInit {
ngOnInit() {
let fired = false;
this.route.queryParams.subscribe(async (qParams) => {
this.route.queryParams.subscribe(async qParams => {
if (fired) {
return;
}

View File

@@ -37,7 +37,7 @@ export class AcceptOrganizationComponent implements OnInit {
ngOnInit() {
let fired = false;
this.route.queryParams.subscribe(async (qParams) => {
this.route.queryParams.subscribe(async qParams => {
if (fired) {
return;
}

View File

@@ -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

@@ -34,7 +34,7 @@ export class LoginComponent extends BaseLoginComponent {
}
async ngOnInit() {
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
if (qParams.email != null && qParams.email.indexOf('@') > -1) {
this.email = qParams.email;
}

View File

@@ -2,7 +2,6 @@ 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';
@@ -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,7 +2,6 @@ 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';
@@ -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

@@ -62,7 +62,7 @@ export class RegisterComponent extends BaseRegisterComponent {
}
async ngOnInit() {
const queryParamsSub = this.route.queryParams.subscribe((qParams) => {
const queryParamsSub = this.route.queryParams.subscribe(qParams => {
this.referenceData = new ReferenceEventRequest();
if (qParams.email != null && qParams.email.indexOf('@') > -1) {
this.email = qParams.email;
@@ -96,8 +96,8 @@ export class RegisterComponent extends BaseRegisterComponent {
const policies = await this.apiService.getPoliciesByToken(invite.organizationId, invite.token,
invite.email, invite.organizationUserId);
if (policies.data != null) {
const policiesData = policies.data.map((p) => new PolicyData(p));
this.policies = policiesData.map((p) => new Policy(p));
const policiesData = policies.data.map(p => new PolicyData(p));
this.policies = policiesData.map(p => new Policy(p));
}
} catch { }
}

View File

@@ -36,7 +36,7 @@ export class SsoComponent extends BaseSsoComponent {
async ngOnInit() {
super.ngOnInit();
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
if (qParams.identifier != null) {
this.identifier = qParams.identifier;
} else {

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

@@ -60,9 +60,12 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
}
async goAfterLogIn() {
const invite = await this.stateService.get<any>('orgInvitation');
if (invite != null) {
this.router.navigate(['accept-organization'], { queryParams: invite });
const orgInvite = await this.stateService.get<any>('orgInvitation');
const emergencyInvite = await this.stateService.get<any>('emergencyInvitation');
if (orgInvite != null) {
this.router.navigate(['accept-organization'], { queryParams: orgInvite });
} else if (emergencyInvite != null) {
this.router.navigate(['accept-emergency'], { queryParams: emergencyInvite });
} else {
const loginRedirect = await this.stateService.get<any>('loginRedirect');
if (loginRedirect != null) {

View File

@@ -26,7 +26,7 @@ export class VerifyEmailTokenComponent implements OnInit {
ngOnInit() {
let fired = false;
this.route.queryParams.subscribe(async (qParams) => {
this.route.queryParams.subscribe(async qParams => {
if (fired) {
return;
}

View File

@@ -8,7 +8,6 @@ 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';
@@ -27,13 +26,13 @@ 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() {
let fired = false;
this.route.queryParams.subscribe(async (qParams) => {
this.route.queryParams.subscribe(async qParams => {
if (fired) {
return;
}
@@ -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,9 +87,10 @@ 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 { LockGuardService } from 'jslib/angular/services/lock-guard.service';
import { UnauthGuardService } from 'jslib/angular/services/unauth-guard.service';
import { Permissions } from 'jslib/enums/permissions';
@@ -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',
@@ -153,11 +158,11 @@ const routes: Routes = [
canActivate: [UnauthGuardService],
data: { titleId: 'deleteAccount' },
},
/*{
{
path: 'send/:sendId/:key',
component: AccessComponent,
data: { title: 'Bitwarden Send' },
},*/
},
],
},
{

View File

@@ -5,11 +5,8 @@ import {
BodyOutputType,
Toast,
ToasterConfig,
ToasterContainerComponent,
ToasterService,
} from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import {
Component,
@@ -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;
@@ -157,7 +157,7 @@ export class AppComponent implements OnDestroy, OnInit {
});
});
this.router.events.subscribe((event) => {
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
const modals = Array.from(document.querySelectorAll('.modal'));
for (const modal of modals) {
@@ -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,6 @@
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';
@@ -114,10 +112,11 @@ import { DeauthorizeSessionsComponent } from './settings/deauthorize-sessions.co
import { DeleteAccountComponent } from './settings/delete-account.component';
import { DomainRulesComponent } from './settings/domain-rules.component';
import { EmergencyAccessAddEditComponent } from './settings/emergency-access-add-edit.component';
import { EmergencyAccessComponent } from './settings/emergency-access.component';
import { EmergencyAccessAttachmentsComponent } from './settings/emergency-access-attachments.component';
import { EmergencyAccessConfirmComponent } from './settings/emergency-access-confirm.component';
import { EmergencyAccessTakeoverComponent } from './settings/emergency-access-takeover.component';
import { EmergencyAccessViewComponent } from './settings/emergency-access-view.component';
import { EmergencyAccessComponent } from './settings/emergency-access.component';
import { EmergencyAddEditComponent } from './settings/emergency-add-edit.component';
import { LinkSsoComponent } from './settings/link-sso.component';
import { OptionsComponent } from './settings/options.component';
@@ -134,8 +133,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';
@@ -166,6 +165,7 @@ 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';
@@ -190,8 +190,8 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
import {
registerLocaleData,
DatePipe,
registerLocaleData,
} from '@angular/common';
import localeCa from '@angular/common/locales/ca';
import localeCs from '@angular/common/locales/cs';
@@ -254,11 +254,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
FormsModule,
AppRoutingModule,
ServicesModule,
Angulartics2Module.forRoot({
pageTracking: {
clearQueryParams: true,
},
}),
ToasterModule.forRoot(),
InfiniteScrollModule,
DragDropModule,
@@ -304,6 +299,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
DomainRulesComponent,
DownloadLicenseComponent,
EmergencyAccessAddEditComponent,
EmergencyAccessAttachmentsComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
@@ -380,6 +376,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
SelectCopyDirective,
SendAddEditComponent,
SendComponent,
SendInfoComponent,
SettingsComponent,
ShareComponent,
SsoComponent,
@@ -395,8 +392,8 @@ registerLocaleData(localeZhTw, 'zh-TW');
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorU2fComponent,
TwoFactorVerifyComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UnsecuredWebsitesReportComponent,
UpdateKeyComponent,
@@ -424,6 +421,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
DeleteAccountComponent,
DeleteOrganizationComponent,
EmergencyAccessAddEditComponent,
EmergencyAccessAttachmentsComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAddEditComponent,
@@ -449,7 +447,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorU2fComponent,
TwoFactorWebAuthnComponent,
TwoFactorYubiKeyComponent,
UpdateKeyComponent,
],

View File

@@ -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

@@ -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

@@ -8,9 +8,9 @@
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/vault">{{'myVault' | i18n}}</a>
</li>
<!--<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/sends">Send</a>
</li>-->
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/sends">{{'send' | i18n}}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/tools">{{'tools' | i18n}}</a>
</li>

View File

@@ -42,7 +42,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
}
document.body.classList.remove('layout_frontend');
this.route.params.subscribe(async (params) => {
this.route.params.subscribe(async params => {
this.organizationId = params.organizationId;
await this.load();
});

View File

@@ -7,7 +7,6 @@ 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';
@@ -15,7 +14,7 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { UserService } from 'jslib/abstractions/user.service';
import { CipherString } from 'jslib/models/domain/cipherString';
import { EncString } from 'jslib/models/domain/encString';
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
import { CollectionRequest } from 'jslib/models/request/collectionRequest';
import { SelectionReadOnlyRequest } from 'jslib/models/request/selectionReadOnlyRequest';
@@ -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);
@@ -56,7 +54,7 @@ export class CollectionAddEditComponent implements OnInit {
this.editMode = this.loading = this.collectionId != null;
if (this.accessGroups) {
const groupsResponse = await this.apiService.getGroups(this.organizationId);
this.groups = groupsResponse.data.map((r) => r).sort(Utils.getSortFunction(this.i18nService, 'name'));
this.groups = groupsResponse.data.map(r => r).sort(Utils.getSortFunction(this.i18nService, 'name'));
}
this.orgKey = await this.cryptoService.getOrgKey(this.organizationId);
@@ -65,11 +63,11 @@ 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) => {
const group = this.groups.filter((g) => !g.accessAll && g.id === s.id);
collection.groups.forEach(s => {
const group = this.groups.filter(g => !g.accessAll && g.id === s.id);
if (group != null && group.length > 0) {
(group[0] as any).checked = true;
(group[0] as any).readOnly = s.readOnly;
@@ -82,7 +80,7 @@ export class CollectionAddEditComponent implements OnInit {
this.title = this.i18nService.t('addCollection');
}
this.groups.forEach((g) => {
this.groups.forEach(g => {
if (g.accessAll) {
(g as any).checked = true;
}
@@ -103,7 +101,7 @@ export class CollectionAddEditComponent implements OnInit {
}
selectAll(select: boolean) {
this.groups.forEach((g) => this.check(g, select));
this.groups.forEach(g => this.check(g, select));
}
async submit() {
@@ -114,8 +112,8 @@ export class CollectionAddEditComponent implements OnInit {
const request = new CollectionRequest();
request.name = (await this.cryptoService.encrypt(this.name, this.orgKey)).encryptedString;
request.externalId = this.externalId;
request.groups = this.groups.filter((g) => (g as any).checked && !g.accessAll)
.map((g) => new SelectionReadOnlyRequest(g.id, !!(g as any).readOnly, !!(g as any).hidePasswords));
request.groups = this.groups.filter(g => (g as any).checked && !g.accessAll)
.map(g => new SelectionReadOnlyRequest(g.id, !!(g as any).readOnly, !!(g as any).hidePasswords));
try {
if (this.editMode) {
@@ -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,7 +8,6 @@ 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';
@@ -52,15 +51,15 @@ 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) => {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
await this.load();
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
this.searchText = qParams.search;
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();
@@ -77,7 +76,7 @@ export class CollectionsComponent implements OnInit {
} else {
response = await this.apiService.getUserCollections();
}
const collections = response.data.filter((c) => c.organizationId === this.organizationId).map((r) =>
const collections = response.data.filter(c => c.organizationId === this.organizationId).map(r =>
new Collection(new CollectionData(r as CollectionDetailsResponse)));
this.collections = await this.collectionService.decryptMany(collections);
this.resetPaging();
@@ -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

@@ -50,7 +50,7 @@ export class EntityEventsComponent implements OnInit {
async load() {
if (this.showUser) {
const response = await this.apiService.getOrganizationUsers(this.organizationId);
response.data.forEach((u) => {
response.data.forEach(u => {
const name = u.name == null || u.name.trim() === '' ? u.email : u.name;
this.orgUsersIdMap.set(u.id, { name: name, email: u.email });
this.orgUsersUserIdMap.set(u.userId, { name: name, email: u.email });
@@ -94,7 +94,7 @@ export class EntityEventsComponent implements OnInit {
} catch { }
this.continuationToken = response.continuationToken;
const events = response.data.map((r) => {
const events = response.data.map(r => {
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
const eventInfo = this.eventService.getEventInfo(r);
const user = this.showUser && userId != null && this.orgUsersUserIdMap.has(userId) ?

View File

@@ -7,7 +7,6 @@ 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';
@@ -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();
@@ -51,7 +50,7 @@ export class EntityUsersComponent implements OnInit {
get users() {
if (this.showSelected) {
return this.allUsers.filter((u) => (u as any).checked);
return this.allUsers.filter(u => (u as any).checked);
} else {
return this.allUsers;
}
@@ -59,12 +58,12 @@ export class EntityUsersComponent implements OnInit {
async loadUsers() {
const users = await this.apiService.getOrganizationUsers(this.organizationId);
this.allUsers = users.data.map((r) => r).sort(Utils.getSortFunction(this.i18nService, 'email'));
this.allUsers = users.data.map(r => r).sort(Utils.getSortFunction(this.i18nService, 'email'));
if (this.entity === 'group') {
const response = await this.apiService.getGroupUsers(this.organizationId, this.entityId);
if (response != null && users.data.length > 0) {
response.forEach((s) => {
const user = users.data.filter((u) => u.id === s);
response.forEach(s => {
const user = users.data.filter(u => u.id === s);
if (user != null && user.length > 0) {
(user[0] as any).checked = true;
}
@@ -73,8 +72,8 @@ export class EntityUsersComponent implements OnInit {
} else if (this.entity === 'collection') {
const response = await this.apiService.getCollectionUsers(this.organizationId, this.entityId);
if (response != null && users.data.length > 0) {
response.forEach((s) => {
const user = users.data.filter((u) => !u.accessAll && u.id === s.id);
response.forEach(s => {
const user = users.data.filter(u => !u.accessAll && u.id === s.id);
if (user != null && user.length > 0) {
(user[0] as any).checked = true;
(user[0] as any).readOnly = s.readOnly;
@@ -84,7 +83,7 @@ export class EntityUsersComponent implements OnInit {
}
}
this.allUsers.forEach((u) => {
this.allUsers.forEach(u => {
if (this.entity === 'collection' && u.accessAll) {
(u as any).checked = true;
}
@@ -121,17 +120,14 @@ export class EntityUsersComponent implements OnInit {
async submit() {
try {
if (this.entity === 'group') {
const selections = this.users.filter((u) => (u as any).checked).map((u) => u.id);
const selections = this.users.filter(u => (u as any).checked).map(u => u.id);
this.formPromise = this.apiService.putGroupUsers(this.organizationId, this.entityId, selections);
} else {
const selections = this.users.filter((u) => (u as any).checked && !u.accessAll)
.map((u) => new SelectionReadOnlyRequest(u.id, !!(u as any).readOnly, !!(u as any).hidePasswords));
const selections = this.users.filter(u => (u as any).checked && !u.accessAll)
.map(u => new SelectionReadOnlyRequest(u.id, !!(u as any).readOnly, !!(u as any).hidePasswords));
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

@@ -39,7 +39,7 @@ export class EventsComponent implements OnInit {
private router: Router) { }
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
const organization = await this.userService.getOrganization(this.organizationId);
if (organization == null || !organization.useEvents) {
@@ -55,7 +55,7 @@ export class EventsComponent implements OnInit {
async load() {
const response = await this.apiService.getOrganizationUsers(this.organizationId);
response.data.forEach((u) => {
response.data.forEach(u => {
const name = u.name == null || u.name.trim() === '' ? u.email : u.name;
this.orgUsersIdMap.set(u.id, { name: name, email: u.email });
this.orgUsersUserIdMap.set(u.userId, { name: name, email: u.email });
@@ -92,7 +92,7 @@ export class EventsComponent implements OnInit {
} catch { }
this.continuationToken = response.continuationToken;
const events = response.data.map((r) => {
const events = response.data.map(r => {
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
const eventInfo = this.eventService.getEventInfo(r);
const user = userId != null && this.orgUsersUserIdMap.has(userId) ?

View File

@@ -7,7 +7,6 @@ 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';
@@ -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;
@@ -58,8 +57,8 @@ export class GroupAddEditComponent implements OnInit {
this.name = group.name;
this.externalId = group.externalId;
if (group.collections != null && this.collections != null) {
group.collections.forEach((s) => {
const collection = this.collections.filter((c) => c.id === s.id);
group.collections.forEach(s => {
const collection = this.collections.filter(c => c.id === s.id);
if (collection != null && collection.length > 0) {
(collection[0] as any).checked = true;
collection[0].readOnly = s.readOnly;
@@ -77,7 +76,7 @@ export class GroupAddEditComponent implements OnInit {
async loadCollections() {
const response = await this.apiService.getCollections(this.organizationId);
const collections = response.data.map((r) =>
const collections = response.data.map(r =>
new Collection(new CollectionData(r as CollectionDetailsResponse)));
this.collections = await this.collectionService.decryptMany(collections);
}
@@ -90,7 +89,7 @@ export class GroupAddEditComponent implements OnInit {
}
selectAll(select: boolean) {
this.collections.forEach((c) => this.check(c, select));
this.collections.forEach(c => this.check(c, select));
}
async submit() {
@@ -99,8 +98,8 @@ export class GroupAddEditComponent implements OnInit {
request.externalId = this.externalId;
request.accessAll = this.access === 'all';
if (!request.accessAll) {
request.collections = this.collections.filter((c) => (c as any).checked)
.map((c) => new SelectionReadOnlyRequest(c.id, !!c.readOnly, !!c.hidePasswords));
request.collections = this.collections.filter(c => (c as any).checked)
.map(c => new SelectionReadOnlyRequest(c.id, !!c.readOnly, !!c.hidePasswords));
}
try {
@@ -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,7 +11,6 @@ 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';
@@ -49,12 +48,12 @@ 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) => {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
const organization = await this.userService.getOrganization(this.organizationId);
if (organization == null || !organization.useGroups) {
@@ -62,7 +61,7 @@ export class GroupsComponent implements OnInit {
return;
}
await this.load();
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
this.searchText = qParams.search;
if (queryParamsSub != null) {
queryParamsSub.unsubscribe();
@@ -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

@@ -21,7 +21,7 @@ export class ManageComponent implements OnInit {
constructor(private route: ActivatedRoute, private userService: UserService) { }
ngOnInit() {
this.route.parent.params.subscribe(async (params) => {
this.route.parent.params.subscribe(async params => {
this.organization = await this.userService.getOrganization(params.organizationId);
this.accessPolicies = this.organization.usePolicies;
this.accessEvents = this.organization.useEvents;

View File

@@ -11,8 +11,8 @@ import {
} from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { ValidationService } from 'jslib/angular/services/validation.service';
import { ConstantsService } from 'jslib/services/constants.service';
import { ApiService } from 'jslib/abstractions/api.service';
@@ -70,13 +70,13 @@ 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) { }
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
const organization = await this.userService.getOrganization(this.organizationId);
if (!organization.canManageUsers) {
@@ -87,10 +87,10 @@ export class PeopleComponent implements OnInit {
this.accessGroups = organization.useGroups;
await this.load();
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
this.searchText = qParams.search;
if (qParams.viewEvents != null) {
const user = this.users.filter((u) => u.id === qParams.viewEvents);
const user = this.users.filter(u => u.id === qParams.viewEvents);
if (user.length > 0 && user[0].status === OrganizationUserStatusType.Confirmed) {
this.events(user[0]);
}
@@ -107,7 +107,7 @@ export class PeopleComponent implements OnInit {
this.statusMap.clear();
this.allUsers = response.data != null && response.data.length > 0 ? response.data : [];
this.allUsers.sort(Utils.getSortFunction(this.i18nService, 'email'));
this.allUsers.forEach((u) => {
this.allUsers.forEach(u => {
if (!this.statusMap.has(u.status)) {
this.statusMap.set(u.status, [u]);
} else {
@@ -231,7 +231,6 @@ export class PeopleComponent implements OnInit {
try {
await this.apiService.deleteOrganizationUser(this.organizationId, user.id);
this.analytics.eventTrack.next({ action: 'Deleted User' });
this.toasterService.popAsync('success', null, this.i18nService.t('removedUserId', user.name || user.email));
this.removeUser(user);
} catch { }
@@ -243,7 +242,6 @@ export class PeopleComponent implements OnInit {
}
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));
this.actionPromise = null;
}
@@ -258,6 +256,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 +289,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 +305,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 +358,8 @@ export class PeopleComponent implements OnInit {
return !searching && this.users && this.users.length > this.pageSize;
}
private async doConfirmation(user: OrganizationUserUserDetailsResponse) {
private async doConfirmation(user: OrganizationUserUserDetailsResponse, publicKey: Uint8Array) {
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
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 { }
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
const request = new OrganizationUserConfirmRequest();
request.key = key.encryptedString;

View File

@@ -12,8 +12,8 @@ import {
import { PolicyType } from 'jslib/enums/policyType';
import { ApiService } from 'jslib/abstractions/api.service';
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';
@@ -51,7 +51,7 @@ export class PoliciesComponent implements OnInit {
private router: Router, private environmentService: EnvironmentService) { }
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
const organization = await this.userService.getOrganization(this.organizationId);
if (organization == null || !organization.usePolicies) {
@@ -102,11 +102,25 @@ export class PoliciesComponent implements OnInit {
enabled: false,
display: true,
},
{
name: this.i18nService.t('disableSend'),
description: this.i18nService.t('disableSendPolicyDesc'),
type: PolicyType.DisableSend,
enabled: false,
display: true,
},
{
name: this.i18nService.t('sendOptions'),
description: this.i18nService.t('sendOptionsPolicyDesc'),
type: PolicyType.SendOptions,
enabled: false,
display: true,
},
];
await this.load();
// Handle policies component launch from Event message
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
if (qParams.policyId != null) {
const policyIdFromEvents: string = qParams.policyId;
for (const orgPolicy of this.orgPolicies) {
@@ -140,10 +154,10 @@ export class PoliciesComponent implements OnInit {
async load() {
const response = await this.apiService.getPolicies(this.organizationId);
this.orgPolicies = response.data != null && response.data.length > 0 ? response.data : [];
this.orgPolicies.forEach((op) => {
this.orgPolicies.forEach(op => {
this.policiesEnabledMap.set(op.type, op.enabled);
});
this.policies.forEach((p) => {
this.policies.forEach(p => {
p.enabled = this.policiesEnabledMap.has(p.type) && this.policiesEnabledMap.get(p.type);
});
this.loading = false;

View File

@@ -32,6 +32,12 @@
<app-callout type="warning" *ngIf="type === policyType.PersonalOwnership">
{{'personalOwnershipExemption' | i18n}}
</app-callout>
<app-callout type="warning" *ngIf="type === policyType.DisableSend">
{{'disableSendExemption' | i18n}}
</app-callout>
<app-callout type="warning" *ngIf="type === policyType.SendOptions">
{{'sendOptionsExemption' | i18n}}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [(ngModel)]="enabled"
@@ -144,6 +150,14 @@
<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>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">

View File

@@ -7,7 +7,6 @@ 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';
@@ -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,13 @@ export class PolicyEditComponent implements OnInit {
passGenCapitalize?: boolean;
passGenIncludeNumber?: boolean;
// Send options
sendDisableHideEmail?: 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 +113,9 @@ 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;
default:
break;
}
@@ -159,13 +162,17 @@ export class PolicyEditComponent implements OnInit {
requireSpecial: this.masterPassRequireSpecial,
};
break;
case PolicyType.SendOptions:
request.data = {
disableHideEmail: this.sendDisableHideEmail,
};
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

@@ -178,6 +178,15 @@
</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">
<label class="form-check-label font-weight-normal" for="manageResetPassword">
{{'manageResetPassword' | i18n}}
</label>
</div>
</div>
</div>
</div>
</div>
@@ -255,8 +264,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,7 +7,6 @@ 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';
@@ -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;
@@ -72,8 +71,8 @@ export class UserAddEditComponent implements OnInit {
this.permissions = user.permissions;
}
if (user.collections != null && this.collections != null) {
user.collections.forEach((s) => {
const collection = this.collections.filter((c) => c.id === s.id);
user.collections.forEach(s => {
const collection = this.collections.filter(c => c.id === s.id);
if (collection != null && collection.length > 0) {
(collection[0] as any).checked = true;
collection[0].readOnly = s.readOnly;
@@ -91,7 +90,7 @@ export class UserAddEditComponent implements OnInit {
async loadCollections() {
const response = await this.apiService.getCollections(this.organizationId);
const collections = response.data.map((r) =>
const collections = response.data.map(r =>
new Collection(new CollectionData(r as CollectionDetailsResponse)));
this.collections = await this.collectionService.decryptMany(collections);
}
@@ -104,7 +103,7 @@ export class UserAddEditComponent implements OnInit {
}
selectAll(select: boolean) {
this.collections.forEach((c) => this.check(c, select));
this.collections.forEach(c => this.check(c, select));
}
setRequestPermissions(p: PermissionsApi, clearPermissions: boolean) {
@@ -138,14 +137,17 @@ export class UserAddEditComponent implements OnInit {
p.manageUsers = clearPermissions ?
false :
this.permissions.manageUsers;
p.manageResetPassword = clearPermissions ?
false :
this.permissions.manageResetPassword;
return p;
}
async submit() {
let collections: SelectionReadOnlyRequest[] = null;
if (this.access !== 'all') {
collections = this.collections.filter((c) => (c as any).checked)
.map((c) => new SelectionReadOnlyRequest(c.id, !!c.readOnly, !!c.hidePasswords));
collections = this.collections.filter(c => (c as any).checked)
.map(c => new SelectionReadOnlyRequest(c.id, !!c.readOnly, !!c.hidePasswords));
}
try {
@@ -167,7 +169,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 +190,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">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-header">
<h2 class="modal-title" id="confirmUserTitle">
{{'confirmUser' | i18n}}

View File

@@ -6,18 +6,12 @@ import {
Output,
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { ConstantsService } from 'jslib/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 { StorageService } from 'jslib/abstractions/storage.service';
import { OrganizationUserConfirmRequest } from 'jslib/models/request/organizationUserConfirmRequest';
import { Utils } from 'jslib/misc/utils';
@Component({
@@ -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

@@ -7,7 +7,6 @@ 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';
@@ -32,11 +31,11 @@ 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);
const groups = groupsResponse.data.map((r) => r);
const groups = groupsResponse.data.map(r => r);
groups.sort(Utils.getSortFunction(this.i18nService, 'name'));
this.groups = groups;
@@ -44,8 +43,8 @@ export class UserGroupsComponent implements OnInit {
const userGroups = await this.apiService.getOrganizationUserGroups(
this.organizationId, this.organizationUserId);
if (userGroups != null && this.groups != null) {
userGroups.forEach((ug) => {
const group = this.groups.filter((g) => g.id === ug);
userGroups.forEach(ug => {
const group = this.groups.filter(g => g.id === ug);
if (group != null && group.length > 0) {
(group[0] as any).checked = true;
}
@@ -64,18 +63,17 @@ export class UserGroupsComponent implements OnInit {
}
selectAll(select: boolean) {
this.groups.forEach((g) => this.check(g, select));
this.groups.forEach(g => this.check(g, select));
}
async submit() {
const request = new OrganizationUserUpdateGroupsRequest();
request.groupIds = this.groups.filter((g) => (g as any).checked).map((g) => g.id);
request.groupIds = this.groups.filter(g => (g as any).checked).map(g => g.id);
try {
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

@@ -7,7 +7,6 @@ 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';
@@ -46,13 +45,12 @@ 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) { }
async ngOnInit() {
this.selfHosted = this.platformUtilsService.isSelfHost();
this.route.parent.parent.params.subscribe(async (params) => {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
try {
this.org = await this.apiService.getOrganization(this.organizationId);
@@ -73,7 +71,6 @@ export class AccountComponent {
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 +78,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,7 +12,6 @@ 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';
@@ -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

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

View File

@@ -2,7 +2,6 @@ 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';
@@ -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

@@ -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,7 +5,6 @@ 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';
@@ -18,14 +17,13 @@ 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() {
this.route.parent.parent.params.subscribe(async (params) => {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
await this.load();
this.firstLoaded = true;

View File

@@ -5,7 +5,6 @@ import {
import { ActivatedRoute } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { OrganizationSubscriptionResponse } from 'jslib/models/response/organizationSubscriptionResponse';
@@ -38,14 +37,13 @@ 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();
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
await this.load();
this.firstLoaded = true;
@@ -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

@@ -16,7 +16,7 @@ export class SettingsComponent {
private platformUtilsService: PlatformUtilsService) { }
ngOnInit() {
this.route.parent.params.subscribe(async (params) => {
this.route.parent.params.subscribe(async params => {
this.selfHosted = await this.platformUtilsService.isSelfHost();
const organization = await this.userService.getOrganization(params.organizationId);
this.access2fa = organization.use2fa;

View File

@@ -26,7 +26,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent {
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
await super.ngOnInit();
});

View File

@@ -23,7 +23,7 @@ export class ExportComponent extends BaseExportComponent {
}
ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
});
}

View File

@@ -13,8 +13,8 @@ import {
ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent,
} from '../../tools/exposed-passwords-report.component';
import { CipherView } from 'jslib/models/view/cipherView';
import { Cipher } from 'jslib/models/domain/cipher';
import { CipherView } from 'jslib/models/view/cipherView';
@Component({
selector: 'app-exposed-passwords-report',
@@ -30,7 +30,7 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC
}
ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.route.parent.parent.params.subscribe(async params => {
this.organization = await this.userService.getOrganization(params.organizationId);
this.manageableCiphers = await this.cipherService.getAll();
super.ngOnInit();

View File

@@ -5,10 +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 { ImportComponent as BaseImportComponent } from '../../tools/import.component';
@@ -17,17 +18,32 @@ import { ImportComponent as BaseImportComponent } from '../../tools/import.compo
templateUrl: '../../tools/import.component.html',
})
export class ImportComponent extends BaseImportComponent {
constructor(i18nService: I18nService, analytics: Angulartics2,
toasterService: ToasterService, importService: ImportService,
router: Router, private route: ActivatedRoute) {
super(i18nService, analytics, toasterService, importService, router);
organizationName: string;
constructor(i18nService: I18nService, toasterService: ToasterService,
importService: ImportService, router: Router, private route: ActivatedRoute,
platformUtilsService: PlatformUtilsService,
private userService: UserService) {
super(i18nService, toasterService, importService, router, platformUtilsService);
}
ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
this.successNavigate = ['organizations', this.organizationId, 'vault'];
super.ngOnInit();
});
const organization = await this.userService.getOrganization(this.organizationId);
this.organizationName = organization.name;
}
async submit() {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('importWarning', this.organizationName),
this.i18nService.t('warning'), this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return;
}
super.submit();
}
}

View File

@@ -26,7 +26,7 @@ export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorRepor
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.route.parent.parent.params.subscribe(async params => {
this.organization = await this.userService.getOrganization(params.organizationId);
await super.ngOnInit();
});

View File

@@ -30,7 +30,7 @@ export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportCom
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.route.parent.parent.params.subscribe(async params => {
this.organization = await this.userService.getOrganization(params.organizationId);
this.manageableCiphers = await this.cipherService.getAll();
await super.ngOnInit();

View File

@@ -19,7 +19,7 @@ export class ToolsComponent {
private messagingService: MessagingService) { }
ngOnInit() {
this.route.parent.params.subscribe(async (params) => {
this.route.parent.params.subscribe(async params => {
this.organization = await this.userService.getOrganization(params.organizationId);
// TODO: Maybe we want to just make sure they are not on a free plan? Just compare useTotp for now
// since all paid plans include useTotp

View File

@@ -26,7 +26,7 @@ export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesRepor
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.route.parent.parent.params.subscribe(async params => {
this.organization = await this.userService.getOrganization(params.organizationId);
await super.ngOnInit();
});

View File

@@ -31,7 +31,7 @@ export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportCompone
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.route.parent.parent.params.subscribe(async params => {
this.organization = await this.userService.getOrganization(params.organizationId);
this.manageableCiphers = await this.cipherService.getAll();
await super.ngOnInit();

View File

@@ -20,12 +20,13 @@ import { AttachmentsComponent as BaseAttachmentsComponent } from '../../vault/at
templateUrl: '../../vault/attachments.component.html',
})
export class AttachmentsComponent extends BaseAttachmentsComponent {
viewOnly = false;
organization: Organization;
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) {

View File

@@ -5,7 +5,6 @@ import {
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { ApiService } from 'jslib/abstractions/api.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
@@ -33,12 +32,11 @@ export class CiphersComponent extends BaseCiphersComponent {
protected allCiphers: CipherView[] = [];
constructor(searchService: SearchService, analytics: Angulartics2,
toasterService: ToasterService, i18nService: I18nService,
constructor(searchService: SearchService, toasterService: ToasterService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, cipherService: CipherService,
private apiService: ApiService, eventService: EventService, totpService: TotpService, userService: UserService) {
super(searchService, analytics, toasterService, i18nService, platformUtilsService,
cipherService, eventService, totpService, userService);
super(searchService, toasterService, i18nService, platformUtilsService, cipherService,
eventService, totpService, userService);
}
async load(filter: (cipher: CipherView) => boolean = null) {
@@ -48,7 +46,8 @@ export class CiphersComponent extends BaseCiphersComponent {
}
this.accessEvents = this.organization.useEvents;
this.allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id);
this.applyFilter(filter);
await this.searchService.indexCiphers(this.organization.id, this.allCiphers);
await this.applyFilter(filter);
this.loaded = true;
}
@@ -62,28 +61,8 @@ export class CiphersComponent extends BaseCiphersComponent {
}
async search(timeout: number = null) {
if (!this.organization.canManageAllCollections) {
return super.search(timeout);
}
this.searchPending = false;
let filteredCiphers = this.allCiphers;
if (this.searchText == null || this.searchText.trim().length < 2) {
this.ciphers = filteredCiphers.filter((c) => {
if (c.isDeleted !== this.deleted) {
return false;
}
return this.filter == null || this.filter(c);
});
} else {
if (this.filter != null) {
filteredCiphers = filteredCiphers.filter(this.filter);
}
this.ciphers = this.searchService.searchCiphersBasic(filteredCiphers, this.searchText, this.deleted);
}
await this.resetPaging();
super.search(timeout, this.allCiphers);
}
events(c: CipherView) {
this.onEventsClicked.emit(c);
}

View File

@@ -36,7 +36,7 @@ export class GroupingsComponent extends BaseGroupingsComponent {
const collections = await this.apiService.getCollections(this.organization.id);
if (collections != null && collections.data != null && collections.data.length) {
const collectionDomains = collections.data.map((r) =>
const collectionDomains = collections.data.map(r =>
new Collection(new CollectionData(r as CollectionDetailsResponse)));
this.collections = await this.collectionService.decryptMany(collectionDomains);
} else {

View File

@@ -29,6 +29,9 @@
</button>
</div>
</div>
<app-callout type="warning" *ngIf="deleted" icon="fa-warning">
{{trashCleanupWarning}}
</app-callout>
<app-org-vault-ciphers (onCipherClicked)="editCipher($event)"
(onAttachmentsClicked)="editCipherAttachments($event)" (onAddCipher)="addCipher()"
(onCollectionsClicked)="editCipherCollections($event)" (onEventsClicked)="viewEvents($event)"

View File

@@ -15,6 +15,7 @@ import {
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SyncService } from 'jslib/abstractions/sync.service';
import { UserService } from 'jslib/abstractions/user.service';
@@ -52,6 +53,7 @@ export class VaultComponent implements OnInit, OnDestroy {
collectionId: string = null;
type: CipherType = null;
deleted: boolean = false;
trashCleanupWarning: string = null;
modal: ModalComponent = null;
@@ -59,15 +61,20 @@ export class VaultComponent implements OnInit, OnDestroy {
private router: Router, private changeDetectorRef: ChangeDetectorRef,
private syncService: SyncService, private i18nService: I18nService,
private componentFactoryResolver: ComponentFactoryResolver, private messagingService: MessagingService,
private broadcasterService: BroadcasterService, private ngZone: NgZone) { }
private broadcasterService: BroadcasterService, private ngZone: NgZone,
private platformUtilsService: PlatformUtilsService) { }
ngOnInit() {
const queryParams = this.route.parent.params.subscribe(async (params) => {
this.trashCleanupWarning = this.i18nService.t(
this.platformUtilsService.isSelfHost() ? 'trashCleanupWarningSelfHosted' : 'trashCleanupWarning'
);
const queryParams = this.route.parent.params.subscribe(async params => {
this.organization = await this.userService.getOrganization(params.organizationId);
this.groupingsComponent.organization = this.organization;
this.ciphersComponent.organization = this.organization;
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search;
if (!this.organization.canManageAllCollections) {
await this.syncService.fullSync(false);
@@ -110,7 +117,7 @@ export class VaultComponent implements OnInit, OnDestroy {
}
if (qParams.viewEvents != null) {
const cipher = this.ciphersComponent.ciphers.filter((c) => c.id === qParams.viewEvents);
const cipher = this.ciphersComponent.ciphers.filter(c => c.id === qParams.viewEvents);
if (cipher.length > 0) {
this.viewEvents(cipher[0]);
}
@@ -235,7 +242,7 @@ export class VaultComponent implements OnInit, OnDestroy {
if (this.organization.canManageAllCollections) {
childComponent.collectionIds = cipher.collectionIds;
childComponent.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
childComponent.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
}
childComponent.organization = this.organization;
childComponent.cipherId = cipher.id;
@@ -254,7 +261,7 @@ export class VaultComponent implements OnInit, OnDestroy {
component.organizationId = this.organization.id;
component.type = this.type;
if (this.organization.canManageAllCollections) {
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
}
if (this.collectionId != null) {
component.collectionIds = [this.collectionId];
@@ -297,7 +304,7 @@ export class VaultComponent implements OnInit, OnDestroy {
component.cloneMode = true;
component.organizationId = this.organization.id;
if (this.organization.canManageAllCollections) {
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
component.collections = this.groupingsComponent.collections.filter(c => !c.readOnly);
}
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
// in the add-edit componenet

View File

@@ -19,4 +19,5 @@ if (process.env.ENV === 'production') {
// Other polyfills
require('whatwg-fetch');
require('webcrypto-shim');
require('date-input-polyfill');
/* tslint:enable */

View File

@@ -1,7 +1,20 @@
<form #form (ngSubmit)="load()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<div class="row justify-content-center mt-5">
<div class="col-12">
<p class="lead text-center mb-4">Bitwarden Send</p>
</div>
<div class="col-12 text-center" *ngIf="creatorIdentifier != null">
<p>{{'sendCreatorIdentifier' | i18n: creatorIdentifier }}</p>
</div>
<div class="col-8" *ngIf="hideEmail">
<app-callout type="warning" title="{{'warning' | i18n}}">
{{'viewSendHiddenEmailWarning' | i18n }}
<a href="https://bitwarden.com/help/article/receive-send/" target="_blank">{{'learnMore' | i18n}}</a>.
</app-callout>
</div>
</div>
<div class="row justify-content-center">
<div class="col-5">
<div class="card d-block">
<div class="card-body" *ngIf="loading" class="text-center">
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}"
@@ -54,12 +67,26 @@
<!-- File -->
<ng-container *ngIf="send.type === sendType.File">
<p>{{send.file.fileName}}</p>
<button class="btn btn-primary btn-block" type="button" (click)="download()">
<button class="btn btn-primary btn-block" type="button" (click)="download()" *ngIf="!downloading">
<i class="fa fa-download" aria-hidden="true"></i>
{{'downloadFile' | i18n}} ({{send.file.sizeName}})</button>
<button class="btn btn-primary btn-block" type="button" *ngIf="downloading" disabled="true">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
</button>
</ng-container>
<p *ngIf="expirationDate" class="text-center text-muted">Expires:
{{expirationDate | date: 'medium'}}</p>
</div>
</div>
</div>
<div class="col-12 text-center mt-5 text-muted">
<p class="mb-0">{{'sendAccessTaglineProductDesc' | i18n}}<br>
{{'sendAccessTaglineLearnMore' | i18n}} <a
href="https://www.bitwarden.com/products/send?source=web-vault" target="_blank">Bitwarden Send</a>
{{'sendAccessTaglineOr' | i18n}} <a
href="https://vault.bitwarden.com/#/register" target="_blank">{{'sendAccessTaglineSignUp' | i18n}}</a>
{{'sendAccessTaglineTryToday' | i18n}}
</p>
</div>
</div>
</form>

View File

@@ -13,8 +13,8 @@ import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { Utils } from 'jslib/misc/utils';
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
import { SendAccess } from 'jslib/models/domain/sendAccess';
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
import { SendAccessView } from 'jslib/models/view/sendAccessView';
@@ -39,10 +39,12 @@ export class AccessComponent implements OnInit {
showText = false;
unavailable = false;
error = false;
hideEmail = false;
private id: string;
private key: string;
private decKey: SymmetricCryptoKey;
private accessRequest: SendAccessRequest;
constructor(private i18nService: I18nService, private cryptoFunctionService: CryptoFunctionService,
private apiService: ApiService, private platformUtilsService: PlatformUtilsService,
@@ -56,8 +58,22 @@ export class AccessComponent implements OnInit {
return this.showText ? this.send.text.text : this.send.text.maskedText;
}
get expirationDate() {
if (this.send == null || this.send.expirationDate == null) {
return null;
}
return this.send.expirationDate;
}
get creatorIdentifier() {
if (this.send == null || this.send.creatorIdentifier == null) {
return null;
}
return this.send.creatorIdentifier;
}
ngOnInit() {
this.route.params.subscribe(async (params) => {
this.route.params.subscribe(async params => {
this.id = params.sendId;
this.key = params.key;
if (this.key == null || this.id == null) {
@@ -76,8 +92,16 @@ export class AccessComponent implements OnInit {
return;
}
const downloadData = await this.apiService.getSendFileDownloadData(this.send, this.accessRequest);
if (Utils.isNullOrWhitespace(downloadData.url)) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('missingSendFile'));
return;
}
this.downloading = true;
const response = await fetch(new Request(this.send.file.url, { cache: 'no-store' }));
const response = await fetch(new Request(downloadData.url, { cache: 'no-store' }));
if (response.status !== 200) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred'));
this.downloading = false;
@@ -108,18 +132,19 @@ export class AccessComponent implements OnInit {
async load() {
this.unavailable = false;
this.error = false;
this.hideEmail = false;
const keyArray = Utils.fromUrlB64ToArray(this.key);
const accessRequest = new SendAccessRequest();
this.accessRequest = new SendAccessRequest();
if (this.password != null) {
const passwordHash = await this.cryptoFunctionService.pbkdf2(this.password, keyArray, 'sha256', 100000);
accessRequest.password = Utils.fromBufferToB64(passwordHash);
this.accessRequest.password = Utils.fromBufferToB64(passwordHash);
}
try {
let sendResponse: SendAccessResponse = null;
if (this.loading) {
sendResponse = await this.apiService.postSendAccess(this.id, accessRequest);
sendResponse = await this.apiService.postSendAccess(this.id, this.accessRequest);
} else {
this.formPromise = this.apiService.postSendAccess(this.id, accessRequest);
this.formPromise = this.apiService.postSendAccess(this.id, this.accessRequest);
sendResponse = await this.formPromise;
}
this.passwordRequired = false;
@@ -139,5 +164,6 @@ export class AccessComponent implements OnInit {
}
}
this.loading = false;
this.hideEmail = this.creatorIdentifier == null && !this.passwordRequired && !this.loading && !this.unavailable;
}
}

View File

@@ -9,32 +9,48 @@
</button>
</div>
<div class="modal-body" *ngIf="send">
<div class="row" *ngIf="!editMode">
<div class="col-6 form-group">
<label for="type">{{'whatTypeOfSend' | i18n}}</label>
<select id="type" name="Type" [(ngModel)]="send.type" class="form-control" appAutofocus
(change)='typeChanged()'>
<option *ngFor="let o of typeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
</div>
<app-callout *ngIf="disableSend">
<span>{{'sendDisabledWarning' | i18n}}</span>
</app-callout>
<app-callout *ngIf="!disableSend && disableHideEmail">
<span>{{'sendOptionsPolicyInEffect' | i18n}}</span>
<ul class="mb-0">
<li>{{'sendDisableHideEmailInEffect' | i18n}}</li>
</ul>
</app-callout>
<div class="row">
<div class="col-6 form-group">
<label for="name">{{'name' | i18n}}</label>
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="send.name" required>
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="send.name" required
[readOnly]="disableSend">
<small class="form-text text-muted">{{'sendNameDesc' | i18n}}</small>
</div>
</div>
<div class="row" *ngIf="!editMode">
<div class="col-6 form-group">
<label>{{'whatTypeOfSend' | i18n}}</label>
<div class="form-check" *ngFor="let o of typeOptions">
<input class="form-check-input" type="radio" [(ngModel)]="send.type" name="Type_{{o.value}}"
id="type_{{o.value}}" [value]="o.value" (change)="typeChanged(o)"
[checked]="send.type === o.value">
<label class="form-check-label" for="type_{{o.value}}">
{{o.name}}
</label>
</div>
</div>
</div>
<!-- Text -->
<ng-container *ngIf="send.type === sendType.Text">
<div class="form-group">
<label for="text">{{'sendTypeText' | i18n}}</label>
<textarea id="text" name="Text.Text" rows="6" [(ngModel)]="send.text.text"
class="form-control"></textarea>
<textarea id="text" name="Text.Text" rows="6" [(ngModel)]="send.text.text" class="form-control"
[readOnly]="disableSend"></textarea>
<small class="form-text text-muted">{{'sendTextDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" [(ngModel)]="send.text.hidden"
id="text-hidden" name="Text.Hidden">
id="text-hidden" name="Text.Hidden" [disabled]="disableSend">
<label class="form-check-label" for="text-hidden">{{'textHiddenByDefault' | i18n}}</label>
</div>
</div>
@@ -48,96 +64,178 @@
</div>
<div *ngIf="!editMode">
<label for="file">{{'file' | i18n}}</label>
<input type="file" id="file" class="form-control-file" name="file" required>
<small class="form-text text-muted">{{'maxFileSize' | i18n}}</small>
<input type="file" id="file" class="form-control-file" name="file" required
[disabled]="disableSend">
<small class="form-text text-muted">{{'sendFileDesc' | i18n}} {{'maxFileSize' |
i18n}}</small>
</div>
</div>
</ng-container>
<h3 class="mt-5">{{'options' | i18n}}</h3>
<div class="row">
<div class="col-6 form-group">
<label for="deletionDate">{{'deletionDate' | i18n}}</label>
<div *ngIf="!editMode">
<select id="deletionDate" name="DeletionDateSelect" [(ngModel)]="deletionDateSelect"
class="form-control" required>
<option *ngFor="let o of deletionDateOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<input id="deletionDateCustom" class="form-control mt-1" type="datetime-local"
name="DeletionDate" [(ngModel)]="deletionDate" required *ngIf="deletionDateSelect === 0"
placeholder="MM/DD/YYYY HH:MM AM/PM">
</div>
<div *ngIf="editMode">
<input id="deletionDate" class="form-control" type="datetime-local" name="DeletionDate"
[(ngModel)]="deletionDate" required placeholder="MM/DD/YYYY HH:MM AM/PM">
</div>
<div class="form-text text-muted small">{{'deletionDateDesc' | i18n}}</div>
</div>
<div class="col-6 form-group">
<div class="d-flex">
<label for="expirationDate">{{'expirationDate' | i18n}}</label>
<a href="#" appStopClick (click)="clearExpiration()" class="ml-auto" *ngIf="editMode">
{{'clear' | i18n}}
</a>
</div>
<div *ngIf="!editMode">
<select id="expirationDate" name="ExpirationDateSelect" [(ngModel)]="expirationDateSelect"
class="form-control" required>
<option *ngFor="let o of expirationDateOptions" [ngValue]="o.value">{{o.name}}
</option>
</select>
<input id="expirationDateCustom" class="form-control mt-1" type="datetime-local"
name="ExpirationDate" [(ngModel)]="expirationDate" required
*ngIf="expirationDateSelect === 0" placeholder="MM/DD/YYYY HH:MM AM/PM">
</div>
<div *ngIf="editMode">
<input id="expirationDate" class="form-control" type="datetime-local" name="ExpirationDate"
[(ngModel)]="expirationDate" placeholder="MM/DD/YYYY HH:MM AM/PM">
</div>
<div class="form-text text-muted small">{{'expirationDateDesc' | i18n}}</div>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="maxAccessCount">{{'maxAccessCount' | i18n}}</label>
<input id="maxAccessCount" class="form-control" type="number" name="MaxAccessCount"
[(ngModel)]="send.maxAccessCount" min="1">
<div class="form-text text-muted small">{{'maxAccessCountDesc' | i18n}}</div>
</div>
<div class="col-6 form-group" *ngIf="editMode">
<label for="accessCount">{{'currentAccessCount' | i18n}}</label>
<input id="accessCount" class="form-control" type="number" name="AccessCount" readonly
[(ngModel)]="send.accessCount">
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="password" *ngIf="!hasPassword">{{'password' | i18n}}</label>
<label for="password" *ngIf="hasPassword">{{'newPassword' | i18n}}</label>
<input id="password" class="form-control" type="password" name="Password"
[(ngModel)]="password">
<div class="form-text text-muted small">{{'sendPasswordDesc' | i18n}}</div>
</div>
</div>
<div class="form-group">
<label for="notes">{{'notes' | i18n}}</label>
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="send.notes" class="form-control"></textarea>
<div class="form-text text-muted small">{{'sendNotesDesc' | i18n}}</div>
<h3 class="mt-5">{{'share' | i18n}}</h3>
<div class="form-group" *ngIf="link">
<label for="link">{{'sendLinkLabel' | i18n}}</label>
<input type="text" readonly id="link" name="Link" [(ngModel)]="link" class="form-control">
</div>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" [(ngModel)]="send.disabled" id="disabled"
name="Disabled">
<label class="form-check-label" for="disabled">{{'disableThisSend' | i18n}}</label>
<input class="form-check-input" type="checkbox" [(ngModel)]="copyLink" id="copy-link"
name="CopyLink">
<label class="form-check-label" for="copy-link">{{'copySendLinkOnSave' | i18n}}</label>
</div>
</div>
<h3 class="mt-5" *ngIf="link">{{'share' | i18n}}</h3>
<div class="form-group" *ngIf="link">
<label for="link">{{'sendLink' | i18n}}</label>
<input type="text" readonly id="link" name="Link" [(ngModel)]="link" class="form-control">
<div id="options-header" class="section-header d-flex flex-row align-items-center mt-5"
(click)="toggleOptions()">
<h3 class="mb-0 mr-2">{{'options' | i18n}}</h3>
<a class="mb-1" href="#" appStopClick role="button">
<i class="fa" aria-hidden="true"
[ngClass]="{'fa-chevron-down': !showOptions, 'fa-chevron-up': showOptions}"></i>
</a>
</div>
<div id="options" [hidden]="!showOptions">
<div class="row">
<div class="col-6 form-group">
<label for="deletionDate">{{'deletionDate' | i18n}}</label>
<ng-template #deletionDateCustom>
<ng-container *ngIf="isDateTimeLocalSupported">
<input id="deletionDateCustom" class="form-control mt-1" type="datetime-local"
name="DeletionDate" [(ngModel)]="deletionDate" required
placeholder="MM/DD/YYYY HH:MM AM/PM" [readOnly]="disableSend">
</ng-container>
<div *ngIf="!isDateTimeLocalSupported" class="d-flex justify-content-around">
<input id="deletionDateCustomFallback" class="form-control mt-1" type="date"
name="DeletionDateFallback" [(ngModel)]="deletionDateFallback" required
placeholder="MM/DD/YYYY" [readOnly]="disableSend" data-date-format="mm/dd/yyyy">
<select *ngIf="isSafari" id="deletionTimeCustomFallback" class="form-control mt-1 ml-1" [required]="!editMode"
[(ngModel)]="safariDeletionTime" name="SafariDeletionTime">
<option *ngFor="let o of safariDeletionTimeOptions" [value]="o.military">{{o.standard}}</option>
</select>
<input *ngIf="!isSafari" id="deletionTimeCustomFallback" class="form-control mt-1 ml-1" type="time"
name="DeletionTimeDate" [(ngModel)]="deletionTimeFallback" required
placeholder="HH:MM AM/PM" [readOnly]="disableSend">
</div>
</ng-template>
<div *ngIf="!editMode">
<select id="deletionDate" name="DeletionDateSelect" [(ngModel)]="deletionDateSelect"
class="form-control" required>
<option *ngFor="let o of deletionDateOptions" [ngValue]="o.value">{{o.name}}
</option>
</select>
<ng-container *ngIf="deletionDateSelect === 0">
<ng-container *ngTemplateOutlet="deletionDateCustom">
</ng-container>
</ng-container>
</div>
<div *ngIf="editMode">
<ng-container *ngTemplateOutlet="deletionDateCustom">
</ng-container>
</div>
<div class="form-text text-muted small">{{'deletionDateDesc' | i18n}}</div>
</div>
<div class="col-6 form-group">
<div class="d-flex">
<label for="expirationDate">{{'expirationDate' | i18n}}</label>
<a href="#" appStopClick (click)="clearExpiration()" class="ml-auto"
*ngIf="editMode && !disableSend">
{{'clear' | i18n}}
</a>
</div>
<ng-template #expirationDateCustom>
<ng-container *ngIf="isDateTimeLocalSupported">
<input id="expirationDateCustom" class="form-control mt-1" type="datetime-local"
name="ExpirationDate" [(ngModel)]="expirationDate" placeholder="MM/DD/YYYY HH:MM AM/PM" [readOnly]="disableSend">
</ng-container>
<div class="d-flex justify-content-around" *ngIf="!isDateTimeLocalSupported">
<input id="expirationDateCustomFallback" class="form-control mt-1" type="date"
name="ExpirationDateFallback" [(ngModel)]="expirationDateFallback" [required]="!editMode"
placeholder="MM/DD/YYYY" [readOnly]="disableSend" data-date-format="mm/dd/yyyy" (change)="expirationDateFallbackChanged()">
<select *ngIf="isSafari" id="expirationTimeCustomFallback" class="form-control mt-1 ml-1" [required]="!editMode"
[(ngModel)]="safariExpirationTime" name="SafariExpirationTime">
<option *ngFor="let o of safariExpirationTimeOptions" [ngValue]="o.military">{{o.standard}}</option>
</select>
<input *ngIf="!isSafari" id="expirationTimeCustomFallback" class="form-control mt-1 ml-1" type="time"
name="ExpirationTimeFallback" [(ngModel)]="expirationTimeFallback" [required]="!editMode"
placeholder="HH:MM AM/PM" [readOnly]="disableSend">
</div>
</ng-template>
<div *ngIf="!editMode">
<select id="expirationDate" name="ExpirationDateSelect"
[(ngModel)]="expirationDateSelect" class="form-control" required>
<option *ngFor="let o of expirationDateOptions" [ngValue]="o.value">{{o.name}}
</option>
</select>
<ng-container *ngIf="expirationDateSelect === 0">
<ng-container *ngTemplateOutlet="expirationDateCustom">
</ng-container>
</ng-container>
</div>
<div *ngIf="editMode">
<ng-container *ngTemplateOutlet="expirationDateCustom">
</ng-container>
</div>
<div class="form-text text-muted small">{{'expirationDateDesc' | i18n}}</div>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="maxAccessCount">{{'maxAccessCount' | i18n}}</label>
<input id="maxAccessCount" class="form-control" type="number" name="MaxAccessCount"
[(ngModel)]="send.maxAccessCount" min="1" [readOnly]="disableSend">
<div class="form-text text-muted small">{{'maxAccessCountDesc' | i18n}}</div>
</div>
<div class="col-6 form-group" *ngIf="editMode">
<label for="accessCount">{{'currentAccessCount' | i18n}}</label>
<input id="accessCount" class="form-control" type="text" name="AccessCount" readonly
[(ngModel)]="send.accessCount">
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="password" *ngIf="!hasPassword">{{'password' | i18n}}</label>
<label for="password" *ngIf="hasPassword">{{'newPassword' | i18n}}</label>
<div class="input-group">
<input id="password" class="form-control text-monospace"
type="{{showPassword ? 'text' : 'password'}}" name="Password" [(ngModel)]="password"
[readOnly]="disableSend">
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePasswordVisible()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
<div class="form-text text-muted small">{{'sendPasswordDesc' | i18n}}</div>
</div>
</div>
<div class="form-group">
<label for="notes">{{'notes' | i18n}}</label>
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="send.notes" class="form-control"
[readOnly]="disableSend"></textarea>
<div class="form-text text-muted small">{{'sendNotesDesc' | i18n}}</div>
</div>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" [(ngModel)]="send.hideEmail" id="hideEmail"
name="HideEmail" [disabled]="(disableHideEmail && !send.hideEmail) || disableSend">
<label class="form-check-label" for="hideEmail">
{{'hideEmail' | i18n}}
</label>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" [(ngModel)]="send.disabled" id="disabled"
name="Disabled" [disabled]="disableSend">
<label class="form-check-label" for="disabled">{{'disableThisSend' | i18n}}</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<button class="btn btn-primary disabled" disabled=true *ngIf="disableSend">
<span>{{'save' | i18n}}</span>
</button>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading" *ngIf="!disableSend">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'save' | i18n}}</span>
</button>

View File

@@ -1,236 +1,33 @@
import { DatePipe } from '@angular/common';
import {
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Component } from '@angular/core';
import { SendType } from 'jslib/enums/sendType';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { SendService } from 'jslib/abstractions/send.service';
import { UserService } from 'jslib/abstractions/user.service';
import { SendFileView } from 'jslib/models/view/sendFileView';
import { SendTextView } from 'jslib/models/view/sendTextView';
import { SendView } from 'jslib/models/view/sendView';
import { Send } from 'jslib/models/domain/send';
import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/send/add-edit.component';
@Component({
selector: 'app-send-add-edit',
templateUrl: 'add-edit.component.html',
})
export class AddEditComponent {
@Input() sendId: string;
@Input() type: SendType;
@Output() onSavedSend = new EventEmitter<SendView>();
@Output() onDeletedSend = new EventEmitter<SendView>();
@Output() onCancelled = new EventEmitter<SendView>();
editMode: boolean = false;
send: SendView;
link: string;
title: string;
deletionDate: string;
expirationDate: string;
hasPassword: boolean;
password: string;
formPromise: Promise<any>;
deletePromise: Promise<any>;
sendType = SendType;
typeOptions: any[];
deletionDateOptions: any[];
expirationDateOptions: any[];
deletionDateSelect = 168;
expirationDateSelect: number = null;
canAccessPremium = true;
premiumRequiredAlertShown = false;
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
private environmentService: EnvironmentService, private datePipe: DatePipe,
private sendService: SendService, private userService: UserService,
private messagingService: MessagingService) {
this.typeOptions = [
{ name: i18nService.t('sendTypeFile'), value: SendType.File },
{ name: i18nService.t('sendTypeText'), value: SendType.Text },
];
this.deletionDateOptions = this.expirationDateOptions = [
{ name: i18nService.t('oneHour'), value: 1 },
{ name: i18nService.t('oneDay'), value: 24 },
{ name: i18nService.t('days', '2'), value: 48 },
{ name: i18nService.t('days', '3'), value: 72 },
{ name: i18nService.t('days', '7'), value: 168 },
{ name: i18nService.t('days', '30'), value: 720 },
{ name: i18nService.t('custom'), value: 0 },
];
this.expirationDateOptions = [
{ name: i18nService.t('never'), value: null },
].concat([...this.deletionDateOptions]);
export class AddEditComponent extends BaseAddEditComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService, datePipe: DatePipe,
sendService: SendService, userService: UserService,
messagingService: MessagingService, policyService: PolicyService) {
super(i18nService, platformUtilsService, environmentService, datePipe, sendService, userService,
messagingService, policyService);
}
async ngOnInit() {
await this.load();
}
async load() {
this.editMode = this.sendId != null;
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t('editSend');
} else {
this.title = this.i18nService.t('createSend');
}
this.canAccessPremium = await this.userService.canAccessPremium();
if (!this.canAccessPremium) {
this.type = SendType.Text;
}
if (this.send == null) {
if (this.editMode) {
const send = await this.loadSend();
this.send = await send.decrypt();
} else {
this.send = new SendView();
this.send.type = this.type == null ? SendType.File : this.type;
this.send.file = new SendFileView();
this.send.text = new SendTextView();
this.send.deletionDate = new Date();
this.send.deletionDate.setDate(this.send.deletionDate.getDate() + 7);
}
}
this.hasPassword = this.send.password != null && this.send.password.trim() !== '';
// Parse dates
this.deletionDate = this.dateToString(this.send.deletionDate);
this.expirationDate = this.dateToString(this.send.expirationDate);
if (this.editMode) {
let webVaultUrl = this.environmentService.getWebVaultUrl();
if (webVaultUrl == null) {
webVaultUrl = 'https://vault.bitwarden.com';
}
this.link = webVaultUrl + '/#/send/' + this.send.accessId + '/' + this.send.urlB64Key;
}
}
async submit(): Promise<boolean> {
if (this.send.name == null || this.send.name === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nameRequired'));
return false;
}
let file: File = null;
if (this.send.type === SendType.File && !this.editMode) {
const fileEl = document.getElementById('file') as HTMLInputElement;
const files = fileEl.files;
if (files == null || files.length === 0) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('selectFile'));
return;
}
file = files[0];
if (file.size > 104857600) { // 100 MB
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('maxFileSize'));
return;
}
}
if (!this.editMode) {
const now = new Date();
if (this.deletionDateSelect > 0) {
const d = new Date();
d.setHours(now.getHours() + this.deletionDateSelect);
this.deletionDate = this.dateToString(d);
}
if (this.expirationDateSelect != null && this.expirationDateSelect > 0) {
const d = new Date();
d.setHours(now.getHours() + this.expirationDateSelect);
this.expirationDate = this.dateToString(d);
}
}
const encSend = await this.encryptSend(file);
try {
this.formPromise = this.sendService.saveWithServer(encSend);
await this.formPromise;
this.send.id = encSend[0].id;
this.platformUtilsService.showToast('success', null,
this.i18nService.t(this.editMode ? 'editedSend' : 'createdSend'));
this.onSavedSend.emit(this.send);
return true;
} catch { }
return false;
}
clearExpiration() {
this.expirationDate = null;
}
async delete(): Promise<void> {
if (this.deletePromise != null) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteSendConfirmation'),
this.i18nService.t('deleteSend'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return;
}
try {
this.deletePromise = this.sendService.deleteWithServer(this.send.id);
await this.deletePromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedSend'));
await this.load();
this.onDeletedSend.emit(this.send);
} catch { }
}
typeChanged() {
if (!this.canAccessPremium && this.send.type === SendType.File && !this.premiumRequiredAlertShown) {
this.premiumRequiredAlertShown = true;
this.messagingService.send('premiumRequired');
}
}
protected async loadSend(): Promise<Send> {
return this.sendService.get(this.sendId);
}
protected async encryptSend(file: File): Promise<[Send, ArrayBuffer]> {
const sendData = await this.sendService.encrypt(this.send, file, this.password, null);
// Parse dates
try {
sendData[0].deletionDate = this.deletionDate == null ? null : new Date(this.deletionDate);
} catch {
sendData[0].deletionDate = null;
}
try {
sendData[0].expirationDate = this.expirationDate == null ? null : new Date(this.expirationDate);
} catch {
sendData[0].expirationDate = null;
}
return sendData;
}
protected dateToString(d: Date) {
return d == null ? null : this.datePipe.transform(d, 'yyyy-MM-ddTHH:mm');
copyLinkToClipboard(link: string) {
// Copy function on web depends on the modal being open or not. Since this event occurs during a transition
// of the modal closing we need to add a small delay to make sure state of the DOM is consistent.
window.setTimeout(() => super.copyLinkToClipboard(link), 500);
}
}

View File

@@ -1,4 +1,12 @@
<div class="container page-content">
<div class="row card border-warning mb-4" *ngIf="disableSend">
<div class="card-header bg-warning text-white">
<i class="fa fa-warning fa-fw" aria-hidden="true"></i> {{'sendDisabled' | i18n}}
</div>
<div class="card-body">
<span>{{'sendDisabledWarning' | i18n}}</span>
</div>
</div>
<div class="row">
<div class="col-3 groupings">
<div class="card vault-filters">
@@ -35,7 +43,7 @@
<div class="col-9">
<div class="page-header d-flex">
<h1>
Send
{{'send' | i18n}}
<small #actionSpinner [appApiAction]="actionPromise">
<ng-container *ngIf="actionSpinner.loading">
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"
@@ -45,7 +53,8 @@
</small>
</h1>
<div class="ml-auto d-flex">
<button type="button" class="btn btn-outline-primary btn-sm" (click)="addSend()">
<button type="button" class="btn btn-outline-primary btn-sm" (click)="addSend()"
[disabled]="disableSend">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>{{'createSend' | i18n}}
</button>
</div>
@@ -62,9 +71,11 @@
</td>
<td class="reduced-lh wrap">
<a href="#" appStopClick appStopProp (click)="editSend(s)">{{s.name}}</a>
<span appStopClick class="badge badge-secondary" *ngIf="s.disabled">
{{'disabled' | i18n}}
</span>
<ng-container *ngIf="s.disabled">
<i class="fa fa-warning" appStopProp title="{{'disabled' | i18n}}"
aria-hidden="true"></i>
<span class="sr-only">{{'disabled' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.password">
<i class="fa fa-key" appStopProp title="{{'password' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'password' | i18n}}</span>
@@ -75,7 +86,8 @@
<span class="sr-only">{{'maxAccessCountReached' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.expired">
<i class="fa fa-clock-o" appStopProp title="{{'expired' | i18n}}" aria-hidden="true"></i>
<i class="fa fa-clock-o" appStopProp title="{{'expired' | i18n}}"
aria-hidden="true"></i>
<span class="sr-only">{{'expired' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.pendingDelete">
@@ -99,7 +111,7 @@
{{'copySendLink' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick (click)="removePassword(s)"
*ngIf="s.password">
*ngIf="s.password && !disableSend">
<i class="fa fa-fw fa-undo" aria-hidden="true"></i>
{{'removePassword' | i18n}}
</a>
@@ -120,7 +132,7 @@
</ng-container>
<ng-container *ngIf="loaded">
<p>{{'noSendsInList' | i18n}}</p>
<button (click)="addSend()" class="btn btn-outline-primary">
<button (click)="addSend()" class="btn btn-outline-primary" [disabled]="disableSend">
<i class="fa fa-plus fa-fw"></i>{{'createSend' | i18n}}</button>
</ng-container>
</div>

View File

@@ -1,105 +1,75 @@
import {
Component,
ComponentFactoryResolver,
OnInit,
NgZone,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { SendType } from 'jslib/enums/sendType';
import { SendView } from 'jslib/models/view/sendView';
import { SendComponent as BaseSendComponent } from 'jslib/angular/components/send/send.component';
import { AddEditComponent } from './add-edit.component';
import { ModalComponent } from '../modal.component';
import { ApiService } from 'jslib/abstractions/api.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { SendService } from 'jslib/abstractions/send.service';
import { UserService } from 'jslib/abstractions/user.service';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
const BroadcasterSubscriptionId = 'SendComponent';
@Component({
selector: 'app-send',
templateUrl: 'send.component.html',
})
export class SendComponent implements OnInit {
export class SendComponent extends BaseSendComponent {
@ViewChild('sendAddEdit', { read: ViewContainerRef, static: true }) sendAddEditModalRef: ViewContainerRef;
sendType = SendType;
loaded = false;
loading = true;
refreshing = false;
expired: boolean = false;
type: SendType = null;
sends: SendView[] = [];
filteredSends: SendView[] = [];
searchText: string;
selectedType: SendType;
selectedAll: boolean;
searchPlaceholder: string;
filter: (cipher: SendView) => boolean;
searchPending = false;
modal: ModalComponent = null;
actionPromise: any;
private searchTimeout: any;
constructor(private apiService: ApiService, private sendService: SendService,
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
private platformUtilsService: PlatformUtilsService, private environmentService: EnvironmentService) { }
constructor(sendService: SendService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
ngZone: NgZone, searchService: SearchService, policyService: PolicyService, userService: UserService,
private componentFactoryResolver: ComponentFactoryResolver, private broadcasterService: BroadcasterService) {
super(sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService,
policyService, userService);
}
async ngOnInit() {
await super.ngOnInit();
await this.load();
}
async load(filter: (send: SendView) => boolean = null) {
this.loading = true;
const sends = await this.sendService.getAllDecrypted();
this.sends = sends;
this.selectAll();
this.loading = false;
this.loaded = true;
// Broadcaster subscription - load if sync completes in the background
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'syncCompleted':
if (message.successfully) {
await this.load();
}
break;
}
});
});
}
async reload(filter: (send: SendView) => boolean = null) {
this.loaded = false;
this.sends = [];
await this.load(filter);
}
async refresh() {
try {
this.refreshing = true;
await this.reload(this.filter);
} finally {
this.refreshing = false;
}
}
async applyFilter(filter: (send: SendView) => boolean = null) {
this.filter = filter;
await this.search(null);
}
async search(timeout: number = null) {
this.searchPending = false;
if (this.searchTimeout != null) {
clearTimeout(this.searchTimeout);
}
if (timeout == null) {
this.filteredSends = this.sends.filter((s) => this.filter == null || this.filter(s));
return;
}
this.searchPending = true;
this.searchTimeout = setTimeout(async () => {
this.filteredSends = this.sends.filter((s) => this.filter == null || this.filter(s));
this.searchPending = false;
}, timeout);
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
addSend() {
if (this.disableSend) {
return;
}
const component = this.editSend(null);
component.type = this.type;
}
@@ -130,78 +100,4 @@ export class SendComponent implements OnInit {
return childComponent;
}
async removePassword(s: SendView): Promise<boolean> {
if (this.actionPromise != null || s.password == null) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('removePasswordConfirmation'),
this.i18nService.t('removePassword'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
this.actionPromise = this.sendService.removePasswordWithServer(s.id);
await this.actionPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedPassword'));
await this.load();
} catch { }
this.actionPromise = null;
}
async delete(s: SendView): Promise<boolean> {
if (this.actionPromise != null) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteSendConfirmation'),
this.i18nService.t('deleteSend'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
this.actionPromise = this.sendService.deleteWithServer(s.id);
await this.actionPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedSend'));
await this.load();
} catch { }
this.actionPromise = null;
return true;
}
copy(s: SendView) {
let webVaultUrl = this.environmentService.getWebVaultUrl();
if (webVaultUrl == null) {
webVaultUrl = 'https://vault.bitwarden.com';
}
const link = webVaultUrl + '/#/send/' + s.accessId + '/' + s.urlB64Key;
this.platformUtilsService.copyToClipboard(link);
this.platformUtilsService.showToast('success', null,
this.i18nService.t('valueCopied', this.i18nService.t('sendLink')));
}
searchTextChanged() {
this.search(200);
}
selectAll() {
this.clearSelections();
this.selectedAll = true;
this.applyFilter(null);
}
selectType(type: SendType) {
this.clearSelections();
this.selectedType = type;
this.applyFilter((s) => s.type === type);
}
clearSelections() {
this.selectedAll = false;
this.selectedType = null;
}
}

View File

@@ -155,6 +155,13 @@ export class EventService {
break;
case EventType.OrganizationUser_UnlinkedSso:
msg = this.i18nService.t('unlinkedSsoUser', this.formatOrgUserId(ev));
break;
case EventType.OrganizationUser_ResetPassword_Enroll:
msg = this.i18nService.t('eventEnrollPasswordReset', this.formatOrgUserId(ev));
break;
case EventType.OrganizationUser_ResetPassword_Withdraw:
msg = this.i18nService.t('eventWithdrawPasswordReset', this.formatOrgUserId(ev));
break;
// Org
case EventType.Organization_Updated:
msg = this.i18nService.t('editedOrgSettings');

View File

@@ -16,7 +16,7 @@ export class RouterService {
constructor(private router: Router, private activatedRoute: ActivatedRoute,
private titleService: Title, i18nService: I18nService) {
this.currentUrl = this.router.url;
router.events.subscribe((event) => {
router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.previousUrl = this.currentUrl;
this.currentUrl = event.url;

View File

@@ -16,14 +16,13 @@ import { EventService } from './event.service';
import { OrganizationGuardService } from './organization-guard.service';
import { OrganizationTypeGuardService } from './organization-type-guard.service';
import { RouterService } from './router.service';
import { UnauthGuardService } from './unauth-guard.service';
import { AuthGuardService } from 'jslib/angular/services/auth-guard.service';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { LockGuardService } from 'jslib/angular/services/lock-guard.service';
import { UnauthGuardService } from 'jslib/angular/services/unauth-guard.service';
import { ValidationService } from 'jslib/angular/services/validation.service';
import { Analytics } from 'jslib/misc/analytics';
import { ApiService } from 'jslib/services/api.service';
import { AppIdService } from 'jslib/services/appId.service';
import { AuditService } from 'jslib/services/audit.service';
@@ -37,6 +36,7 @@ import { CryptoService } from 'jslib/services/crypto.service';
import { EnvironmentService } from 'jslib/services/environment.service';
import { EventService as EventLoggingService } from 'jslib/services/event.service';
import { ExportService } from 'jslib/services/export.service';
import { FileUploadService } from 'jslib/services/fileUpload.service';
import { FolderService } from 'jslib/services/folder.service';
import { ImportService } from 'jslib/services/import.service';
import { NotificationsService } from 'jslib/services/notifications.service';
@@ -64,6 +64,7 @@ import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib/abstractions/environment.service';
import { EventService as EventLoggingServiceAbstraction } from 'jslib/abstractions/event.service';
import { ExportService as ExportServiceAbstraction } from 'jslib/abstractions/export.service';
import { FileUploadService as FileUploadServiceAbstraction } from 'jslib/abstractions/fileUpload.service';
import { FolderService as FolderServiceAbstraction } from 'jslib/abstractions/folder.service';
import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service';
import { ImportService as ImportServiceAbstraction } from 'jslib/abstractions/import.service';
@@ -106,14 +107,15 @@ const apiService = new ApiService(tokenService, platformUtilsService,
const userService = new UserService(tokenService, storageService);
const settingsService = new SettingsService(userService, storageService);
export let searchService: SearchService = null;
const fileUploadService = new FileUploadService(consoleLogService, apiService);
const cipherService = new CipherService(cryptoService, userService, settingsService,
apiService, storageService, i18nService, () => searchService);
apiService, fileUploadService, storageService, i18nService, () => searchService);
const folderService = new FolderService(cryptoService, userService, apiService, storageService,
i18nService, cipherService);
const collectionService = new CollectionService(cryptoService, userService, storageService, i18nService);
searchService = new SearchService(cipherService, consoleLogService);
searchService = new SearchService(cipherService, consoleLogService, i18nService);
const policyService = new PolicyService(userService, storageService);
const sendService = new SendService(cryptoService, userService, apiService, storageService,
const sendService = new SendService(cryptoService, userService, apiService, fileUploadService, storageService,
i18nService, cryptoFunctionService);
const vaultTimeoutService = new VaultTimeoutService(cipherService, folderService, collectionService,
cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, tokenService,
@@ -128,42 +130,33 @@ const authService = new AuthService(cryptoService, apiService,
userService, tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
consoleLogService);
const exportService = new ExportService(folderService, cipherService, apiService);
const importService = new ImportService(cipherService, folderService, apiService, i18nService, collectionService);
const importService = new ImportService(cipherService, folderService, apiService, i18nService, collectionService,
platformUtilsService);
const notificationsService = new NotificationsService(userService, syncService, appIdService,
apiService, vaultTimeoutService, async () => messagingService.send('logout', { expired: true }), consoleLogService);
const environmentService = new EnvironmentService(apiService, storageService, notificationsService);
const auditService = new AuditService(cryptoFunctionService, apiService);
const eventLoggingService = new EventLoggingService(storageService, apiService, userService, cipherService);
const analytics = new Analytics(window, () => platformUtilsService.isDev() || platformUtilsService.isSelfHost(),
platformUtilsService, storageService, appIdService);
containerService.attachToWindow(window);
export function initFactory(): Function {
return async () => {
await (storageService as HtmlStorageService).init();
const isDev = platformUtilsService.isDev();
if (!isDev && platformUtilsService.isSelfHost()) {
if (isDev || platformUtilsService.isSelfHost()) {
environmentService.baseUrl = window.location.origin;
} else {
environmentService.notificationsUrl = isDev ? 'http://localhost:61840' :
'https://notifications.bitwarden.com'; // window.location.origin + '/notifications';
environmentService.enterpriseUrl = isDev ? 'http://localhost:52313' :
'https://portal.bitwarden.com'; // window.location.origin + '/portal';
environmentService.notificationsUrl = 'https://notifications.bitwarden.com';
environmentService.enterpriseUrl = 'https://portal.bitwarden.com';
}
apiService.setUrls({
base: isDev ? null : window.location.origin,
api: isDev ? 'http://localhost:4000' : null,
identity: isDev ? 'http://localhost:33656' : null,
events: isDev ? 'http://localhost:46273' : null,
// Uncomment these (and comment out the above) if you want to target production
// servers for local development.
// base: null,
// api: 'https://api.bitwarden.com',
// identity: 'https://identity.bitwarden.com',
// events: 'https://events.bitwarden.com',
base: window.location.origin,
api: null,
identity: null,
events: null,
});
setTimeout(() => notificationsService.init(environmentService), 3000);
@@ -198,6 +191,7 @@ export function initFactory(): Function {
UnauthGuardService,
RouterService,
EventService,
LockGuardService,
{ provide: AuditServiceAbstraction, useValue: auditService },
{ provide: AuthServiceAbstraction, useValue: authService },
{ provide: CipherServiceAbstraction, useValue: cipherService },
@@ -211,6 +205,7 @@ export function initFactory(): Function {
{ provide: PlatformUtilsServiceAbstraction, useValue: platformUtilsService },
{ provide: PasswordGenerationServiceAbstraction, useValue: passwordGenerationService },
{ provide: ApiServiceAbstraction, useValue: apiService },
{ provide: FileUploadServiceAbstraction, useValue: fileUploadService },
{ provide: SyncServiceAbstraction, useValue: syncService },
{ provide: UserServiceAbstraction, useValue: userService },
{ provide: MessagingServiceAbstraction, useValue: messagingService },

View File

@@ -1,29 +0,0 @@
import { Injectable } from '@angular/core';
import {
CanActivate,
Router,
} from '@angular/router';
import { UserService } from 'jslib/abstractions/user.service';
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
@Injectable()
export class UnauthGuardService implements CanActivate {
constructor(private vaultTimeoutService: VaultTimeoutService, private userService: UserService,
private router: Router) { }
async canActivate() {
const isAuthed = await this.userService.isAuthenticated();
if (isAuthed) {
const locked = await this.vaultTimeoutService.isLocked();
if (locked) {
this.router.navigate(['lock']);
} else {
this.router.navigate(['vault']);
}
return false;
}
return true;
}
}

View File

@@ -8,9 +8,6 @@ import {
ViewChild,
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { ApiService } from 'jslib/abstractions/api.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { UserService } from 'jslib/abstractions/user.service';
@@ -49,7 +46,6 @@ export class AddCreditComponent implements OnInit {
private email: string;
constructor(private userService: UserService, private apiService: ApiService,
private analytics: Angulartics2, private toasterService: ToasterService,
private platformUtilsService: PlatformUtilsService) {
if (platformUtilsService.isDev()) {
this.ppButtonFormAction = WebConstants.paypal.buttonActionSandbox;
@@ -108,9 +104,6 @@ export class AddCreditComponent implements OnInit {
return;
}
try {
this.analytics.eventTrack.next({
action: 'Added Credit',
});
this.onAdded.emit();
} catch { }
}

View File

@@ -7,7 +7,6 @@ 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';
@@ -36,12 +35,12 @@ export class AdjustPaymentComponent {
formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService) { }
private toasterService: ToasterService) { }
async submit() {
try {
const request = new PaymentRequest();
this.formPromise = this.paymentComponent.createPaymentToken().then((result) => {
this.formPromise = this.paymentComponent.createPaymentToken().then(result => {
request.paymentToken = result[0];
request.paymentMethodType = result[1];
request.postalCode = this.taxInfoComponent.taxInfo.postalCode;
@@ -59,9 +58,6 @@ export class AdjustPaymentComponent {
}
});
await this.formPromise;
this.analytics.eventTrack.next({
action: this.currentType == null ? 'Added Payment Method' : 'Changed Payment Method',
});
this.toasterService.popAsync('success', null, this.i18nService.t('updatedPaymentMethod'));
this.onAdjusted.emit();
} catch { }

View File

@@ -12,7 +12,6 @@ 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';
@@ -41,8 +40,8 @@ export class AdjustStorageComponent {
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 {
@@ -71,7 +70,6 @@ export class AdjustStorageComponent {
};
this.formPromise = action();
await this.formPromise;
this.analytics.eventTrack.next({ action: this.add ? 'Added Storage' : 'Removed Storage' });
this.onAdjusted.emit(this.storageAdjustment);
if (paymentFailed) {
this.toasterService.popAsync({

View File

@@ -1,7 +1,6 @@
import { Component } from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
@@ -30,8 +29,8 @@ export class ApiKeyComponent {
clientId: string;
clientSecret: string;
constructor(private i18nService: I18nService, private analytics: Angulartics2,
private toasterService: ToasterService, private cryptoService: CryptoService) { }
constructor(private i18nService: I18nService, private toasterService: ToasterService,
private cryptoService: CryptoService) { }
async submit() {
if (this.masterPassword == null || this.masterPassword === '') {
@@ -47,7 +46,6 @@ export class ApiKeyComponent {
const response = await this.formPromise;
this.clientSecret = response.apiKey;
this.clientId = `${this.keyType}.${this.entityId}`;
this.analytics.eventTrack.next({ action: `Viewed ${this.keyType} API Key` });
} catch { }
}
}

View File

@@ -3,7 +3,6 @@ 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';
@@ -27,9 +26,8 @@ export class ChangeEmailComponent {
formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private cryptoService: CryptoService, private messagingService: MessagingService,
private userService: UserService) { }
private toasterService: ToasterService, private cryptoService: CryptoService,
private messagingService: MessagingService, private userService: UserService) { }
async submit() {
const hasEncKey = await this.cryptoService.hasEncKey();
@@ -63,7 +61,6 @@ export class ChangeEmailComponent {
this.formPromise = this.apiService.postEmail(request);
await this.formPromise;
this.reset();
this.analytics.eventTrack.next({ action: 'Changed Email' });
this.toasterService.popAsync('success', this.i18nService.t('emailChanged'),
this.i18nService.t('logBackIn'));
this.messagingService.send('logout');

View File

@@ -25,7 +25,7 @@
<div class="col-6">
<div class="form-group mb-0">
<label for="kdfIterations">{{'kdfIterations' | i18n}}</label>
<a class="ml-auto" href="https://en.wikipedia.org/wiki/PBKDF2" target="_blank" rel="noopener"
<a class="ml-auto" href="https://bitwarden.com/help/article/what-encryption-is-used/#pbkdf2" target="_blank" rel="noopener"
appA11yTitle="{{'learnMore' | i18n}}">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>

View File

@@ -4,7 +4,6 @@ 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';
@@ -28,9 +27,8 @@ export class ChangeKdfComponent implements OnInit {
formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private cryptoService: CryptoService, private messagingService: MessagingService,
private userService: UserService) {
private toasterService: ToasterService, private cryptoService: CryptoService,
private messagingService: MessagingService, private userService: UserService) {
this.kdfOptions = [
{ name: 'PBKDF2 SHA-256', value: KdfType.PBKDF2_SHA256 },
];
@@ -60,7 +58,6 @@ export class ChangeKdfComponent implements OnInit {
try {
this.formPromise = this.apiService.postAccountKdf(request);
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Changed KDF' });
this.toasterService.popAsync('success', this.i18nService.t('encKeySettingsChanged'),
this.i18nService.t('logBackIn'));
this.messagingService.send('logout');

View File

@@ -51,7 +51,7 @@
<label class="form-check-label" for="rotateEncKey">
{{'rotateAccountEncKey' | i18n}}
</label>
<a href="https://help.bitwarden.com/article/change-your-master-password/#rotating-your-accounts-encryption-key"
<a href="https://bitwarden.com/help/article/account-encryption-key/#rotate-your-encryption-key"
target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>

View File

@@ -19,12 +19,13 @@ import {
import { EmergencyAccessStatusType } from 'jslib/enums/emergencyAccessStatusType';
import { Utils } from 'jslib/misc/utils';
import { CipherString } from 'jslib/models/domain/cipherString';
import { EncString } from 'jslib/models/domain/encString';
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
import { CipherWithIdRequest } from 'jslib/models/request/cipherWithIdRequest';
import { EmergencyAccessUpdateRequest } from 'jslib/models/request/emergencyAccessUpdateRequest';
import { FolderWithIdRequest } from 'jslib/models/request/folderWithIdRequest';
import { OrganizationUserResetPasswordEnrollmentRequest } from 'jslib/models/request/organizationUserResetPasswordEnrollmentRequest';
import { PasswordRequest } from 'jslib/models/request/passwordRequest';
import { UpdateKeyRequest } from 'jslib/models/request/updateKeyRequest';
@@ -41,7 +42,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
userService: UserService, passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService, policyService: PolicyService,
private folderService: FolderService, private cipherService: CipherService,
private syncService: SyncService, private apiService: ApiService ) {
private syncService: SyncService, private apiService: ApiService) {
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService,
platformUtilsService, policyService);
}
@@ -107,7 +108,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
}
async performSubmitActions(newMasterPasswordHash: string, newKey: SymmetricCryptoKey,
newEncKey: [SymmetricCryptoKey, CipherString]) {
newEncKey: [SymmetricCryptoKey, EncString]) {
const request = new PasswordRequest();
request.masterPasswordHash = await this.cryptoService.hashPassword(this.currentMasterPassword, null);
request.newMasterPasswordHash = newMasterPasswordHash;
@@ -135,7 +136,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
private async updateKey(key: SymmetricCryptoKey, masterPasswordHash: string) {
const encKey = await this.cryptoService.makeEncKey(key);
const privateKey = await this.cryptoService.getPrivateKey();
let encPrivateKey: CipherString = null;
let encPrivateKey: EncString = null;
if (privateKey != null) {
encPrivateKey = await this.cryptoService.encrypt(privateKey, encKey[0]);
}
@@ -166,6 +167,8 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
await this.apiService.postAccountKey(request);
await this.updateEmergencyAccesses(encKey[0]);
await this.updateAllResetPasswordKeys(encKey[0]);
}
private async updateEmergencyAccesses(encKey: SymmetricCryptoKey) {
@@ -192,4 +195,25 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
await this.apiService.putEmergencyAccess(details.id, updateRequest);
}
}
private async updateAllResetPasswordKeys(encKey: SymmetricCryptoKey) {
const orgs = await this.userService.getAllOrganizations();
for (const org of orgs) {
// If not already enrolled, skip
if (!org.isResetPasswordEnrolled) {
continue;
}
// Re-enroll - encrpyt user's encKey.key with organization key
const orgSymKey = await this.cryptoService.getOrgKey(org.id);
const encryptedKey = await this.cryptoService.encrypt(encKey.key, orgSymKey);
// Create/Execute request
const request = new OrganizationUserResetPasswordEnrollmentRequest();
request.resetPasswordKey = encryptedKey.encryptedString;
await this.apiService.putOrganizationUserResetPasswordEnrollment(org.id, org.userId, request);
}
}
}

View File

@@ -20,7 +20,7 @@ export class CreateOrganizationComponent implements OnInit {
constructor(private route: ActivatedRoute) { }
ngOnInit() {
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
if (qParams.plan === 'families') {
this.orgPlansComponent.plan = PlanType.FamiliesAnnually;
this.orgPlansComponent.product = ProductType.Families;

View File

@@ -1,7 +1,6 @@
import { Component } 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';
@@ -19,8 +18,8 @@ export class DeauthorizeSessionsComponent {
formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private cryptoService: CryptoService, private messagingService: MessagingService) { }
private toasterService: ToasterService, private cryptoService: CryptoService,
private messagingService: MessagingService) { }
async submit() {
if (this.masterPassword == null || this.masterPassword === '') {
@@ -34,7 +33,6 @@ export class DeauthorizeSessionsComponent {
try {
this.formPromise = this.apiService.postSecurityStamp(request);
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Deauthorized Sessions' });
this.toasterService.popAsync('success', this.i18nService.t('sessionsDeauthorized'),
this.i18nService.t('logBackIn'));
this.messagingService.send('logout');

View File

@@ -1,7 +1,6 @@
import { Component } 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';
@@ -19,8 +18,8 @@ export class DeleteAccountComponent {
formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService,
private cryptoService: CryptoService, private messagingService: MessagingService) { }
private toasterService: ToasterService, private cryptoService: CryptoService,
private messagingService: MessagingService) { }
async submit() {
if (this.masterPassword == null || this.masterPassword === '') {
@@ -34,7 +33,6 @@ export class DeleteAccountComponent {
try {
this.formPromise = this.apiService.deleteAccount(request);
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Deleted Account' });
this.toasterService.popAsync('success', this.i18nService.t('accountDeleted'),
this.i18nService.t('accountDeletedDesc'));
this.messagingService.send('logout');

View File

@@ -4,7 +4,6 @@ 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';
@@ -22,16 +21,16 @@ export class DomainRulesComponent implements OnInit {
formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private analytics: Angulartics2, private toasterService: ToasterService) { }
private toasterService: ToasterService) { }
async ngOnInit() {
const response = await this.apiService.getSettingsDomains();
this.loading = false;
if (response.equivalentDomains != null) {
this.custom = response.equivalentDomains.map((d) => d.join(', '));
this.custom = response.equivalentDomains.map(d => d.join(', '));
}
if (response.globalEquivalentDomains != null) {
this.global = response.globalEquivalentDomains.map((d) => {
this.global = response.globalEquivalentDomains.map(d => {
return {
domains: d.domains.join(', '),
excluded: d.excluded,
@@ -60,13 +59,13 @@ export class DomainRulesComponent implements OnInit {
async submit() {
const request = new UpdateDomainsRequest();
request.excludedGlobalEquivalentDomains = this.global.filter((d) => d.excluded)
.map((d) => d.key);
request.excludedGlobalEquivalentDomains = this.global.filter(d => d.excluded)
.map(d => d.key);
if (request.excludedGlobalEquivalentDomains.length === 0) {
request.excludedGlobalEquivalentDomains = null;
}
request.equivalentDomains = this.custom.filter((d) => d != null && d.trim() !== '')
.map((d) => d.split(',').map((d2) => d2.trim()));
request.equivalentDomains = this.custom.filter(d => d != null && d.trim() !== '')
.map(d => d.split(',').map(d2 => d2.trim()));
if (request.equivalentDomains.length === 0) {
request.equivalentDomains = null;
}
@@ -74,7 +73,6 @@ export class DomainRulesComponent implements OnInit {
try {
this.formPromise = this.apiService.putSettingsDomains(request);
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Saved Equivalent Domains' });
this.toasterService.popAsync('success', null, this.i18nService.t('domainsUpdated'));
} catch { }
}

View File

@@ -26,7 +26,7 @@
<h3>
{{'userAccess' | i18n}}
<a target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}"
href="https://bitwarden.com/help/article/user-types-access-control/#user-types">
href="https://bitwarden.com/help/article/emergency-access/#user-access">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</h3>
@@ -55,9 +55,11 @@
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" [disabled]="loading || readOnly">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="loading"></i>
<span *ngIf="!loading">{{'save' | i18n}}</span>
<button #submitBtn type="submit" class="btn btn-primary"
[disabled]="loading || submitBtn.loading || readOnly" [appApiAction]="formPromise">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"
*ngIf="loading || submitBtn.loading"></i>
<span *ngIf="!loading && !submitBtn.loading">{{'save' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary"
data-dismiss="modal">{{'cancel' | i18n}}</button>

View File

@@ -0,0 +1,35 @@
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 { AttachmentView } from 'jslib/models/view/attachmentView';
import { AttachmentsComponent as BaseAttachmentsComponent } from 'jslib/angular/components/attachments.component';
@Component({
selector: 'emergency-access-attachments',
templateUrl: '../vault/attachments.component.html',
})
export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponent {
viewOnly = true;
canAccessAttachments = true;
constructor(cipherService: CipherService, i18nService: I18nService,
cryptoService: CryptoService, userService: UserService,
platformUtilsService: PlatformUtilsService, apiService: ApiService) {
super(cipherService, i18nService, cryptoService, userService, platformUtilsService, apiService, window);
}
protected async init() {
// Do nothing since cipher is already decoded
}
protected showFixOldAttachments(attachment: AttachmentView) {
return false;
}
}

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