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

Compare commits

...

606 Commits

Author SHA1 Message Date
Kyle Spearrin
4a815b0bdf duplicate node_env check 2018-08-25 08:48:07 -04:00
Kyle Spearrin
b7525e1e7e messagepack protocolf or signalr 2018-08-23 21:45:06 -04:00
Kyle Spearrin
c3f64fe9c4 update jslib 2018-08-23 08:56:45 -04:00
Kyle Spearrin
34f6bc2403 10 minute idle timeout 2018-08-22 22:56:00 -04:00
Kyle Spearrin
9ecec972ca local notifications URL 2018-08-22 22:43:40 -04:00
Kyle Spearrin
80febf97d3 idle reconnects for notifications 2018-08-22 22:37:55 -04:00
Kyle Spearrin
71073874eb update jslib 2018-08-22 16:02:45 -04:00
Kyle Spearrin
f3dfeac125 update jslib 2018-08-22 08:54:22 -04:00
Kyle Spearrin
f12e73519e New Crowdin translations (#249)
* New translations messages.json (Danish)

* New translations messages.json (Dutch)

* New translations messages.json (Estonian)

* New translations messages.json (French)

* New translations messages.json (Italian)

* New translations messages.json (Spanish)
2018-08-21 23:27:57 -04:00
Kyle Spearrin
19f7dda4cc add nl language 2018-08-21 23:23:30 -04:00
Kyle Spearrin
221397b159 add et language 2018-08-21 16:01:47 -04:00
Kyle Spearrin
91766ecea3 word break-all on card lists 2018-08-21 15:48:37 -04:00
Kyle Spearrin
191be134f9 update some packages 2018-08-21 15:31:29 -04:00
Kyle Spearrin
56d279ae1e npm audit fix 2018-08-21 15:19:50 -04:00
Kyle Spearrin
3e61464dac explicitly use https://notifications.bitwarden.com 2018-08-21 13:58:45 -04:00
Kyle Spearrin
85ca10dbb3 notification service implementation 2018-08-20 22:21:13 -04:00
Kyle Spearrin
eaf08c45d9 dont use clipboard writeText api 2018-08-20 09:20:24 -04:00
Kyle Spearrin
bcb44e8cf7 fix copying 2018-08-17 12:25:21 -04:00
Kyle Spearrin
d215e0716e update jslib 2018-08-17 11:09:11 -04:00
Kyle Spearrin
f635162832 New Crowdin translations (#247)
* New translations messages.json (Dutch)

* New translations messages.json (Estonian)
2018-08-16 15:17:55 -04:00
Kyle Spearrin
c892480086 wrap cipher list details for long names 2018-08-16 08:46:49 -04:00
Kyle Spearrin
ea49d17c47 set search text before load 2018-08-15 22:26:39 -04:00
Kyle Spearrin
39c32b0e62 pass original cipher if admin. resolves #245 2018-08-15 11:47:47 -04:00
Kyle Spearrin
5cdfa35a76 update jslib 2018-08-15 09:01:37 -04:00
Kyle Spearrin
147b3ff993 prelogin kdf info 2018-08-14 15:14:04 -04:00
Kyle Spearrin
662c229de1 add italian language to web vault 2018-08-14 10:42:54 -04:00
Kyle Spearrin
c90cb2ae6e New Crowdin translations (#244)
* New translations messages.json (Chinese Traditional)

* New translations messages.json (Danish)

* New translations messages.json (Dutch)

* New translations messages.json (French)

* New translations messages.json (German)

* New translations messages.json (Italian)

* New translations messages.json (Polish)

* New translations messages.json (Slovak)

* New translations messages.json (Spanish)

* New translations messages.json (Swedish)
2018-08-14 10:40:14 -04:00
Kyle Spearrin
864d070656 bump version 2018-08-14 10:36:45 -04:00
Kyle Spearrin
e8ac2b561a apply filters on org vault 2018-08-13 16:38:21 -04:00
Kyle Spearrin
c71a432ce4 update jslib 2018-08-13 16:27:35 -04:00
Kyle Spearrin
e3ca470a6a implement search service 2018-08-13 16:27:17 -04:00
Kyle Spearrin
e7c6fbf423 bump version 2018-08-08 13:45:07 -04:00
Kyle Spearrin
6cef5e614d dont async refresh and sync 2018-08-08 13:44:01 -04:00
Kyle Spearrin
33cf77559f dont refresh token and sync at same time 2018-08-08 13:41:39 -04:00
Kyle Spearrin
b5085d8004 update packages for node 10 2018-08-08 12:08:26 -04:00
Kyle Spearrin
80af20ef54 control save button enabled for sharing modal 2018-08-07 23:46:31 -04:00
Kyle Spearrin
4a324ca764 New Crowdin translations (#240)
* New translations messages.json (Chinese Simplified)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (Danish)

* New translations messages.json (French)

* New translations messages.json (German)

* New translations messages.json (Polish)

* New translations messages.json (Portuguese)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Russian)

* New translations messages.json (Spanish)

* New translations messages.json (Swedish)
2018-08-07 15:43:30 -04:00
Kyle Spearrin
85db64ed70 webVault param 2018-08-07 15:04:30 -04:00
Kyle Spearrin
d72ba456ae cacheWeb arg 2018-08-07 14:31:16 -04:00
Kyle Spearrin
b4a0913994 dont need to copy settings file anymore 2018-08-07 14:01:47 -04:00
Kyle Spearrin
6fb8a9ef2f add polish language 2018-08-06 17:42:59 -04:00
Kyle Spearrin
4e58708f12 New Crowdin translations (#238)
* New translations messages.json (Chinese Simplified)

* New translations messages.json (Norwegian Bokmal)

* New translations messages.json (Swedish)

* New translations messages.json (Spanish)

* New translations messages.json (Slovak)

* New translations messages.json (Russian)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Portuguese)

* New translations messages.json (Polish)

* New translations messages.json (Korean)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (Japanese)

* New translations messages.json (Italian)

* New translations messages.json (German)

* New translations messages.json (French)

* New translations messages.json (Estonian)

* New translations messages.json (Danish)

* New translations messages.json (Czech)

* New translations messages.json (Ukrainian)
2018-08-06 17:41:26 -04:00
Kyle Spearrin
32876367c2 update status maps when user status changes 2018-08-06 17:37:54 -04:00
Kyle Spearrin
226aa0b3ba show message that users needs to be confirmed 2018-08-06 17:18:33 -04:00
Kyle Spearrin
c37359cdfd use jslib import service 2018-08-06 11:40:12 -04:00
Kyle Spearrin
d2d89c252a add new languages 2018-08-06 08:58:50 -04:00
Kyle Spearrin
b76f1d3c8c New Crowdin translations (#237)
* New translations messages.json (Chinese Simplified)

* New translations messages.json (Norwegian Bokmal)

* New translations messages.json (Swedish)

* New translations messages.json (Spanish)

* New translations messages.json (Slovak)

* New translations messages.json (Russian)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Portuguese)

* New translations messages.json (Polish)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (Japanese)

* New translations messages.json (German)

* New translations messages.json (French)

* New translations messages.json (Danish)

* New translations messages.json (Czech)

* New translations messages.json (Ukrainian)
2018-08-06 08:57:14 -04:00
Kyle Spearrin
9ff34a12a2 fix save button on collection management 2018-08-03 21:09:22 -04:00
Kyle Spearrin
c5f6ce3fe5 no org message on share 2018-08-03 21:07:24 -04:00
Kyle Spearrin
965d556ac9 fix grammatical error 2018-08-03 17:13:33 -04:00
Kyle Spearrin
e52d3f21d1 standardize locale name 2018-08-02 09:35:44 -04:00
Kyle Spearrin
00576053d0 toaster paragraph formatting 2018-08-02 08:46:33 -04:00
Kyle Spearrin
1f040efb7f set viewport width 2018-08-01 22:02:32 -04:00
Kyle Spearrin
3b5e9775c5 New Crowdin translations (#235)
* New translations messages.json (Chinese Simplified)

* New translations messages.json (Norwegian Bokmal)

* New translations messages.json (Swedish)

* New translations messages.json (Spanish)

* New translations messages.json (Slovak)

* New translations messages.json (Russian)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Portuguese)

* New translations messages.json (Polish)

* New translations messages.json (Korean)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (Japanese)

* New translations messages.json (Italian)

* New translations messages.json (German)

* New translations messages.json (French)

* New translations messages.json (Estonian)

* New translations messages.json (Danish)

* New translations messages.json (Czech)

* New translations messages.json (Ukrainian)
2018-08-01 17:03:43 -04:00
Kyle Spearrin
ea0653460e update jslib 2018-08-01 16:55:36 -04:00
Kyle Spearrin
6ed80eb6c9 org disabled and license expired warnings 2018-08-01 16:51:25 -04:00
Kyle Spearrin
12bdd87705 free desc 2018-08-01 15:51:46 -04:00
Kyle Spearrin
9bdf7ff174 remove ps1 script 2018-08-01 10:53:02 -04:00
Kyle Spearrin
2ab2f757a0 is mobile browser checks on autofocus 2018-08-01 09:13:55 -04:00
Kyle Spearrin
f830de8d13 New Crowdin translations (#234)
* New translations messages.json (Chinese Simplified)

* New translations messages.json (Norwegian Bokmal)

* New translations messages.json (Swedish)

* New translations messages.json (Spanish)

* New translations messages.json (Slovak)

* New translations messages.json (Russian)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Portuguese)

* New translations messages.json (Polish)

* New translations messages.json (Korean)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (Japanese)

* New translations messages.json (Italian)

* New translations messages.json (German)

* New translations messages.json (French)

* New translations messages.json (Estonian)

* New translations messages.json (Danish)

* New translations messages.json (Czech)

* New translations messages.json (Ukrainian)
2018-08-01 07:58:30 -04:00
Kyle Spearrin
7eb329db6b add french translations support 2018-08-01 07:35:33 -04:00
Kyle Spearrin
fecb980c9b update jslib 2018-07-31 23:49:40 -04:00
Kyle Spearrin
600218cf7b support for otpauth 2018-07-31 11:26:04 -04:00
Kyle Spearrin
8eb48e4311 rename to "enable gravatars" 2018-07-31 00:02:09 -04:00
Kyle Spearrin
ac33d2f37c add support for gravatars 2018-07-30 23:52:04 -04:00
Kyle Spearrin
bfc462cbec fix navbar text breaks 2018-07-30 22:53:08 -04:00
Kyle Spearrin
8b8bd88adf control when password history shows 2018-07-30 22:02:01 -04:00
Kyle Spearrin
bc768b773b bump version 2018-07-30 21:24:26 -04:00
Kyle Spearrin
950e5a57b1 additional language support 2018-07-30 21:14:34 -04:00
Kyle Spearrin
52a4fc8e93 clean l10n script 2018-07-30 17:10:35 -04:00
Kyle Spearrin
dd5c840b61 contributing doc 2018-07-30 17:06:57 -04:00
Kyle Spearrin
337c01daf8 select, not select in 2018-07-30 16:57:58 -04:00
Kyle Spearrin
5a9da2a97a New Crowdin translations (#232)
* New translations messages.json (Chinese Simplified)

* New translations messages.json (Norwegian Bokmal)

* New translations messages.json (Swedish)

* New translations messages.json (Spanish)

* New translations messages.json (Slovak)

* New translations messages.json (Russian)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Portuguese)

* New translations messages.json (Polish)

* New translations messages.json (Korean)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (Japanese)

* New translations messages.json (Italian)

* New translations messages.json (German)

* New translations messages.json (French)

* New translations messages.json (Estonian)

* New translations messages.json (Danish)

* New translations messages.json (Czech)

* New translations messages.json (Ukrainian)
2018-07-30 16:55:06 -04:00
Kyle Spearrin
38c1a0f383 clean l10n command 2018-07-30 16:52:00 -04:00
Kyle Spearrin
2b1718945f update paymentCharged messages 2018-07-30 16:47:25 -04:00
Kyle Spearrin
a6319a3865 update jslib 2018-07-30 11:00:23 -04:00
Kyle Spearrin
35241307d3 update jslib 2018-07-30 10:05:44 -04:00
Kyle Spearrin
ed8aaa5505 view password history 2018-07-30 08:48:48 -04:00
Kyle Spearrin
a25f6dee73 check group access on collection edit page 2018-07-30 08:12:47 -04:00
Kyle Spearrin
9aa8699617 disable autocomplete of password fields 2018-07-30 08:00:23 -04:00
Kyle Spearrin
94f671ca6b show reivison dates for item edit 2018-07-27 23:38:12 -04:00
Kyle Spearrin
7c9016bf4a never option in dev mode 2018-07-27 22:52:27 -04:00
Kyle Spearrin
98d18fb097 only show add button if admin 2018-07-27 22:39:51 -04:00
Kyle Spearrin
dc69887628 fixes for non-admin actions on org vault list 2018-07-27 22:36:25 -04:00
Kyle Spearrin
7c882ed40c catch error 2018-07-27 22:08:31 -04:00
Kyle Spearrin
c8909beedd uncheck all on destroy 2018-07-27 22:05:03 -04:00
Kyle Spearrin
15fc4e5f2d filter ciphers before doing select all, resolves #227 2018-07-27 21:59:49 -04:00
Kyle Spearrin
229b9cf40a update jslib 2018-07-27 21:52:49 -04:00
Kyle Spearrin
17ae441c22 dont set default lock option in dev 2018-07-27 21:52:36 -04:00
Kyle Spearrin
9a70c0e8a7 update jslib 2018-07-27 17:48:32 -04:00
Kyle Spearrin
2a41fee1ff on browser refresh 2018-07-27 15:11:57 -04:00
Kyle Spearrin
f385c3773c allow users to change lock options, default to 15 min 2018-07-27 15:08:59 -04:00
Kyle Spearrin
4b4e847816 Update README.md 2018-07-26 10:03:55 -04:00
Kyle Spearrin
a70c4c250c Update README.md 2018-07-26 10:03:22 -04:00
Kyle Spearrin
950e75e7e3 Update README.md 2018-07-26 10:02:24 -04:00
Kyle Spearrin
2cbf2006c6 Update README.md 2018-07-26 10:02:04 -04:00
Kyle Spearrin
14c4095a4e Update README.md 2018-07-26 09:54:36 -04:00
Kyle Spearrin
c755e8a90d Update README.md 2018-07-26 09:25:43 -04:00
Kyle Spearrin
7c1d824636 New Crowdin translations (#226)
* New translations messages.json (Chinese Simplified)

* New translations messages.json (Spanish)

* New translations messages.json (Slovak)

* New translations messages.json (Polish)

* New translations messages.json (Ukrainian)

* New translations messages.json (Estonian)

* New translations messages.json (Danish)

* New translations messages.json (Swedish)

* New translations messages.json (Russian)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Portuguese)

* New translations messages.json (Korean)

* New translations messages.json (Japanese)

* New translations messages.json (Italian)

* New translations messages.json (German)

* New translations messages.json (French)

* New translations messages.json (Czech)
2018-07-26 08:51:51 -04:00
Kyle Spearrin
f71be112c2 added missing filters translation 2018-07-26 08:16:37 -04:00
Kyle Spearrin
8a4dba6317 public computer, not PC 2018-07-25 23:21:41 -04:00
Kyle Spearrin
87c72bd595 append copy textarea to modal if open 2018-07-25 22:01:26 -04:00
Kyle Spearrin
f9e756402f prod endpoints commented in 2018-07-25 21:41:55 -04:00
Kyle Spearrin
f576bbd3e1 adjust frontend footer margins 2018-07-25 14:26:22 -04:00
Kyle Spearrin
f57d54249c dont show terms on self hosted 2018-07-25 13:59:55 -04:00
Kyle Spearrin
20dca960ef frontend cards are d-block 2018-07-25 12:13:18 -04:00
Kyle Spearrin
fe9df3977a add/remove created anchor to page when clicking it 2018-07-25 12:04:14 -04:00
Kyle Spearrin
c5bac554a7 overflow hidden on user menu 2018-07-25 11:45:07 -04:00
Kyle Spearrin
0e791fd0ec added sk language 2018-07-25 11:38:37 -04:00
Kyle Spearrin
6fb9aafa03 New Crowdin translations (#224)
* New translations messages.json (Chinese Simplified)

* New translations messages.json (French)

* New translations messages.json (German)

* New translations messages.json (Italian)

* New translations messages.json (Portuguese)

* New translations messages.json (Russian)

* New translations messages.json (Spanish)

* New translations messages.json (Danish)

* New translations messages.json (Estonian)

* New translations messages.json (Polish)

* New translations messages.json (Slovak)
2018-07-25 11:36:39 -04:00
Kyle Spearrin
12f7f764dc upadte jslib 2018-07-25 11:01:24 -04:00
Kyle Spearrin
d9a09a815a remove partial line 2018-07-25 09:26:04 -04:00
Kyle Spearrin
489c7934bf showPremiumCallout always after sync 2018-07-25 09:24:55 -04:00
Kyle Spearrin
5cc1438e8b inOrgWithPremium after sync 2018-07-25 09:23:29 -04:00
Kyle Spearrin
6ee2f05fb7 Whoops 2018-07-25 09:13:46 -04:00
Kyle Spearrin
695dc98010 fix url encoding issue with spaces 2018-07-25 09:10:24 -04:00
Kyle Spearrin
cbc975399c update jslib 2018-07-24 23:23:03 -04:00
Kyle Spearrin
25c643a1a3 vivaldi supports u2f 2018-07-24 21:49:09 -04:00
Kyle Spearrin
2a16be2fb2 await org premium check 2018-07-24 10:53:01 -04:00
Kyle Spearrin
105a7870bf dont show premium ad if in rog with premium 2018-07-24 10:44:52 -04:00
Kyle Spearrin
c05442dea8 delpoy script 2018-07-24 08:45:02 -04:00
Kyle Spearrin
370197aea3 Update README.md 2018-07-24 08:16:49 -04:00
Kyle Spearrin
cf09918a94 sort languages 2018-07-23 22:45:43 -04:00
Kyle Spearrin
65713477de set locale for angular app 2018-07-23 21:47:08 -04:00
Kyle Spearrin
9d4f29f0a9 add danish support 2018-07-23 20:16:49 -04:00
Kyle Spearrin
85429bc505 New Crowdin translations (#222)
* New translations messages.json (Chinese Simplified)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (French)

* New translations messages.json (German)

* New translations messages.json (Italian)

* New translations messages.json (Japanese)

* New translations messages.json (Korean)

* New translations messages.json (Portuguese)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Russian)

* New translations messages.json (Spanish)

* New translations messages.json (Swedish)

* New translations messages.json (Danish)

* New translations messages.json (Estonian)

* New translations messages.json (Ukrainian)
2018-07-23 20:13:48 -04:00
Kyle Spearrin
5b66143fbe update sub 2018-07-23 17:37:41 -04:00
Kyle Spearrin
ca78953889 password agent csv importer, resolves #221 2018-07-23 17:34:13 -04:00
Kyle Spearrin
865d475083 fix u2f ref 2018-07-23 17:15:33 -04:00
Kyle Spearrin
6b9c9f9f78 agree to policies on registration page 2018-07-23 16:37:10 -04:00
Kyle Spearrin
97dfb6fdec gnome json importer 2018-07-23 12:04:38 -04:00
Kyle Spearrin
473f5bfb62 passkeep csv importer 2018-07-23 11:41:15 -04:00
Kyle Spearrin
183f5fea5c splash id importer 2018-07-23 11:23:23 -04:00
Kyle Spearrin
69e059ba01 sort user groups 2018-07-23 10:57:25 -04:00
Kyle Spearrin
3544c96a64 nothing selected error 2018-07-21 21:58:24 -04:00
Kyle Spearrin
56c2515f01 mobile check for u2f 2018-07-21 15:45:35 -04:00
Kyle Spearrin
90d3639796 update jslib 2018-07-21 14:38:29 -04:00
Kyle Spearrin
a3e994fc95 update jslib 2018-07-21 14:07:13 -04:00
Kyle Spearrin
98b3cfaaa0 update jslib 2018-07-21 09:59:52 -04:00
Kyle Spearrin
a8f28af2b3 bind nfc 2018-07-21 09:50:50 -04:00
Kyle Spearrin
c2603c3f53 yubikey nfc true by default 2018-07-21 09:28:59 -04:00
Kyle Spearrin
949433b65f update jslib 2018-07-21 08:21:16 -04:00
Kyle Spearrin
dda1a9ecc4 update jslib 2018-07-21 00:32:49 -04:00
Kyle Spearrin
abdb40179c fix u2f params 2018-07-21 00:26:16 -04:00
Kyle Spearrin
c1efe268d0 gulp postdist tasks for version file 2018-07-21 00:11:55 -04:00
Kyle Spearrin
58818dabc5 vendor js chunk 2018-07-20 22:46:03 -04:00
Kyle Spearrin
24ee5c2d5d cleanup braintree stylesheet 2018-07-20 17:37:16 -04:00
Kyle Spearrin
93cec9a2d6 show refunded status 2018-07-20 17:20:41 -04:00
Kyle Spearrin
fe1c5b4b38 update jslib 2018-07-20 13:01:58 -04:00
Kyle Spearrin
e3e94ede65 allow custom plans to download license 2018-07-20 12:16:41 -04:00
Kyle Spearrin
b9ea3de860 premium callout 2018-07-20 10:44:17 -04:00
Kyle Spearrin
b99df5905f handle redirect params for create org/premium 2018-07-20 10:29:40 -04:00
Kyle Spearrin
a5246df3ed check for email verified on premium license upload 2018-07-19 22:04:16 -04:00
Kyle Spearrin
f4127a575b filter analytics on self host 2018-07-19 19:49:48 -04:00
Kyle Spearrin
65860b166f set environmentService baseUrl on self host 2018-07-19 17:27:02 -04:00
Kyle Spearrin
59f5304d87 no collections message 2018-07-19 17:17:30 -04:00
Kyle Spearrin
3d4848da90 zoho csv importer 2018-07-19 15:13:12 -04:00
Kyle Spearrin
f43fd34a8c fallback to en-us 2018-07-19 14:33:33 -04:00
Kyle Spearrin
bb68303b03 larn more links 2018-07-19 13:56:44 -04:00
Kyle Spearrin
236bcdfb68 skip no folder when updating key 2018-07-19 13:31:14 -04:00
Kyle Spearrin
6c7df2788e footer updates 2018-07-19 12:21:31 -04:00
Jacob
b3a344635a Fetch image from bitwarden/brand (#219) 2018-07-19 10:50:45 -04:00
Kyle Spearrin
4ca29a4a75 New Crowdin translations (#218)
* New translations messages.json (Chinese Simplified)

* New translations messages.json (Chinese Traditional)

* New translations messages.json (French)

* New translations messages.json (German)

* New translations messages.json (Italian)

* New translations messages.json (Japanese)

* New translations messages.json (Korean)

* New translations messages.json (Portuguese)

* New translations messages.json (Portuguese, Brazilian)

* New translations messages.json (Russian)

* New translations messages.json (Spanish)

* New translations messages.json (Swedish)
2018-07-19 09:37:19 -04:00
Kyle Spearrin
de35a62984 remove spansih translation file 2018-07-19 09:33:43 -04:00
Kyle Spearrin
ef886c8742 crowdin yml 2018-07-19 09:32:48 -04:00
Kyle Spearrin
1b754b435b rel="noopener" on all target _blank links 2018-07-19 09:18:31 -04:00
Kyle Spearrin
50b7ce764e trim all domains 2018-07-19 09:16:23 -04:00
Kyle Spearrin
090fa76d15 update jslib 2018-07-19 08:30:19 -04:00
Kyle Spearrin
898137c8a5 password boss importer 2018-07-19 08:05:53 -04:00
Kyle Spearrin
901a9ae9d7 ascendo dv csv importer 2018-07-19 00:01:29 -04:00
Kyle Spearrin
40a5c6f55e remove base tag 2018-07-18 22:53:16 -04:00
Kyle Spearrin
6d0651a1cb move org id to base class 2018-07-18 17:20:35 -04:00
Kyle Spearrin
ee4d2400c9 org 2fa setting for duo 2018-07-18 17:10:26 -04:00
Kyle Spearrin
5131ebb9c9 docker build updates 2018-07-18 15:21:51 -04:00
Kyle Spearrin
0a86709440 update jslib 2018-07-18 15:09:25 -04:00
Kyle Spearrin
9d5b9f0bde inject Angulartics2GoogleAnalytics 2018-07-18 12:57:20 -04:00
Kyle Spearrin
19fa769bd3 only show disable all keys if enabled 2018-07-18 12:42:06 -04:00
Kyle Spearrin
de8e2d1be7 chunkhas on packed assets 2018-07-18 12:34:08 -04:00
Kyle Spearrin
f546d682bd fixes for pathed URL 2018-07-18 12:19:16 -04:00
Kyle Spearrin
197d1c673c subscription null check 2018-07-18 12:13:24 -04:00
Kyle Spearrin
16e6a88ccf deploy with gh-pages 2018-07-18 12:11:35 -04:00
Kyle Spearrin
de43eb4a57 clean gulp task 2018-07-18 11:15:23 -04:00
Kyle Spearrin
ff64ad8df0 gulp webfonts task as part of build 2018-07-18 10:54:22 -04:00
Kyle Spearrin
e55408424e delete webfonts 2018-07-18 10:48:44 -04:00
Kyle Spearrin
3c9354ba2f local webfonts 2018-07-18 10:32:44 -04:00
Kyle Spearrin
2382d5028b passbolt csv import 2018-07-18 10:10:45 -04:00
Kyle Spearrin
7346958b27 totp upgrade fixes 2018-07-18 09:47:02 -04:00
Kyle Spearrin
78e12775e4 fix margin 2018-07-18 09:37:50 -04:00
Kyle Spearrin
a76053be58 new org button 2018-07-18 09:34:48 -04:00
Kyle Spearrin
028731458b update jslib 2018-07-18 09:21:41 -04:00
Kyle Spearrin
1cee1c6e8f premium and paid org callouts 2018-07-18 09:21:23 -04:00
Kyle Spearrin
1f6dd079cd user => organizationId 2018-07-17 23:52:03 -04:00
Kyle Spearrin
faddfe8506 roboform csv importer 2018-07-17 23:47:31 -04:00
Kyle Spearrin
223cd61220 missing loading 2018-07-17 23:27:56 -04:00
Kyle Spearrin
8278a8f3e4 remove all blur clicks 2018-07-17 23:21:23 -04:00
Kyle Spearrin
8bd9eafa37 loading titles 2018-07-17 23:15:15 -04:00
Kyle Spearrin
9a23d5fa97 billing updates 2018-07-17 22:49:53 -04:00
Kyle Spearrin
4557366154 formatting 2018-07-17 22:21:59 -04:00
Kyle Spearrin
8f95ba03ab disable and mark accessall groups 2018-07-17 22:21:09 -04:00
Kyle Spearrin
89b7672630 lint fix 2018-07-17 17:38:48 -04:00
Kyle Spearrin
49f948844f update enc key 2018-07-17 17:22:51 -04:00
Kyle Spearrin
d1395e37fd only owner can access org settings 2018-07-17 15:57:04 -04:00
Kyle Spearrin
06de7b5176 verify bank account 2018-07-17 15:53:52 -04:00
Kyle Spearrin
56b9cb5c9e adjust payment for orgs 2018-07-17 15:07:32 -04:00
Kyle Spearrin
e4a684ff10 org billing seat adjustments 2018-07-17 12:07:52 -04:00
Kyle Spearrin
e4f12ed47f download license for org 2018-07-17 11:25:41 -04:00
Kyle Spearrin
6fcda290c7 org change plan, cancel, reinstate actions 2018-07-17 11:04:40 -04:00
Kyle Spearrin
243a00e326 CVV security code 2018-07-17 10:23:13 -04:00
Kyle Spearrin
d274a83c24 clipperz html importer 2018-07-17 10:08:38 -04:00
Kyle Spearrin
bdb95e58e6 truekey csv importer 2018-07-17 00:15:18 -04:00
Kyle Spearrin
0650cafb28 org billing settings setup 2018-07-16 17:17:07 -04:00
Kyle Spearrin
786f6953e7 my organization page 2018-07-16 12:42:49 -04:00
Kyle Spearrin
a1d52af0ba generate qr code locally 2018-07-13 23:15:09 -04:00
Kyle Spearrin
1da85c96cf stick password xml importer 2018-07-13 17:11:31 -04:00
Kyle Spearrin
84dc1d1b74 verify recovery delete page 2018-07-13 16:24:53 -04:00
Kyle Spearrin
63aa55baf1 recover delete request page 2018-07-13 15:54:49 -04:00
Kyle Spearrin
3b28e68e31 recover 2fa 2018-07-13 15:36:27 -04:00
Kyle Spearrin
19d835c793 more form fixes 2018-07-13 14:55:50 -04:00
Kyle Spearrin
df9282e759 adjust form requirements and verbatim input 2018-07-13 14:50:21 -04:00
Kyle Spearrin
c679726564 lead font weight 2018-07-13 11:23:41 -04:00
Kyle Spearrin
7e6f9eb67a register state service 2018-07-13 11:09:24 -04:00
Kyle Spearrin
3cfe8bf751 org invite accept flow on login/register 2018-07-13 10:51:52 -04:00
Kyle Spearrin
a1495a8f0c update jslib 2018-07-13 09:31:40 -04:00
Kyle Spearrin
fe6a40f7d0 move focus to base 2018-07-13 09:29:22 -04:00
Kyle Spearrin
e5733b83a0 remember email on login 2018-07-13 09:13:37 -04:00
Kyle Spearrin
1dc4f851cb hasEncKey checks 2018-07-12 17:09:09 -04:00
Kyle Spearrin
ba2debf577 msecure csv importer 2018-07-12 16:27:37 -04:00
Kyle Spearrin
470a767eaf accept org invite 2018-07-12 16:05:42 -04:00
Kyle Spearrin
8e3d5b99c5 dashlane csv importer 2018-07-12 15:53:34 -04:00
Kyle Spearrin
763e43905a verify email page 2018-07-12 14:19:47 -04:00
Kyle Spearrin
ab4005ae00 verify email and outdated browser callouts 2018-07-12 11:34:51 -04:00
Kyle Spearrin
0c61e48977 update layouts for user groups and collections 2018-07-12 10:41:38 -04:00
Kyle Spearrin
8526ad17c4 add missing strings for date filters 2018-07-12 10:26:22 -04:00
Kyle Spearrin
ee91f4610e check for confirm status before showing events 2018-07-12 10:14:33 -04:00
Kyle Spearrin
1afc05310e pwsafe xml importer 2018-07-12 09:49:00 -04:00
Kyle Spearrin
ed381bcf0a fix orgs load 2018-07-12 09:14:20 -04:00
Kyle Spearrin
59aaa07e3b set bg color of callout 2018-07-12 09:12:04 -04:00
Kyle Spearrin
4d759a6995 enpass csv importer 2018-07-12 00:11:14 -04:00
Kyle Spearrin
ce00587041 added password dragon xml importer 2018-07-11 23:30:56 -04:00
Kyle Spearrin
50aad69189 kepper csv importer 2018-07-11 17:43:35 -04:00
Kyle Spearrin
2dc04fa041 status filters for user list 2018-07-11 16:40:32 -04:00
Kyle Spearrin
6dd21fe9e9 wire up search and view events query params 2018-07-11 15:44:40 -04:00
Kyle Spearrin
98d3b42728 generic event log component for user/ciphers 2018-07-11 15:22:55 -04:00
Kyle Spearrin
6d225beb46 user events 2018-07-11 14:43:00 -04:00
Kyle Spearrin
1f7ca7386a reinvite and confirm users 2018-07-11 13:30:17 -04:00
Kyle Spearrin
455df290bf load orgs manually from vault 2018-07-11 11:39:48 -04:00
Kyle Spearrin
2d2192f2f6 update jslib 2018-07-11 10:26:42 -04:00
Kyle Spearrin
175d5ec57f onepassword importers 2018-07-11 10:18:40 -04:00
Kyle Spearrin
255886df53 add more importers 2018-07-11 00:03:40 -04:00
Kyle Spearrin
f0bc2d9c9b add keepass importer 2018-07-10 17:58:08 -04:00
Kyle Spearrin
dc778ea578 import isntructions for safeincloud and padlock 2018-07-10 16:44:13 -04:00
Kyle Spearrin
bd0d321dba add padlock importer, move from maps to arrays 2018-07-10 16:39:04 -04:00
Kyle Spearrin
57e13c25b5 manage user groups 2018-07-10 15:03:13 -04:00
Kyle Spearrin
b428660f92 invite/edit org users 2018-07-10 14:46:13 -04:00
Kyle Spearrin
a8618f1c79 modal widths are fixed 2018-07-10 11:02:25 -04:00
Kyle Spearrin
716619e7f1 always operate in xl breakpoint 2018-07-10 10:55:36 -04:00
Kyle Spearrin
b8da4cf6fc remove viewport meta 2018-07-10 10:47:31 -04:00
Kyle Spearrin
9432abbd1a no group/collections message 2018-07-10 10:07:52 -04:00
Kyle Spearrin
edef454043 collection add/edit modal 2018-07-10 10:06:57 -04:00
Kyle Spearrin
febc3093a9 restrict access based on org permissions 2018-07-10 09:19:29 -04:00
Kyle Spearrin
ddd832d016 fix event i18n ids 2018-07-10 08:47:34 -04:00
Kyle Spearrin
137bb3a4c2 allow removeal of accessall users on groups 2018-07-10 08:42:15 -04:00
Kyle Spearrin
5ade229cb9 revert usage of THING translations 2018-07-10 08:39:05 -04:00
Kyle Spearrin
a6bd9ecfa3 remove text button 2018-07-10 08:20:22 -04:00
Kyle Spearrin
1cb396dc51 entity users component 2018-07-09 23:48:26 -04:00
Kyle Spearrin
a27357213a external id desc 2018-07-09 21:44:48 -04:00
Kyle Spearrin
2d4676ed7e primary outline btn 2018-07-09 17:21:13 -04:00
Kyle Spearrin
3e9ac965e8 Merge branch 'master' of github.com:bitwarden/web 2018-07-09 17:07:16 -04:00
Kyle Spearrin
a45c77bda9 aot compilation fixes 2018-07-09 17:07:13 -04:00
Kyle Spearrin
e659fd1262 Update README.md 2018-07-09 17:02:03 -04:00
Kyle Spearrin
7459f1d1ad Update README.md 2018-07-09 17:01:15 -04:00
Kyle Spearrin
637120d0c5 make shareableCiphers public 2018-07-09 16:56:09 -04:00
Kyle Spearrin
cd131b2d21 use https for submodule 2018-07-09 16:52:46 -04:00
Kyle Spearrin
90dd0d0b9b Update README.md 2018-07-09 16:38:21 -04:00
Kyle Spearrin
3358846d41 Merge branch 'angular' 2018-07-09 16:34:17 -04:00
Mart124
473fc1a766 Add a bitwarden label to docker images (#210)
* Add a bitwarden label to docker images

* Prefix label with reverse DNS
2018-07-09 16:33:05 -04:00
Kyle Spearrin
ecfecd295a group add/edit/delete 2018-07-09 16:27:54 -04:00
Kyle Spearrin
b774091b83 placeholders showing format for datetime 2018-07-09 14:29:19 -04:00
Kyle Spearrin
f49640cbe6 event page tweaks 2018-07-09 12:22:32 -04:00
Kyle Spearrin
8b4fadde2b update jslib 2018-07-09 11:49:24 -04:00
Kyle Spearrin
1b94b22360 view l10n 2018-07-09 11:49:08 -04:00
Kyle Spearrin
b090de0da1 event info 2018-07-09 11:47:57 -04:00
Kyle Spearrin
0294c2cb6d device types for web 2018-07-09 09:32:24 -04:00
Kyle Spearrin
d9bcce781a style updates 2018-07-08 00:27:37 -04:00
Kyle Spearrin
3451322a38 org import has all options 2018-07-08 00:01:50 -04:00
Kyle Spearrin
ba604fcd3c update jslib 2018-07-07 23:50:31 -04:00
Kyle Spearrin
c7df888d39 add safeincloud imports 2018-07-07 23:50:22 -04:00
Kyle Spearrin
7b80505f69 api service to audit service 2018-07-07 23:50:06 -04:00
Kyle Spearrin
de5914194e ie11 style fixes 2018-07-07 23:49:48 -04:00
Kyle Spearrin
e9da73b930 event filters and paging 2018-07-06 23:08:10 -04:00
Kyle Spearrin
15cc46bba5 loading styles 2018-07-06 16:36:51 -04:00
Kyle Spearrin
417743ccdd style tweaks 2018-07-06 16:21:19 -04:00
Kyle Spearrin
0f04286783 change org nav bg color 2018-07-06 16:13:12 -04:00
Kyle Spearrin
3e0f2126b3 list styling for people 2018-07-06 15:45:35 -04:00
Kyle Spearrin
656d17cc07 id searching 2018-07-06 15:03:54 -04:00
Kyle Spearrin
35bb106654 people listing 2018-07-06 15:01:23 -04:00
Kyle Spearrin
634e5e27d3 sort groups 2018-07-06 14:22:20 -04:00
Kyle Spearrin
0f9186628b sort orgs 2018-07-06 14:19:49 -04:00
Kyle Spearrin
6267ca52fc remove form inline 2018-07-06 14:15:10 -04:00
Kyle Spearrin
06b32dc6ec cast fixes 2018-07-06 12:55:59 -04:00
Kyle Spearrin
93582da044 collection and group listing from org admin 2018-07-06 12:49:06 -04:00
Kyle Spearrin
d830499c76 stub out org manage pages 2018-07-06 10:21:08 -04:00
Kyle Spearrin
30d1a119f3 lint fix 2018-07-06 10:07:03 -04:00
Kyle Spearrin
5efab1d495 add my account to nav dropdown 2018-07-06 10:04:53 -04:00
Kyle Spearrin
1228010488 avatar in nav dropdown 2018-07-06 10:00:09 -04:00
Kyle Spearrin
b8921713eb nav dropdown menu styling 2018-07-06 09:47:50 -04:00
Kyle Spearrin
0b779dfd3d layout fixes 2018-07-06 00:29:10 -04:00
Kyle Spearrin
311baaa3d1 navigate fix on import success 2018-07-05 23:44:58 -04:00
Kyle Spearrin
6f75e0bba0 org imports 2018-07-05 23:38:36 -04:00
Kyle Spearrin
6193bf431d set page titles 2018-07-05 22:37:35 -04:00
Kyle Spearrin
7baf72d3db org route guards 2018-07-05 15:27:23 -04:00
Kyle Spearrin
ba3b2fbed1 exporting organization data 2018-07-05 14:40:53 -04:00
Kyle Spearrin
9d4e0849d6 move org vault stuff into subfolder 2018-07-05 13:14:33 -04:00
Kyle Spearrin
1a51a7bf5b default collection is i18n 2018-07-05 13:08:29 -04:00
Kyle Spearrin
7d42c4eaa0 org vault collections 2018-07-05 12:56:58 -04:00
Kyle Spearrin
1d60e881ee admin cipher attachments 2018-07-05 10:48:51 -04:00
Kyle Spearrin
fda8155894 set org id when encrypting new 2018-07-05 10:10:30 -04:00
Kyle Spearrin
f089b5e3d1 inherit local base for groupings and ciphers 2018-07-05 09:58:15 -04:00
Kyle Spearrin
f578ebe4ef org cipher add/edit 2018-07-05 09:42:50 -04:00
Kyle Spearrin
b97378dd40 sync after premium/org purchase 2018-07-05 08:39:22 -04:00
Kyle Spearrin
32f62b7ceb org component, org vault listing updates 2018-07-04 09:55:52 -04:00
Kyle Spearrin
db43f817f7 unassigned collection filtering 2018-07-03 23:43:57 -04:00
Kyle Spearrin
3c45e7dac9 org vault listing from apis 2018-07-03 23:33:19 -04:00
Kyle Spearrin
f1584ad7d7 org vault listing from apis 2018-07-03 23:33:12 -04:00
Kyle Spearrin
8f503f4f99 org component loaded independently 2018-07-03 16:04:38 -04:00
Kyle Spearrin
499ecf9c39 org layout 2018-07-03 15:44:33 -04:00
Kyle Spearrin
9cf15e37ce add circle input for avatar 2018-07-03 15:11:58 -04:00
Kyle Spearrin
6d731e2939 setup org module and link org listing 2018-07-03 12:34:20 -04:00
Kyle Spearrin
1e7c2c2362 add name to registration form 2018-07-03 11:42:50 -04:00
Kyle Spearrin
ae51847f02 premium link 2018-07-03 09:58:35 -04:00
Kyle Spearrin
738eaa6ca7 create by license moved from update license component 2018-07-03 09:55:59 -04:00
Kyle Spearrin
70dbca67e7 move premium component to it's own route 2018-07-03 09:27:59 -04:00
Kyle Spearrin
16c3d4c253 style updates on total 2018-07-03 00:16:34 -04:00
Kyle Spearrin
1f62b9fdcb org create 2018-07-02 17:09:53 -04:00
Kyle Spearrin
bd070ff066 no organizations message 2018-07-02 10:58:07 -04:00
Kyle Spearrin
3bb667f524 create and update premium license for self host 2018-07-02 10:30:51 -04:00
Kyle Spearrin
463c1f8b77 alt color navbar on self host 2018-06-30 14:19:01 -04:00
Kyle Spearrin
c5d575b9b6 self host builds 2018-06-30 14:14:52 -04:00
Kyle Spearrin
d509637623 adjust payment 2018-06-30 13:36:39 -04:00
Kyle Spearrin
37026e556f adjust storage implementation 2018-06-29 23:41:35 -04:00
Kyle Spearrin
f7b9416460 user billing page 2018-06-29 16:55:54 -04:00
Kyle Spearrin
1ac22c6d48 col6 2018-06-29 10:49:46 -04:00
Kyle Spearrin
66cb7f9bf0 card brand images 2018-06-29 10:27:04 -04:00
Kyle Spearrin
87f7b782dc blur csv import 2018-06-29 09:28:02 -04:00
Kyle Spearrin
15d6e4937f changed breach report to allow username entry 2018-06-29 08:21:08 -04:00
Kyle Spearrin
dec6b17aa4 missing premium updated message 2018-06-28 23:11:47 -04:00
Kyle Spearrin
4a2b3d6293 load premium status after pruchase 2018-06-28 23:05:49 -04:00
Kyle Spearrin
04ef16a94b premium callout 2018-06-28 22:40:07 -04:00
Kyle Spearrin
fd88a066da payment component and styling 2018-06-28 22:27:32 -04:00
Kyle Spearrin
7267045475 stub out premium payment with payment form 2018-06-28 17:17:14 -04:00
Kyle Spearrin
318b750603 stub out billing component 2018-06-28 14:05:04 -04:00
Kyle Spearrin
38e87b1af0 breach report styling 2018-06-28 11:59:49 -04:00
Kyle Spearrin
ce96a32af3 data breach report 2018-06-28 11:58:33 -04:00
Kyle Spearrin
0ec9045849 u2f logo 2018-06-28 09:46:27 -04:00
Kyle Spearrin
b9895ba79a 2fa recovery code view 2018-06-28 09:40:11 -04:00
Kyle Spearrin
ff65297275 consolidate 2fa component functionality 2018-06-27 23:55:50 -04:00
Kyle Spearrin
c647c0f509 lint fixes 2018-06-27 22:09:50 -04:00
Kyle Spearrin
5760efdaa1 u2d lib is externals 2018-06-27 22:05:33 -04:00
Kyle Spearrin
bf5d8cab1c implement 2fa setup for u2f 2018-06-27 17:50:31 -04:00
Kyle Spearrin
890bf49294 email 2fa setup 2018-06-27 16:56:11 -04:00
Kyle Spearrin
a097793b0d configure duo 2fa 2018-06-27 15:27:59 -04:00
Kyle Spearrin
4b4bedaef3 yubikey 2fa config 2018-06-27 13:45:11 -04:00
Kyle Spearrin
246d605e5c autofocus any type of element on modal shown 2018-06-27 09:45:26 -04:00
Kyle Spearrin
f43319ba7a focus any necessary input elements on modal shown 2018-06-27 09:38:44 -04:00
Kyle Spearrin
09ef907673 authenticator setup 2018-06-27 09:20:09 -04:00
Kyle Spearrin
e345bbcae0 implement two-factor setup page 2018-06-26 22:51:58 -04:00
Kyle Spearrin
381da132f8 sync is not forced 2018-06-26 12:03:11 -04:00
Kyle Spearrin
998a63612f domain rules page implementation 2018-06-26 11:50:23 -04:00
Kyle Spearrin
63ccc49ce2 import analytics 2018-06-26 09:11:29 -04:00
Kyle Spearrin
ce3b4cd817 save button for options 2018-06-26 09:04:12 -04:00
Kyle Spearrin
28f4ed9144 some styling 2018-06-26 08:49:45 -04:00
Kyle Spearrin
4bd47f728a stub out domain rules page 2018-06-25 23:16:59 -04:00
Kyle Spearrin
03dfda7a17 callout component 2018-06-25 20:49:49 -04:00
Kyle Spearrin
1717960a8c options page description 2018-06-25 17:17:55 -04:00
Kyle Spearrin
bb14aa821b options 2018-06-25 16:44:06 -04:00
Kyle Spearrin
fd8128dfe9 callout 2018-06-25 16:04:19 -04:00
Kyle Spearrin
9b67f6e398 improt instructions 2018-06-25 15:47:05 -04:00
Kyle Spearrin
e59277742d avira csv importer 2018-06-25 15:20:58 -04:00
Kyle Spearrin
53d23ec831 update cipher lsiting properly when folder deleted 2018-06-25 15:20:45 -04:00
Kyle Spearrin
a2a6d89908 new folder service deps 2018-06-25 15:20:18 -04:00
Kyle Spearrin
be29e6d847 update jslib 2018-06-25 11:40:13 -04:00
Kyle Spearrin
c26a6a5252 encrypt and post import to api 2018-06-25 11:39:55 -04:00
Kyle Spearrin
ce01fe6141 importing 2018-06-23 15:16:23 -04:00
Kyle Spearrin
39ff952667 export vault on buttton 2018-06-23 09:27:46 -04:00
Kyle Spearrin
cafc65ffa5 blur clicks 2018-06-21 22:41:36 -04:00
Kyle Spearrin
cccd2abb55 purge vault and delete account features 2018-06-21 22:40:01 -04:00
Kyle Spearrin
cb1a62ee27 set proper api url 2018-06-21 22:10:20 -04:00
Kyle Spearrin
51efa59728 set api urls for dev 2018-06-21 21:32:17 -04:00
Kyle Spearrin
aed5db0a8c deauth sessions 2018-06-21 17:14:36 -04:00
Kyle Spearrin
0599dd1525 no toggle on export password field 2018-06-21 15:59:31 -04:00
Kyle Spearrin
989f4c3aa5 danger zone 2018-06-21 15:57:28 -04:00
Kyle Spearrin
22093d5111 change master password implementation 2018-06-21 15:30:17 -04:00
Kyle Spearrin
7022bf005f update jslib 2018-06-21 14:29:09 -04:00
Kyle Spearrin
68b8ad7e28 change email components implemented 2018-06-21 14:28:00 -04:00
Kyle Spearrin
306f8a43e1 stub out change email/password components 2018-06-21 11:47:23 -04:00
Kyle Spearrin
e46f3073b4 move profile to its own component 2018-06-21 11:43:50 -04:00
Kyle Spearrin
ed65bcf185 fallback to email on avatar 2018-06-21 11:29:32 -04:00
Kyle Spearrin
9633800977 avatar component 2018-06-21 11:28:14 -04:00
Kyle Spearrin
1fb4f2946a submit button spinners 2018-06-21 10:06:32 -04:00
Kyle Spearrin
d73b01674f update profile 2018-06-20 23:35:40 -04:00
Kyle Spearrin
271510ffb5 stub out settings 2018-06-20 22:27:37 -04:00
Kyle Spearrin
16b29c6116 cleanup generator 2018-06-20 22:12:54 -04:00
Kyle Spearrin
367104f0a7 added password generator tool 2018-06-20 18:16:20 -04:00
Kyle Spearrin
7979953f33 export data 2018-06-20 16:28:56 -04:00
Kyle Spearrin
5ffd13e2c8 totp code generation on add/edit page 2018-06-19 23:40:51 -04:00
Kyle Spearrin
edb1700218 monospaced card code input 2018-06-18 17:32:59 -04:00
Neil Burrows
9729a2595d Card Code Mask in Angular Branch (#212) 2018-06-18 07:42:58 -04:00
Kyle Spearrin
7c3468fbcb update jslib 2018-06-13 22:14:55 -04:00
Kyle Spearrin
94c5bdb5aa implement hasKey helper 2018-06-13 17:21:27 -04:00
Kyle Spearrin
e714d61a66 update jslib 2018-06-13 17:15:23 -04:00
Kyle Spearrin
14ecc534e0 setUrlsFromStorage in init 2018-06-13 14:09:16 -04:00
Kyle Spearrin
e18f76d2b0 stub out bulk share 2018-06-13 00:03:48 -04:00
Kyle Spearrin
3edf761549 bulk move 2018-06-12 17:33:08 -04:00
Kyle Spearrin
27f18c1630 update jslib 2018-06-12 17:12:34 -04:00
Kyle Spearrin
314ab61349 bulk actions stubbed out. bulk delete implemented 2018-06-12 17:11:24 -04:00
Kyle Spearrin
3666ee5a87 only use blob options if available and not IE 2018-06-12 14:35:44 -04:00
Kyle Spearrin
1f3ecbab33 delete from cipher list 2018-06-12 14:15:19 -04:00
Kyle Spearrin
4aacc06af0 disabled button styling 2018-06-12 14:06:42 -04:00
Kyle Spearrin
ddab383b55 manage item collections 2018-06-12 13:08:47 -04:00
Kyle Spearrin
edef3f90f1 sharing 2018-06-12 11:46:11 -04:00
Kyle Spearrin
85fd36f5c3 update jslib 2018-06-11 13:40:26 -04:00
Kyle Spearrin
d16f599db9 fixes for duo 2018-06-11 12:58:56 -04:00
Kyle Spearrin
ba6451856a frontend footer 2018-06-11 12:06:57 -04:00
Kyle Spearrin
f1651078e4 fido u2f fixes 2018-06-11 11:58:11 -04:00
Kyle Spearrin
a6aef345d5 two-step login pages 2018-06-11 11:43:10 -04:00
Kyle Spearrin
4df4f57de3 logo height 2018-06-11 10:27:36 -04:00
Kyle Spearrin
5bd9a9a81d loaded state for orgs 2018-06-11 10:26:02 -04:00
Kyle Spearrin
a0ac8ec9c2 org link styles 2018-06-11 10:13:06 -04:00
Kyle Spearrin
2bac2f1a39 organizations component in vault 2018-06-11 10:09:59 -04:00
Kyle Spearrin
ab353d8498 add cipher from list 2018-06-11 09:43:22 -04:00
Kyle Spearrin
f054365a46 cache tag 2018-06-11 09:37:29 -04:00
Kyle Spearrin
cfc3fae67c application version 2018-06-11 09:31:11 -04:00
Kyle Spearrin
5d8e32222a use html storage for secure storage in dev 2018-06-11 09:17:41 -04:00
Kyle Spearrin
21a126f31f no items to list 2018-06-11 09:17:24 -04:00
Kyle Spearrin
d21540878e prod build fixes 2018-06-11 08:54:48 -04:00
Kyle Spearrin
894ab16b35 page header styles 2018-06-09 23:43:33 -04:00
Kyle Spearrin
945d661a1e tools pages stubbed out 2018-06-09 23:33:12 -04:00
Kyle Spearrin
a176a4819f logo styling and lock now 2018-06-09 23:13:19 -04:00
Kyle Spearrin
6892badf36 mute color on lock 2018-06-09 22:59:36 -04:00
Kyle Spearrin
5db9e9531f route to previous url after unlock 2018-06-09 22:40:53 -04:00
Kyle Spearrin
7ebd18b00d auth guards 2018-06-09 22:02:45 -04:00
Kyle Spearrin
9f1c7a0a32 html/memory storage services 2018-06-09 15:26:21 -04:00
Kyle Spearrin
561120c51c add local and memory storage to web storage service 2018-06-09 15:19:05 -04:00
Kyle Spearrin
b93b9feee4 enable lock timeout 2018-06-09 14:55:34 -04:00
Kyle Spearrin
ea6317e3a2 lock screen. cleanup modals on navigate 2018-06-09 13:59:09 -04:00
Kyle Spearrin
5ecb26b032 refresh list if attachments change 2018-06-09 13:29:30 -04:00
Mart124
c1a0818376 Add a bitwarden label to docker images (#210)
* Add a bitwarden label to docker images

* Prefix label with reverse DNS
2018-06-09 08:17:56 -04:00
Kyle Spearrin
fbc756c6e3 style fixes on add/edit lists 2018-06-09 00:39:14 -04:00
Kyle Spearrin
8f258a2d05 frontend page styling 2018-06-09 00:38:55 -04:00
Kyle Spearrin
d4d01cc186 set frontend layout body class 2018-06-08 17:37:39 -04:00
Kyle Spearrin
5098153fde layouts and messaging 2018-06-08 17:08:19 -04:00
Kyle Spearrin
63a56b359b swal styling 2018-06-08 15:20:07 -04:00
Kyle Spearrin
38a95c3745 dialogs with swal 2018-06-08 14:56:26 -04:00
Kyle Spearrin
f05c6ccddd attachments list icon 2018-06-08 12:45:31 -04:00
Kyle Spearrin
de9780a756 toaster styles 2018-06-08 12:32:00 -04:00
Kyle Spearrin
b34e511ddc savefile imeplementation 2018-06-08 12:23:46 -04:00
Kyle Spearrin
3de3c7a189 add download file function for attachments 2018-06-08 12:16:47 -04:00
Kyle Spearrin
d256a872fc cipher attachments modal 2018-06-08 12:04:03 -04:00
Kyle Spearrin
3db86e2a6b move duo connector to ts 2018-06-08 10:15:45 -04:00
Kyle Spearrin
6436bb65e2 IE fixes and polyfills 2018-06-07 23:38:17 -04:00
Kyle Spearrin
059df9c45d various vault functionality 2018-06-07 17:12:11 -04:00
Kyle Spearrin
a18a591f0a layouts for cipher/folder add/edit 2018-06-07 10:21:28 -04:00
Kyle Spearrin
08dc36fbb0 some i18n and modal work 2018-06-07 09:05:25 -04:00
Kyle Spearrin
a0ace6e70f adjust viewport width 2018-06-06 23:38:30 -04:00
Kyle Spearrin
a52aadd37d add/edit cipher modal 2018-06-06 23:23:14 -04:00
Kyle Spearrin
0fb66e247a search and move dropdown button 2018-06-06 23:00:57 -04:00
Kyle Spearrin
ecddc1691f style updates 2018-06-06 17:42:42 -04:00
Kyle Spearrin
b0f683bacf update jslib 2018-06-06 17:27:13 -04:00
Kyle Spearrin
a89cf28812 get vault working 2018-06-06 17:25:57 -04:00
Kyle Spearrin
cc9410602c vault layout 2018-06-06 12:35:10 -04:00
Kyle Spearrin
e814b8ef09 theme styling 2018-06-06 11:58:56 -04:00
Kyle Spearrin
ce34ef902f stubbing out some layout 2018-06-06 09:43:28 -04:00
Kyle Spearrin
db5a5e1b37 toastr container 2018-06-05 15:46:26 -04:00
Kyle Spearrin
9e3f3e324c add missing modules for prod build 2018-06-05 15:06:50 -04:00
Kyle Spearrin
a4ff241574 setup services, accounts components 2018-06-05 15:02:53 -04:00
Kyle Spearrin
28197970bd add jslib 2018-06-05 13:31:25 -04:00
Kyle Spearrin
7aa4450556 webpack adjustments 2018-06-05 11:52:09 -04:00
Kyle Spearrin
dabc37a1ef remove test scripts 2018-06-05 11:16:22 -04:00
Kyle Spearrin
4315000905 rename dev cert to shared 2018-06-05 11:14:53 -04:00
Kyle Spearrin
8d4bd1171b remove old cert 2018-06-05 11:14:15 -04:00
Kyle Spearrin
82c5a53f5d es5 support 2018-06-05 09:34:08 -04:00
Kyle Spearrin
29d0803a5c scripts, connectors, and webpack adjustments 2018-06-05 09:01:40 -04:00
Kyle Spearrin
8f14ced5af u2f connector updates 2018-06-05 00:32:06 -04:00
Kyle Spearrin
9cafb76781 enable prod mode on production env 2018-06-05 00:31:06 -04:00
Kyle Spearrin
22c1b0627e https webpack-serve 2018-06-05 00:02:43 -04:00
Kyle Spearrin
995835a96c copy over some necessary assets 2018-06-04 23:26:05 -04:00
Kyle Spearrin
bf796fe80c stub out angular app with webpack 4 2018-06-04 23:10:41 -04:00
Kyle Spearrin
425029783b cleanup index 2018-06-04 21:55:38 -04:00
Kyle Spearrin
8f399f3c64 cleanout old angular.js app 2018-06-04 21:52:26 -04:00
Kyle Spearrin
192eb7e7c9 bump version 2018-05-31 14:33:00 -04:00
Kyle Spearrin
48a36649fa Merge branch 'master' of github.com:bitwarden/web 2018-05-30 22:36:12 -04:00
Kyle Spearrin
6b83b53401 delete old sln file 2018-05-30 22:36:10 -04:00
Mart124
c953c2b93f Rework service user (#208)
Related to https://github.com/bitwarden/core/pull/292
2018-05-29 16:46:04 -04:00
Kyle Spearrin
7cdce165fb pass token and org user id to registration 2018-05-24 16:51:47 -04:00
Kyle Spearrin
b393064f26 reset folder id for ciphers. resolves #204 2018-05-24 09:21:43 -04:00
Kyle Spearrin
31bf22063e Update README.md 2018-05-12 13:56:19 -04:00
Kyle Spearrin
cd9a43f359 update importer for firefox 2018-05-11 08:04:21 -04:00
Kyle Spearrin
c326e03eb2 null check on subtle 2018-05-05 21:28:34 -04:00
Kyle Spearrin
f0a986aa04 remove old api 2018-04-24 11:47:16 -04:00
Kyle Spearrin
4225da355d publish 2018-04-19 21:21:55 -04:00
Kyle Spearrin
14bac6a744 fix userid comparisons 2018-04-16 16:26:54 -04:00
Kyle Spearrin
05cc9b45e6 bump version 2018-04-16 16:10:53 -04:00
Kyle Spearrin
dba596bf35 fix if when no currentid 2018-04-16 16:08:59 -04:00
Kyle Spearrin
db39d58ea8 remove empty uri on add 2018-04-16 15:17:50 -04:00
Kyle Spearrin
c0f38216ef manage group from entrypoint 2018-04-16 15:17:13 -04:00
Kyle Spearrin
3643222b3c added org duo to 2fa flow 2018-04-03 14:33:00 -04:00
Kyle Spearrin
551217ea38 filter for unassigned collection grouping 2018-04-03 08:35:45 -04:00
Kyle Spearrin
24bf1363ab org 2fa management for duo 2018-04-02 23:19:04 -04:00
Kyle Spearrin
08b2184e12 version bump 2018-04-02 21:22:30 -04:00
Kyle Spearrin
b73161882c make user homedir with helper 2018-04-02 21:12:45 -04:00
Kyle Spearrin
e2186ecd62 Revert "make user home dir"
This reverts commit b407402f3f.
2018-04-02 21:12:02 -04:00
Kyle Spearrin
b407402f3f make user home dir 2018-04-02 19:59:11 -04:00
Kyle Spearrin
8bb4132458 version bump 2018-03-30 10:56:24 -04:00
Kyle Spearrin
443822fd52 step down from host root LUID 2018-03-27 22:56:50 -04:00
Kyle Spearrin
68427fd2de bash 2018-03-27 21:15:16 -04:00
Kyle Spearrin
c3d3369601 proper and syntax for entrypoint conditions 2018-03-27 17:11:48 -04:00
Kyle Spearrin
3c5022d628 upsert bitwarden user 2018-03-27 16:37:50 -04:00
Kyle Spearrin
832ddddc58 gosu 2018-03-27 15:44:25 -04:00
Kyle Spearrin
0fc1415a06 chown deep directories 2018-03-26 14:30:37 -04:00
Kyle Spearrin
1ab408c591 non-root docker 2018-03-26 11:24:09 -04:00
Kyle Spearrin
3160d3f275 disable uglify for now 2018-03-24 20:55:00 -04:00
Kyle Spearrin
d083f1ddc3 version bump and lint fix 2018-03-24 20:49:20 -04:00
Kyle Spearrin
5fbc09b135 cannot create item in collection.
set collection after share.
2018-03-24 20:44:51 -04:00
Kyle Spearrin
6282fabf98 use bitwarden user for docker 2018-03-23 21:21:01 -04:00
Kyle Spearrin
2b528bad97 version json file on dist 2018-03-23 13:04:59 -04:00
Kyle Spearrin
c3be8195fd no edit/del of "no folder" 2018-03-20 15:58:00 -04:00
Kyle Spearrin
39471d0421 loading ciphers false after first chunk 2018-03-19 11:33:52 -04:00
Kyle Spearrin
7a50c0536c loading switches for cipher and groupings 2018-03-19 11:28:23 -04:00
Kyle Spearrin
4ccd9501a8 add back missing select function 2018-03-19 11:14:28 -04:00
Kyle Spearrin
75c05a4a85 version bump in settings 2018-03-17 12:03:07 -04:00
Kyle Spearrin
ca7e12370f version bump 2018-03-17 12:02:00 -04:00
Kyle Spearrin
8bc9dafff2 vault fixes 2018-03-17 12:01:03 -04:00
Kyle Spearrin
dcb0416fd6 re-factor vault listings 2018-03-17 11:42:35 -04:00
Kyle Spearrin
bbb69bba26 Update ISSUE_TEMPLATE.md 2018-03-10 16:36:53 -05:00
Kyle Spearrin
c1838b48ff Create ISSUE_TEMPLATE.md 2018-03-10 09:48:22 -05:00
Kyle Spearrin
d53f40002c totp-col breaks at sm, not md 2018-03-09 23:07:43 -05:00
Kyle Spearrin
866954b180 fix lint issues 2018-03-09 16:42:10 -05:00
Kyle Spearrin
befa9cbf08 version bump 2018-03-09 16:39:17 -05:00
Kyle Spearrin
859f44db43 only perpend http if there is no protocol 2018-03-05 22:15:22 -05:00
Kyle Spearrin
cca9c3c561 get rid of apps page and link to bitwarden.com 2018-03-02 22:42:32 -05:00
Kyle Spearrin
27e68e4c75 multi uri support for import/export 2018-03-02 22:13:53 -05:00
Kyle Spearrin
5c92350ed2 refactor for cipher response. add login uris. 2018-03-02 21:12:26 -05:00
Kyle Spearrin
b94c62d1e5 upadte security md 2018-02-27 23:00:10 -05:00
Kyle Spearrin
de888d8a37 remove pwnedtest 2018-02-27 22:42:39 -05:00
Kyle Spearrin
f8d6816101 Uppercase Bitwarden 2018-02-27 22:41:27 -05:00
Kyle Spearrin
119c6d5817 big-textarea not important 2018-02-27 08:21:26 -05:00
Kyle Spearrin
aaa21daa29 only intercept with headers when api is at start 2018-02-26 23:18:03 -05:00
Kyle Spearrin
10f41bf288 pwned test 2018-02-26 22:52:56 -05:00
Kyle Spearrin
91582691d8 whiteListedDomains for jwt 2018-02-26 13:48:26 -05:00
Kyle Spearrin
463efc2254 use new admin apis for attachments 2018-02-24 14:36:13 -05:00
Kyle Spearrin
0333354271 version bump 2018-02-20 23:34:10 -05:00
Kyle Spearrin
b85f56c681 restore collection ids on edit. resolves #174 2018-02-09 10:39:18 -05:00
Kyle Spearrin
be491be2cd Update organizationBilling.html 2018-02-04 16:00:00 -05:00
Kyle Spearrin
4be4a8115d Update settingsBilling.html 2018-02-04 15:58:41 -05:00
Kyle Spearrin
c0eb499f4d value.type should not be case sensitive 2018-01-26 11:55:57 -05:00
Kyle Spearrin
1b43f3facd check for empty name on SIC importer 2018-01-25 21:22:17 -05:00
Chuck
26d41d3cb9 Change npm to use https for gulp-gh-pages restore. (#168)
When using VS 2017 node.js integration, npm fails because a host key cannot be validated. Switching to https, provides security and no additional configuration to restore the package.
2018-01-23 11:43:51 -05:00
Kyle Spearrin
179765f6e4 use random bytes for each HMAC comparison 2018-01-18 12:07:32 -05:00
Kyle Spearrin
df2e332134 macBuf must exist if key has macKey 2018-01-18 09:03:51 -05:00
Kyle Spearrin
2952f9d158 manifest.json included with dist 2018-01-02 23:54:10 -05:00
Kyle Spearrin
3c9face597 disable autocomplete on duo and yubi setup 2018-01-02 23:38:54 -05:00
Kyle Spearrin
25f2e9c1b7 autocomplete="new-password" to disable autofilling 2018-01-02 22:49:05 -05:00
Kyle Spearrin
a6f8e1b9a3 duo connector moved to its own js file 2018-01-02 13:20:58 -05:00
Kyle Spearrin
d832031cec update cdn libs 2017-12-29 09:45:44 -05:00
Kyle Spearrin
7a1a3ab64d revert uglify removal 2017-12-29 09:28:49 -05:00
Kyle Spearrin
19491a684e additional user/pw field names for roboform 2017-12-29 08:43:07 -05:00
Kyle Spearrin
757224287e disable uglify since it seems to be conflicting 2017-12-29 08:40:37 -05:00
Kyle Spearrin
c9b5426f6f version bump 2017-12-28 12:56:00 -05:00
Kyle Spearrin
bf885c184f lint fixes 2017-12-19 12:15:24 -05:00
Kyle Spearrin
0d2bf4f7a1 update libs 2017-12-19 12:13:33 -05:00
Kyle Spearrin
01ffc68fc2 focus vault search on $viewContentLoaded 2017-12-19 11:30:52 -05:00
Kyle Spearrin
16892239fb cross navigation for event subject ids 2017-12-19 11:14:15 -05:00
Kyle Spearrin
d5765d8814 display app/device info on events 2017-12-18 13:56:38 -05:00
Kyle Spearrin
8d6a96074d send device type header 2017-12-18 13:37:06 -05:00
Kyle Spearrin
f54884eb79 event logs for users. ip address. useEvents checks 2017-12-18 13:17:49 -05:00
Kyle Spearrin
828149b2d6 eventService and cipher event logs page 2017-12-18 11:52:42 -05:00
Kyle Spearrin
501c4fc263 serve CSP from proxy 2017-12-16 23:44:35 -05:00
Kyle Spearrin
1d0b45e17d whiteListedDomains only on dev builds 2017-12-16 23:23:17 -05:00
Kyle Spearrin
a0f7ed68fb content-type doesn't need to be text anymore 2017-12-16 23:14:43 -05:00
Kyle Spearrin
7bd0c17188 switch to fork for gh-pages fix 2017-12-16 23:10:40 -05:00
Kyle Spearrin
1ea9d28523 local api/identity uri paths 2017-12-16 22:08:23 -05:00
Kyle Spearrin
8a3fb92bbe paging 2017-12-15 15:02:27 -05:00
Kyle Spearrin
de3a9b9903 date range filtering 2017-12-15 12:42:21 -05:00
Kyle Spearrin
9834f3d2aa use events check 2017-12-14 18:04:18 -05:00
Kyle Spearrin
ac079b9d88 audit logs icon 2017-12-14 15:24:18 -05:00
Kyle Spearrin
9e96906f32 compute counts on every load scenario 2017-12-14 15:20:18 -05:00
Kyle Spearrin
90c079e743 org events page setup 2017-12-14 15:03:46 -05:00
Kyle Spearrin
4ecf307285 properly flag new folder as type folder
resolves #149
2017-12-09 08:28:52 -05:00
Kyle Spearrin
6cf4c453d9 Update README.md 2017-12-05 11:12:34 -05:00
Philipp Hug
d2899d14c7 vaultAddCipherController.js: secureNote Type is int not string (#144) 2017-12-04 07:59:28 -05:00
Kyle Spearrin
f3b438d514 null ref on keeper import 2017-12-03 21:27:49 -05:00
Kyle Spearrin
2997f694f8 import notes for form fills 2017-11-30 23:45:06 -05:00
Kyle Spearrin
b78ab4db27 import form fill csv for lastpass 2017-11-30 23:40:05 -05:00
Kyle Spearrin
37dddea515 simplify collapse/expand logic 2017-11-30 22:47:16 -05:00
Kyle Spearrin
e307d1e87d init storage 2017-11-29 22:47:21 -05:00
Kyle Spearrin
62e1dbb642 expand/collapse all boxes 2017-11-29 22:43:58 -05:00
Kyle Spearrin
b8a425f530 version bump 2017-11-29 22:12:46 -05:00
Kyle Spearrin
cafb6fa694 not always CSV data 2017-11-28 10:07:21 -05:00
Kyle Spearrin
0482ddea2c store large items in notes for import 2017-11-28 10:02:41 -05:00
Kyle Spearrin
b411176c8d better error message handling 2017-11-28 09:27:44 -05:00
Kyle Spearrin
2f13449cb6 fix null ref 2017-11-22 12:29:30 -05:00
Kyle Spearrin
b0c1b7b683 default password generated is 14 length 2017-11-22 12:28:06 -05:00
Kyle Spearrin
7e8978c7fc single collection icon is a cube 2017-11-22 12:24:21 -05:00
Kyle Spearrin
d58b422bd0 no items in folder/collection 2017-11-22 12:21:55 -05:00
Kyle Spearrin
3563601382 no collections message 2017-11-22 12:17:40 -05:00
Kyle Spearrin
d42e6ca3fd show collection and folder groupings together 2017-11-22 12:08:31 -05:00
464 changed files with 78455 additions and 24845 deletions

View File

@@ -1,3 +1,3 @@
*
!dist/*
!build/*
!entrypoint.sh

15
.editorconfig Normal file
View File

@@ -0,0 +1,15 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Set default charset
[*.{js,ts,scss,html}]
charset = utf-8
indent_style = space
indent_size = 4

212
.gitignore vendored
View File

@@ -1,203 +1,13 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
# Visual Studo 2015 cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
*.[Cc]ache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
bower_components/
lib/
.vs
.idea
.DS_Store
node_modules
npm-debug.log
vwd.webinfo
css/
dist/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Other
package-lock.json
src/js/*.min.js
*.pem
*.crx
*.zip
build/
!dev-server.shared.pem

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "jslib"]
path = jslib
url = https://github.com/bitwarden/jslib.git
branch = master

1
CNAME
View File

@@ -1 +0,0 @@
vault.bitwarden.com

13
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,13 @@
Code contributions are welcome! Please commit any pull requests against the `master` branch.
# Localization (l10n)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-web/localized.svg)](https://crowdin.com/project/bitwarden-web)
We use a translation tool called [Crowdin](https://crowdin.com) to help manage our localization efforts across many different languages.
If you are interested in helping translate the Bitwarden web vault into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-web
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/kspearrin).
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/

View File

@@ -1,10 +1,17 @@
FROM bitwarden/server
LABEL com.bitwarden.product="bitwarden"
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gosu \
&& rm -rf /var/lib/apt/lists/*
ENV ASPNETCORE_URLS http://+:5000
WORKDIR /app
COPY ./dist .
EXPOSE 80
EXPOSE 5000
COPY ./build .
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

5
ISSUE_TEMPLATE.md Normal file
View File

@@ -0,0 +1,5 @@
<!--
Please do not submit feature requests. The [Community Forums][1] has a
section for submitting, voting for, and discussing product feature requests.
[1]: https://community.bitwarden.com
-->

View File

@@ -1,31 +1,59 @@
[![appveyor build](https://ci.appveyor.com/api/projects/status/github/bitwarden/web?branch=master&svg=true)](https://ci.appveyor.com/project/bitwarden/web) [![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby)
<p align="center">
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/web-vault-macbook.png" alt="" width="600" height="358" />
</p>
<p align="center">
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>
<a href="https://crowdin.com/project/bitwarden-web" target="_blank">
<img src="https://d322cqt584bo4o.cloudfront.net/bitwarden-web/localized.svg" alt="Crowdin" />
</a>
<a href="https://hub.docker.com/u/bitwarden/" target="_blank">
<img src="https://img.shields.io/docker/pulls/bitwarden/web.svg" alt="DockerHub" />
</a>
<a href="https://gitter.im/bitwarden/Lobby" target="_blank">
<img src="https://badges.gitter.im/bitwarden/Lobby.svg" alt="gitter chat" />
</a>
</p>
# bitwarden Web
## Build/Run
The bitwarden Web project is an AngularJS application that powers the web vault (https://vault.bitwarden.com/).
### Requirements
<img src="https://i.imgur.com/rxrykeX.png" alt="" width="791" height="739" />
- [Node.js](https://nodejs.org) v8.11 or greater
# Build/Run
### Run the app
**Requirements**
```
npm install
npm run build:watch
```
- Node.js
- Gulp
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:
By default the application points to the production API. If you want to change that to point to a local instance of
the [Core](https://github.com/bitwarden/core) API, you can modify the `package.json` `env` property to `Development`
and then set your local endpoints in `settings.json`.
```typescript
await apiService.setUrls({
base: isDev ? null : window.location.origin,
api: isDev ? 'http://mylocalapi' : null,
identity: isDev ? 'http://mylocalidentity' : null,
});
```
Then run the following commands:
If you want to point the development web vault to the production APIs, you can set:
- `npm install`
- `gulp build`
- `gulp serve`
```typescript
await apiService.setUrls({
base: null,
api: 'https://api.bitwarden.com',
identity: 'https://identity.bitwarden.com',
});
```
You can now access the web vault at `http://localhost:4001`.
# Contribute
## Contribute
Code contributions are welcome! Please commit any pull requests against the `master` branch.

View File

@@ -1,4 +1,4 @@
bitwarden believes that working with security researchers across the globe is crucial to keeping our
Bitwarden believes that working with security researchers across the globe is crucial to keeping our
users safe. If you believe you've found a security issue in our product or service, we encourage you to
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
@@ -16,7 +16,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
# In-scope
- Security issues in any current release of bitwarden. This includes the web vault, browser extension,
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
code is available at https://github.com/bitwarden.
@@ -24,14 +24,14 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
The following bug classes are out-of scope:
- Bugs that are already reported on any of bitwarden's issue trackers (https://github.com/bitwarden),
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
or that we already know of. Note that some of our issue tracking is private.
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
upstream maintainer.
- Attacks requiring physical access to a user's device.
- Self-XSS
- Issues related to software or protocols not under bitwarden's control
- Vulnerabilities in outdated versions of bitwarden
- Issues related to software or protocols not under Bitwarden's control
- Vulnerabilities in outdated versions of Bitwarden
- Missing security best practices that do not directly lead to a vulnerability
- Issues that do not have any impact on the general public
@@ -39,7 +39,7 @@ While researching, we'd like to ask you to refrain from:
- Denial of service
- Spamming
- Social engineering (including phishing) of bitwarden staff or contractors
- Any physical attempts against bitwarden property or data centers
- Social engineering (including phishing) of Bitwarden staff or contractors
- Any physical attempts against Bitwarden property or data centers
Thank you for helping keep bitwarden and our users safe!
Thank you for helping keep Bitwarden and our users safe!

View File

@@ -1,39 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.9
MinimumVisualStudioVersion = 10.0.40219.1
Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "bitwarden-web", ".", "{25BEDEF4-2CAF-445A-807D-63C17FF85694}"
ProjectSection(WebsiteProperties) = preProject
TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.6.1"
Debug.AspNetCompiler.VirtualPath = "/localhost_15509"
Debug.AspNetCompiler.PhysicalPath = "."
Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_15509\"
Debug.AspNetCompiler.Updateable = "true"
Debug.AspNetCompiler.ForceOverwrite = "true"
Debug.AspNetCompiler.FixedNames = "false"
Debug.AspNetCompiler.Debug = "True"
Release.AspNetCompiler.VirtualPath = "/localhost_15509"
Release.AspNetCompiler.PhysicalPath = "."
Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_15509\"
Release.AspNetCompiler.Updateable = "true"
Release.AspNetCompiler.ForceOverwrite = "true"
Release.AspNetCompiler.FixedNames = "false"
Release.AspNetCompiler.Debug = "False"
VWDPort = "15509"
SlnRelativePath = "."
DefaultWebSiteLanguage = "Visual C#"
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{25BEDEF4-2CAF-445A-807D-63C17FF85694}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{25BEDEF4-2CAF-445A-807D-63C17FF85694}.Debug|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -1,13 +0,0 @@
$dir = Split-Path -Parent $MyInvocation.MyCommand.Path
echo "`n# Building Web"
echo "`nBuilding app"
echo "npm version $(npm --version)"
echo "gulp version $(gulp --version)"
npm install
gulp dist:selfHosted
echo "`nBuilding docker image"
docker --version
docker build -t bitwarden/web $dir\.

View File

@@ -22,9 +22,9 @@ else
echo ""
echo "Building app"
echo "npm version $(npm --version)"
echo "gulp version $(gulp --version)"
npm install
gulp dist:selfHosted
npm run sub:update
npm run dist:selfhost
echo ""
echo "Building docker image"

10
crowdin.yml Normal file
View File

@@ -0,0 +1,10 @@
files:
- source: /src/locales/en/messages.json
translation: /src/locales/%two_letters_code%/%original_file_name%
update_option: update_as_unapproved
languages_mapping:
two_letters_code:
pt-PT: pt_PT
pt-BR: pt_BR
zh-CN: zh_CN
zh-TW: zh_TW

45
dev-server.shared.pem Normal file
View File

@@ -0,0 +1,45 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDaEy1cPw07irjg
4wUgaxshW7oQrgVoNZYRROmdU20K22L+HyG2ahW6usiWw8+6fPgVve7Y1z+/GYsK
DhaDdY1Ket3JvZHxoAJ/+6lYY+05PhtmYnyEZzZlnuYx/tu3vyGpsXqMpzL3ZrX2
Mh2dWE7ZXKxsyig4wSDJhBPMrW8HKXLrLR/JPFhu/nz5MpRF5LfzWU13FEfmS43s
PEkBCn5ZxhVX4eNclQhTl7oOo5LU+KCWn+C/GPQir7pdmMoYF6D5j3qtbsq9irPe
qR7HsM9z6DnX+L0mF31P2S6OTgLT3kuWJn9vqLUwvQWGFvSSzpw7JgGBK3eX6zE9
2koGWP9NAgMBAAECggEAIpwCie5TykxU1RQSfzegYaXuHLGRmB1RCMKYFOjlmGCD
EHOeZRXnBvCX3x2KfT1SHhk7q9xVeJ20LE9aEVj5qIVhZ6AXZnKPkwI8uRN61afe
r1wYCOdcgbo7LFoXQs0pqYXKPkJW217IqB8CBjO6p9KGZumago9cBb9ZaRVpVohZ
c6YHeatrna2mPb/EUPHdT0RHHQ5Dz2ToPjCkDtxsNHLZLekR35WIMtCBlp0xY5hb
5h54ZxnaMihvHTLa8L/pgxGEUsP+XFpdXkM1oREzh8tDRFcUL8mUVZq8bGyzALn5
MxDhdXqxrnyD2cQ/cSqXLs1/2mh5eccU3g5IaNtrAQKBgQD0Q4K2UYXa8jWQu7jI
b37zwr2EypLFjeluqF4fxs+oz3UYEXeBDK0Td19/tze6/XieKibKDtFrOZQwDDKC
AVxD7Dm58T9Jf4LDHNYOfYL3X/E4H+JrVBh94s0B00jVJ6TnEQDMuLi+wMGtvTdW
huxoNefIWKf73ozvxIF+nsyeDQKBgQDkjYoXkBtfNgQR42RVA0/UdLLDWWctMU4F
sJYc1bLL3txbf+fK7QzbU/ggLMW0hv8/IdyirGJhW3G0K0yhpAOlPhe36qv4QyhD
o2nFlRrzfzvGJAgH9b1s+VcL/cSIuv4aCkbv97DAoQGPzAWEKv5gY1iw1DsGgrz5
svZUvd7WQQKBgQDPrp7yuTngQNP+bT3dXb9JLqjIwRwt0E1LgugUiIuDcnCSuDct
iEOYK4UNKBDAckcd46T7Y8H3MwumFpjTJKj4L1+dk1tF+J6Lmnb99wVlozOLjsCK
lQQF9NJt3OEuKvjwZeqSJfUeavHB8QGeFjXnHP4nwAmEA2M9cYzQxeAf+QKBgCbS
U+6Er+GQT0iqk1RNZ7XyzJqaCQiII3Sb9iOXuPMgO9Xe+ARkF5b5wF/Wuw5bD+gt
XEjVdzCKU9oCsNWUAnqC/Yxj9CoLXj9+9mx1U0qhBgo1/Jc9ipuEDuEejc+b06Wg
sUP5krBlqNpAEX/Nvb+poFsI8a29b1QKrgTe64cBAoGALg92rZBG60N2n8fTokou
f1fui8Ftb+vOVGv9CM6icmNuwXeMF40A33Hvx14XLFk6B5p5dtVyOR660rRv4HRV
cBUm5wwCZjwR5Aj83XGR0PRbTNFNngHbawQiutSo6dw8cNNKCZMywVh2KX29dsLh
0Yj++kb8+G1kzFonR8WWoC8=
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIICwzCCAaugAwIBAgIJAN5sbMfEx05qMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
BAMTCWxvY2FsaG9zdDAeFw0xODA2MDUwMzMxNDhaFw0yODA2MDIwMzMxNDhaMBQx
EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBANoTLVw/DTuKuODjBSBrGyFbuhCuBWg1lhFE6Z1TbQrbYv4fIbZqFbq6yJbD
z7p8+BW97tjXP78ZiwoOFoN1jUp63cm9kfGgAn/7qVhj7Tk+G2ZifIRnNmWe5jH+
27e/IamxeoynMvdmtfYyHZ1YTtlcrGzKKDjBIMmEE8ytbwcpcustH8k8WG7+fPky
lEXkt/NZTXcUR+ZLjew8SQEKflnGFVfh41yVCFOXug6jktT4oJaf4L8Y9CKvul2Y
yhgXoPmPeq1uyr2Ks96pHsewz3PoOdf4vSYXfU/ZLo5OAtPeS5Ymf2+otTC9BYYW
9JLOnDsmAYErd5frMT3aSgZY/00CAwEAAaMYMBYwFAYDVR0RBA0wC4IJbG9jYWxo
b3N0MA0GCSqGSIb3DQEBCwUAA4IBAQCBTn7szrcs+fSs1Q/a2O3ng35zcme6NRhp
T65RP0ooj3tPT9QlTJyKjo9Yb2RW2RGVbQO86mkYe9N9wcZkzurZ6KDqsfBn3FkI
eZA1G/za907Dt/25mOdrsav7NmFBwxo9iuZ/cozgneK1mAXOu4nDI5yYvAlvNA6E
iXgls4WX1LtHL5b9YV7Jz27d5tTmGxEimakMBo+zr10vCtMCsTlDs/ChamnI7ljN
7B4WIVUMI3xOZzqClLnSzFJNReAlapjtGtp1qH6Y+6aZ9OErIwZOjE9CYYvm6MbE
CblXQ9Uifpwrc09TA5S2Y/9+VxUBF9xlxh8hkcGLTzlNFDzVWdmR
-----END CERTIFICATE-----

1
dist/.publish vendored

Submodule dist/.publish deleted from 697d06985c

View File

@@ -1,5 +1,38 @@
#!/bin/sh
#!/bin/bash
cp /etc/bitwarden/web/settings.js /app/js/settings.js
# Setup
GROUPNAME="bitwarden"
USERNAME="bitwarden"
LUID=${LOCAL_UID:-0}
LGID=${LOCAL_GID:-0}
# Step down from host root to well-known nobody/nogroup user
if [ $LUID -eq 0 ]
then
LUID=65534
fi
if [ $LGID -eq 0 ]
then
LGID=65534
fi
# Create user and group
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
mkhomedir_helper $USERNAME
# The rest...
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
cp /etc/bitwarden/web/app-id.json /app/app-id.json
dotnet /bitwarden_server/Server.dll /contentRoot=/app /webRoot=. /serveUnknown=false
chown -R $USERNAME:$GROUPNAME /app
chown -R $USERNAME:$GROUPNAME /bitwarden_server
exec gosu $USERNAME:$GROUPNAME dotnet /bitwarden_server/Server.dll \
/contentRoot=/app /webRoot=. /serveUnknown=false /webVault=true

View File

@@ -1,486 +1,35 @@
/// <binding BeforeBuild='build, dist' Clean='clean' ProjectOpened='build. dist' />
const gulp = require('gulp');
const googleWebFonts = require('gulp-google-webfonts');
const del = require('del');
const package = require('./package.json');
const fs = require('fs');
var gulp = require('gulp'),
rimraf = require('rimraf'),
concat = require('gulp-concat'),
rename = require('gulp-rename'),
cssmin = require('gulp-cssmin'),
uglify = require('gulp-uglify'),
ghPages = require('gulp-gh-pages'),
less = require('gulp-less'),
connect = require('gulp-connect'),
ngAnnotate = require('gulp-ng-annotate'),
preprocess = require('gulp-preprocess'),
runSequence = require('run-sequence'),
merge = require('merge-stream'),
ngConfig = require('gulp-ng-config'),
settings = require('./settings.json'),
project = require('./package.json'),
jshint = require('gulp-jshint'),
_ = require('lodash'),
webpack = require('webpack-stream'),
browserify = require('browserify'),
derequire = require('gulp-derequire'),
source = require('vinyl-source-stream');
const paths = {
src: './src/',
build: './build/',
cssDir: './src/css/',
};
var paths = {};
paths.dist = './dist/';
paths.webroot = './src/';
paths.js = paths.webroot + 'js/**/*.js';
paths.minJs = paths.webroot + 'js/**/*.min.js';
paths.concatJsDest = paths.webroot + 'js/bw.min.js';
paths.libDir = paths.webroot + 'lib/';
paths.npmDir = 'node_modules/';
paths.lessDir = paths.webroot + 'less/';
paths.cssDir = paths.webroot + 'css/';
paths.jsDir = paths.webroot + 'js/';
var randomString = Math.random().toString(36).substring(7);
gulp.task('lint', function () {
return gulp.src(paths.webroot + 'app/**/*.js')
.pipe(jshint())
.pipe(jshint.reporter('default'));
});
gulp.task('build', function (cb) {
return runSequence(
'clean',
['browserify', 'lib', 'webpack', 'less', 'settings', 'lint', 'min:js'],
cb);
});
gulp.task('clean:js', function (cb) {
return rimraf(paths.concatJsDest, cb);
});
gulp.task('clean:css', function (cb) {
return rimraf(paths.cssDir, cb);
});
gulp.task('clean:lib', function (cb) {
return rimraf(paths.libDir, cb);
});
gulp.task('clean', ['clean:js', 'clean:css', 'clean:lib', 'dist:clean']);
gulp.task('min:js', ['clean:js'], function () {
return gulp.src(
[
paths.js,
'!' + paths.minJs,
'!' + paths.jsDir + 'fallback*.js',
'!' + paths.jsDir + 'u2f-connector.js',
'!' + paths.jsDir + 'duo.js',
'!' + paths.jsDir + 'settings.js'
], { base: '.' })
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
.pipe(concat(paths.concatJsDest))
.pipe(uglify())
.pipe(gulp.dest('.'));
});
gulp.task('min:css', [], function () {
return gulp.src([paths.cssDir + '**/*.css', '!' + paths.cssDir + '**/*.min.css'], { base: '.' })
.pipe(cssmin())
.pipe(rename({ suffix: '.min' }))
.pipe(gulp.dest('.'));
});
gulp.task('min', ['min:js', 'min:css']);
gulp.task('lib', ['clean:lib'], function () {
var libs = [
{
src: [
paths.npmDir + 'bootstrap/dist/**/*',
'!' + paths.npmDir + 'bootstrap/dist/**/npm.js',
'!' + paths.npmDir + 'bootstrap/dist/**/css/*theme*'
],
dest: paths.libDir + 'bootstrap'
},
{
src: paths.npmDir + 'font-awesome/css/*',
dest: paths.libDir + 'font-awesome/css'
},
{
src: paths.npmDir + 'font-awesome/fonts/*',
dest: paths.libDir + 'font-awesome/fonts'
},
{
src: paths.npmDir + 'jquery/dist/*.js',
dest: paths.libDir + 'jquery'
},
{
src: paths.npmDir + 'admin-lte/dist/js/app*.js',
dest: paths.libDir + 'admin-lte/js'
},
{
src: paths.npmDir + 'angular/angular*.js',
dest: paths.libDir + 'angular'
},
{
src: paths.npmDir + 'angular-ui-bootstrap/dist/*tpls*.js',
dest: paths.libDir + 'angular-ui-bootstrap'
},
{
src: paths.npmDir + 'angular-bootstrap-show-errors/src/*.js',
dest: paths.libDir + 'angular-bootstrap-show-errors'
},
{
src: paths.npmDir + 'angular-cookies/*cookies*.js',
dest: paths.libDir + 'angular-cookies'
},
{
src: paths.npmDir + 'angular-jwt/dist/*.js',
dest: paths.libDir + 'angular-jwt'
},
{
src: paths.npmDir + 'angular-resource/*resource*.js',
dest: paths.libDir + 'angular-resource'
},
{
src: paths.npmDir + 'angular-sanitize/*sanitize*.js',
dest: paths.libDir + 'angular-sanitize'
},
{
src: [paths.npmDir + 'angular-toastr/dist/**/*.css', paths.npmDir + 'angular-toastr/dist/**/*.js'],
dest: paths.libDir + 'angular-toastr'
},
{
src: paths.npmDir + 'angular-ui-router/release/*.js',
dest: paths.libDir + 'angular-ui-router'
},
{
src: paths.npmDir + 'angular-messages/*messages*.js',
dest: paths.libDir + 'angular-messages'
},
{
src: paths.npmDir + 'ngstorage/*.js',
dest: paths.libDir + 'ngstorage'
},
{
src: paths.npmDir + 'papaparse/papaparse*.js',
dest: paths.libDir + 'papaparse'
},
{
src: paths.npmDir + 'ngclipboard/dist/ngclipboard*.js',
dest: paths.libDir + 'ngclipboard'
},
{
src: paths.npmDir + 'clipboard/dist/clipboard*.js',
dest: paths.libDir + 'clipboard'
},
{
src: paths.npmDir + 'node-forge/dist/prime.worker.*',
dest: paths.libDir + 'forge'
},
{
src: [
paths.npmDir + 'angulartics-google-analytics/lib/angulartics*.js',
paths.npmDir + 'angulartics/src/angulartics.js'
],
dest: paths.libDir + 'angulartics'
},
//{
// src: paths.npmDir + 'duo_web_sdk/index.js',
// dest: paths.libDir + 'duo'
//},
{
src: paths.jsDir + 'duo.js',
dest: paths.libDir + 'duo'
},
{
src: paths.npmDir + 'angular-promise-polyfill/index.js',
dest: paths.libDir + 'angular-promise-polyfill'
}
];
var tasks = libs.map(function (lib) {
return gulp.src(lib.src).pipe(gulp.dest(lib.dest));
});
return merge(tasks);
});
gulp.task('webpack', ['webpack:forge']);
gulp.task('webpack:forge', function () {
var forgeDir = paths.npmDir + '/node-forge/lib/';
return gulp.src([
forgeDir + 'pbkdf2.js',
forgeDir + 'aes.js',
forgeDir + 'rsa.js',
forgeDir + 'hmac.js',
forgeDir + 'sha256.js',
forgeDir + 'random.js',
forgeDir + 'forge.js'
]).pipe(webpack({
output: {
filename: 'forge.js',
library: 'forge',
libraryTarget: 'umd'
},
node: {
Buffer: false,
process: false,
crypto: false,
setImmediate: false
}
})).pipe(gulp.dest(paths.libDir + 'forge'));
});
gulp.task('settings', function () {
return config()
.pipe(gulp.dest(paths.webroot + 'app'));
});
function config() {
return gulp.src('./settings.json')
.pipe(ngConfig('bit', {
createModule: false,
constants: _.merge({}, {
appSettings: {
selfHosted: false,
version: project.version,
environment: project.env
}
}, require('./settings' + (project.env !== 'Development' ? ('.' + project.env) : '') + '.json') || {})
}));
function clean() {
return del([paths.cssDir]);
}
gulp.task('less', function () {
return gulp.src(paths.lessDir + 'vault.less')
.pipe(less())
function webfonts() {
return gulp.src('./webfonts.list')
.pipe(googleWebFonts({
fontsDir: 'webfonts',
cssFilename: 'webfonts.css',
format: 'woff',
}))
.pipe(gulp.dest(paths.cssDir));
});
};
gulp.task('watch', function () {
gulp.watch(paths.lessDir + '*.less', ['less']);
gulp.watch('./settings*.json', ['settings']);
});
function version() {
fs.writeFileSync(paths.build + 'version.json', '{"version":"' + package.version + '"}');
}
gulp.task('browserify', ['browserify:stripe', 'browserify:cc']);
gulp.task('browserify:stripe', function () {
return browserify(paths.npmDir + 'angular-stripe/src/index.js',
{
entry: '.',
standalone: 'angularStripe',
global: true
})
.transform('exposify', { expose: { angular: 'angular' } })
.bundle()
.pipe(source('angular-stripe.js'))
.pipe(derequire())
.pipe(gulp.dest(paths.libDir + 'angular-stripe'));
});
gulp.task('browserify:cc', function () {
return browserify(paths.npmDir + 'angular-credit-cards/src/index.js',
{
entry: '.',
standalone: 'angularCreditCards'
})
.transform('exposify', { expose: { angular: 'angular' } })
.bundle()
.pipe(source('angular-credit-cards.js'))
.pipe(derequire())
.pipe(gulp.dest(paths.libDir + 'angular-credit-cards'));
});
gulp.task('dist:clean', function (cb) {
return rimraf(paths.dist + '**/*', cb);
});
gulp.task('dist:move', function () {
var moves = [
{
src: './CNAME',
dest: paths.dist
},
{
src: [
paths.npmDir + 'bootstrap/dist/**/bootstrap.min.js',
paths.npmDir + 'bootstrap/dist/**/bootstrap.min.css',
paths.npmDir + 'bootstrap/dist/**/fonts/**/*'
],
dest: paths.dist + 'lib/bootstrap'
},
{
src: [
paths.npmDir + 'font-awesome/**/font-awesome.min.css',
paths.npmDir + 'font-awesome/**/fonts/**/*'
],
dest: paths.dist + 'lib/font-awesome'
},
{
src: paths.npmDir + 'jquery/dist/jquery.min.js',
dest: paths.dist + 'lib/jquery'
},
{
src: paths.npmDir + 'angular/angular.min.js',
dest: paths.dist + 'lib/angular'
},
{
src: paths.npmDir + 'node-forge/dist/prime.worker.*',
dest: paths.dist + 'lib/forge'
},
//{
// src: paths.npmDir + 'duo_web_sdk/index.js',
// dest: paths.dist + 'lib/duo'
//},
{
src: paths.jsDir + 'duo.js',
dest: paths.dist + 'js'
},
{
src: paths.jsDir + 'settings.js',
dest: paths.dist + 'js'
},
{
src: paths.jsDir + 'bw.min.js',
dest: paths.dist + 'js'
},
{
src: [
paths.webroot + '**/app/**/*.html',
paths.webroot + '**/images/**/*',
paths.webroot + 'index.html',
paths.webroot + 'u2f-connector.html',
paths.webroot + 'duo-connector.html',
paths.webroot + 'favicon.ico',
paths.webroot + 'app-id.json'
],
dest: paths.dist
}
];
var tasks = moves.map(function (move) {
return gulp.src(move.src).pipe(gulp.dest(move.dest));
});
return merge(tasks);
});
gulp.task('dist:css', function () {
return gulp
.src([
paths.cssDir + '**/*.css',
'!' + paths.cssDir + '**/*.min.css'
])
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
.pipe(cssmin())
.pipe(rename({ suffix: '.min' }))
.pipe(gulp.dest(paths.dist + 'css'));
});
gulp.task('dist:js:app', function () {
var mainStream = gulp
.src([
paths.webroot + 'app/app.js',
'!' + paths.webroot + 'app/settings.js',
paths.webroot + 'app/**/*Module.js',
paths.webroot + 'app/**/*.js'
]);
merge(mainStream, config())
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
.pipe(concat(paths.dist + '/js/app.min.js'))
.pipe(ngAnnotate())
.pipe(uglify())
.pipe(gulp.dest('.'));
});
gulp.task('dist:js:fallback', function () {
var mainStream = gulp
.src([
paths.jsDir + 'fallback*.js'
]);
merge(mainStream)
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
.pipe(uglify())
.pipe(rename({ suffix: '.min' }))
.pipe(gulp.dest(paths.dist + 'js'));
});
gulp.task('dist:js:u2f', function () {
var mainStream = gulp
.src([
paths.jsDir + 'u2f*.js'
]);
merge(mainStream)
.pipe(concat(paths.dist + '/js/u2f.min.js'))
.pipe(uglify())
.pipe(gulp.dest('.'));
});
gulp.task('dist:js:lib', function () {
return gulp
.src([
paths.libDir + 'angulartics/angulartics.js',
paths.libDir + '**/*.js',
'!' + paths.libDir + '**/*.min.js',
'!' + paths.libDir + 'angular/**/*',
'!' + paths.libDir + 'bootstrap/**/*',
'!' + paths.libDir + 'jquery/**/*'
])
.pipe(concat(paths.dist + '/js/lib.min.js'))
.pipe(uglify())
.pipe(gulp.dest('.'));
});
gulp.task('dist:preprocess', function () {
return gulp
.src([
paths.dist + '/**/*.html'
], { base: '.' })
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
.pipe(gulp.dest('.'));
});
gulp.task('dist', ['build'], function (cb) {
return runSequence(
'dist:clean',
['dist:move', 'dist:css', 'dist:js:app', 'dist:js:lib', 'dist:js:fallback', 'dist:js:u2f'],
'dist:preprocess',
cb);
});
var selfHosted = false;
gulp.task('dist:selfHosted', function (cb) {
selfHosted = true;
return runSequence('dist', cb);
});
gulp.task('deploy', ['dist'], function () {
return gulp.src(paths.dist + '**/*')
.pipe(ghPages({ cacheDir: paths.dist + '.publish' }));
});
gulp.task('deploy-preview', ['dist'], function () {
return gulp.src(paths.dist + '**/*')
.pipe(ghPages({
cacheDir: paths.dist + '.publish',
remoteUrl: 'git@github.com:kspearrin/bitwarden-web-preview.git'
}));
});
gulp.task('serve', function () {
connect.server({
port: 4001,
root: ['src'],
//https: true,
middleware: function (connect, opt) {
return [function (req, res, next) {
if (req.originalUrl.indexOf('app-id.json') > -1) {
res.setHeader('Content-Type', 'application/fido.trusted-apps+json');
}
next();
}];
}
});
});
gulp.task('clean', clean);
gulp.task('webfonts', ['clean'], webfonts);
gulp.task('prebuild', ['webfonts']);
gulp.task('version', version);
gulp.task('postdist', ['version']);

1
jslib Submodule

Submodule jslib added at 6f43b73237

12214
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,55 +1,92 @@
{
"name": "bitwarden",
"version": "1.20.1",
"env": "Production",
"name": "bitwarden-web",
"version": "2.2.0",
"scripts": {
"sub:init": "git submodule update --init --recursive",
"sub:update": "git submodule update --remote",
"sub:pull": "git submodule foreach git pull",
"postinstall": "npm run sub:init",
"build": "gulp prebuild && webpack --config webpack.config.js",
"build:watch": "gulp prebuild && webpack-serve --config webpack.config.js",
"build:prod": "gulp prebuild && cross-env NODE_ENV=production webpack --config webpack.config.js",
"build:prod:watch": "gulp prebuild && cross-env NODE_ENV=production webpack-serve --config webpack.config.js",
"build:selfhost": "gulp prebuild && cross-env SELF_HOST=true webpack-serve --config webpack.config.js",
"build:selfhost:watch": "gulp prebuild && cross-env SELF_HOST=true webpack-serve --config webpack.config.js",
"build:selfhost:prod": "gulp prebuild && cross-env SELF_HOST=true NODE_ENV=production webpack --config webpack.config.js",
"build:selfhost:prod:watch": "gulp prebuild && cross-env SELF_HOST=true NODE_ENV=production webpack-serve --config webpack.config.js",
"clean:l10n": "git push origin --delete l10n_master",
"dist": "npm run build:prod && gulp postdist",
"dist:selfhost": "npm run build:selfhost:prod && gulp postdist",
"deploy": "npm run dist && gh-pages -d build",
"deploy:preview": "npm run dist && gh-pages -d build -r git@github.com:kspearrin/web-preview.git",
"lint": "tslint src/**/*.ts || true",
"lint:fix": "tslint src/**/*.ts --fix"
},
"devDependencies": {
"connect": "3.6.3",
"lodash": "4.17.4",
"gulp": "3.9.1",
"gulp-concat": "2.6.1",
"gulp-cssmin": "0.2.0",
"gulp-less": "3.3.2",
"gulp-rename": "1.2.2",
"gulp-uglify": "3.0.0",
"gulp-gh-pages": "0.5.4",
"gulp-preprocess": "2.0.0",
"gulp-ng-annotate": "2.0.0",
"gulp-ng-config": "1.4.0",
"gulp-connect": "5.0.0",
"jshint": "2.9.5",
"gulp-jshint": "2.0.4",
"rimraf": "2.6.1",
"run-sequence": "2.1.0",
"merge-stream": "1.0.1",
"jquery": "2.2.4",
"font-awesome": "4.7.0",
"bootstrap": "3.3.7",
"angular": "1.6.6",
"angular-resource": "1.6.6",
"angular-sanitize": "1.6.6",
"angular-ui-bootstrap": "2.5.0",
"angular-ui-router": "0.4.2",
"angular-jwt": "0.1.9",
"angular-cookies": "1.6.6",
"admin-lte": "2.3.11",
"angular-toastr": "2.1.1",
"angular-bootstrap-show-errors": "2.3.0",
"angular-messages": "1.6.6",
"ngstorage": "0.3.11",
"papaparse": "4.3.5",
"clipboard": "1.7.1",
"ngclipboard": "1.1.1",
"angulartics": "1.4.0",
"angulartics-google-analytics": "0.4.0",
"node-forge": "0.7.1",
"webpack-stream": "4.0.0",
"angular-stripe": "5.0.0",
"angular-credit-cards": "3.1.6",
"browserify": "14.4.0",
"vinyl-source-stream": "1.1.0",
"gulp-derequire": "2.1.0",
"exposify": "0.5.0",
"@angular/compiler-cli": "5.2.0",
"@ngtools/webpack": "1.10.2",
"@types/jquery": "^3.3.2",
"@types/lunr": "^2.1.6",
"@types/node": "8.0.19",
"@types/node-forge": "0.6.10",
"@types/papaparse": "4.1.33",
"@types/webcrypto": "^0.0.28",
"angular2-template-loader": "^0.6.2",
"clean-webpack-plugin": "^0.1.19",
"copy-webpack-plugin": "^4.5.1",
"cross-env": "^5.1.4",
"css-loader": "^0.28.11",
"del": "^3.0.0",
"extract-text-webpack-plugin": "next",
"file-loader": "^1.1.11",
"gh-pages": "^1.2.0",
"gulp": "^3.9.1",
"gulp-google-webfonts": "^2.0.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"node-sass": "^4.9.2",
"sass-loader": "^7.0.2",
"style-loader": "^0.21.0",
"ts-loader": "^4.3.1",
"tslint": "^5.10.0",
"tslint-loader": "^3.6.0",
"typescript": "^2.7.2",
"webpack": "^4.10.2",
"webpack-cli": "^3.0.2",
"webpack-serve": "^1.0.2"
},
"dependencies": {
"@angular/animations": "5.2.0",
"@angular/common": "5.2.0",
"@angular/compiler": "5.2.0",
"@angular/core": "5.2.0",
"@angular/forms": "5.2.0",
"@angular/http": "5.2.0",
"@angular/platform-browser": "5.2.0",
"@angular/platform-browser-dynamic": "5.2.0",
"@angular/router": "5.2.0",
"@angular/upgrade": "5.2.0",
"@aspnet/signalr": "1.0.3",
"@aspnet/signalr-protocol-msgpack": "1.0.3",
"angular2-toaster": "4.0.2",
"angulartics2": "5.0.1",
"bootstrap": "4.1.3",
"core-js": "2.4.1",
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
"angular-promise-polyfill": "0.0.4"
"font-awesome": "4.7.0",
"jquery": "3.3.1",
"lunr": "2.3.1",
"mousetrap": "1.6.1",
"ngx-infinite-scroll": "0.8.4",
"node-forge": "0.7.1",
"papaparse": "4.3.5",
"popper.js": "1.14.4",
"qrious": "4.0.2",
"rxjs": "5.5.6",
"sweetalert": "2.1.0",
"web-animations-js": "2.3.1",
"webcrypto-shim": "0.1.4",
"whatwg-fetch": "^2.0.4",
"zone.js": "0.8.19"
}
}

View File

@@ -1,12 +0,0 @@
{
"appSettings": {
"apiUri": "https://preview-api.bitwarden.com",
"identityUri": "https://preview-identity.bitwarden.com",
"iconsUri": "https://icons.bitwarden.com",
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
"whitelistDomains": [
"preview-api.bitwarden.com"
]
}
}

View File

@@ -1,12 +0,0 @@
{
"appSettings": {
"apiUri": "https://api.bitwarden.com",
"identityUri": "https://identity.bitwarden.com",
"iconsUri": "https://icons.bitwarden.com",
"stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk",
"braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd",
"whitelistDomains": [
"api.bitwarden.com"
]
}
}

View File

@@ -1,12 +0,0 @@
{
"appSettings": {
"apiUri": "http://localhost:4000",
"identityUri": "http://localhost:33656",
"iconsUri": "https://icons.bitwarden.com",
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
"whitelistDomains": [
"localhost"
]
}
}

View File

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

View File

@@ -0,0 +1,86 @@
import {
Component,
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import {
Toast,
ToasterService,
} from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { StateService } from 'jslib/abstractions/state.service';
import { UserService } from 'jslib/abstractions/user.service';
import { OrganizationUserAcceptRequest } from 'jslib/models/request/organizationUserAcceptRequest';
@Component({
selector: 'app-accept-organization',
templateUrl: 'accept-organization.component.html',
})
export class AcceptOrganizationComponent implements OnInit {
loading = true;
authed = false;
orgName: string;
email: string;
actionPromise: Promise<any>;
constructor(private router: Router, private toasterService: ToasterService,
private i18nService: I18nService, private route: ActivatedRoute,
private apiService: ApiService, private userService: UserService,
private stateService: StateService) { }
ngOnInit() {
let fired = false;
this.route.queryParams.subscribe(async (qParams) => {
if (fired) {
return;
}
fired = true;
await this.stateService.remove('orgInvitation');
let error = qParams.organizationId == null || qParams.organizationUserId == null || qParams.token == null;
if (!error) {
this.authed = await this.userService.isAuthenticated();
if (this.authed) {
const request = new OrganizationUserAcceptRequest();
request.token = qParams.token;
try {
this.actionPromise = this.apiService.postOrganizationUserAccept(qParams.organizationId,
qParams.organizationUserId, request);
await this.actionPromise;
const toast: Toast = {
type: 'success',
title: this.i18nService.t('inviteAccepted'),
body: this.i18nService.t('inviteAcceptedDesc'),
timeout: 10000,
};
this.toasterService.popAsync(toast);
this.router.navigate(['/vault']);
} catch {
error = true;
}
} else {
await this.stateService.save('orgInvitation', qParams);
this.email = qParams.email;
this.orgName = qParams.organizationName;
if (this.orgName != null) {
// Fix URL encoding of space issue with Angular
this.orgName = this.orgName.replace(/\+/g, ' ');
}
}
}
if (error) {
this.toasterService.popAsync('error', null, this.i18nService.t('inviteAcceptFailed'));
this.router.navigate(['/']);
}
this.loading = false;
});
}
}

View File

@@ -1,278 +0,0 @@
angular
.module('bit.accounts')
.controller('accountsLoginController', function ($scope, $rootScope, $cookies, apiService, cryptoService, authService,
$state, constants, $analytics, $uibModal, $timeout, $window, $filter, toastr) {
$scope.state = $state;
$scope.twoFactorProviderConstants = constants.twoFactorProvider;
$scope.rememberTwoFactor = { checked: false };
var stopU2fCheck = true;
$scope.returnState = $state.params.returnState;
$scope.stateEmail = $state.params.email;
if (!$scope.returnState && $state.params.org) {
$scope.returnState = {
name: 'backend.user.settingsCreateOrg',
params: { plan: $state.params.org }
};
}
else if (!$scope.returnState && $state.params.premium) {
$scope.returnState = {
name: 'backend.user.settingsPremium'
};
}
if ($state.current.name.indexOf('twoFactor') > -1 && (!$scope.twoFactorProviders || !$scope.twoFactorProviders.length)) {
$state.go('frontend.login.info', { returnState: $scope.returnState });
}
var rememberedEmail = $cookies.get(constants.rememberedEmailCookieName);
if (rememberedEmail || $scope.stateEmail) {
$scope.model = {
email: $scope.stateEmail || rememberedEmail,
rememberEmail: rememberedEmail !== null
};
$timeout(function () {
$("#masterPassword").focus();
});
}
else {
$timeout(function () {
$("#email").focus();
});
}
var _email,
_masterPassword;
$scope.twoFactorProviders = null;
$scope.twoFactorProvider = null;
$scope.login = function (model) {
$scope.loginPromise = authService.logIn(model.email, model.masterPassword).then(function (twoFactorProviders) {
if (model.rememberEmail) {
var cookieExpiration = new Date();
cookieExpiration.setFullYear(cookieExpiration.getFullYear() + 10);
$cookies.put(
constants.rememberedEmailCookieName,
model.email,
{ expires: cookieExpiration });
}
else {
$cookies.remove(constants.rememberedEmailCookieName);
}
if (twoFactorProviders && Object.keys(twoFactorProviders).length > 0) {
_email = model.email;
_masterPassword = model.masterPassword;
$scope.twoFactorProviders = cleanProviders(twoFactorProviders);
$scope.twoFactorProvider = getDefaultProvider($scope.twoFactorProviders);
$analytics.eventTrack('Logged In To Two-step');
$state.go('frontend.login.twoFactor', { returnState: $scope.returnState }).then(function () {
$timeout(function () {
$("#code").focus();
init();
});
});
}
else {
$analytics.eventTrack('Logged In');
loggedInGo();
}
model.masterPassword = '';
});
};
function getDefaultProvider(twoFactorProviders) {
var keys = Object.keys(twoFactorProviders);
var providerType = null;
var providerPriority = -1;
for (var i = 0; i < keys.length; i++) {
var provider = $filter('filter')(constants.twoFactorProviderInfo, { type: keys[i], active: true });
if (provider.length && provider[0].priority > providerPriority) {
if (provider[0].type === constants.twoFactorProvider.u2f && !u2f.isSupported) {
continue;
}
providerType = provider[0].type;
providerPriority = provider[0].priority;
}
}
if (providerType === null) {
return null;
}
return parseInt(providerType);
}
function cleanProviders(twoFactorProviders) {
if (canUseSecurityKey()) {
return twoFactorProviders;
}
var keys = Object.keys(twoFactorProviders);
for (var i = 0; i < keys.length; i++) {
var provider = $filter('filter')(constants.twoFactorProviderInfo, {
type: keys[i],
active: true,
requiresUsb: false
});
if (!provider.length) {
delete twoFactorProviders[keys[i]];
}
}
return twoFactorProviders;
}
// ref: https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser
function canUseSecurityKey() {
var mobile = false;
(function (a) {
if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) {
mobile = true;
}
})(navigator.userAgent || navigator.vendor || window.opera);
return !mobile && !navigator.userAgent.match(/iPad/i);
}
$scope.twoFactor = function (token) {
if ($scope.twoFactorProvider === constants.twoFactorProvider.email ||
$scope.twoFactorProvider === constants.twoFactorProvider.authenticator) {
token = token.replace(' ', '');
}
$scope.twoFactorPromise = authService.logIn(_email, _masterPassword, token, $scope.twoFactorProvider,
$scope.rememberTwoFactor.checked || false);
$scope.twoFactorPromise.then(function () {
$analytics.eventTrack('Logged In From Two-step');
loggedInGo();
}, function () {
if ($scope.twoFactorProvider === constants.twoFactorProvider.u2f) {
init();
}
});
};
$scope.anotherMethod = function () {
var modal = $uibModal.open({
animation: true,
templateUrl: 'app/accounts/views/accountsTwoFactorMethods.html',
controller: 'accountsTwoFactorMethodsController',
resolve: {
providers: function () { return $scope.twoFactorProviders; }
}
});
modal.result.then(function (provider) {
$scope.twoFactorProvider = provider;
$timeout(function () {
$("#code").focus();
init();
});
});
};
$scope.sendEmail = function (doToast) {
if ($scope.twoFactorProvider !== constants.twoFactorProvider.email) {
return;
}
return cryptoService.makeKeyAndHash(_email, _masterPassword).then(function (result) {
return apiService.twoFactor.sendEmailLogin({
email: _email,
masterPasswordHash: result.hash
}).$promise;
}).then(function () {
if (doToast) {
toastr.success('Verification email sent to ' + $scope.twoFactorEmail + '.');
}
}, function () {
toastr.error('Could not send verification email.');
});
};
$scope.$on('$destroy', function () {
stopU2fCheck = true;
});
function loggedInGo() {
if ($scope.returnState) {
$state.go($scope.returnState.name, $scope.returnState.params);
}
else {
$state.go('backend.user.vault');
}
}
function init() {
stopU2fCheck = true;
var params;
if ($scope.twoFactorProvider === constants.twoFactorProvider.duo) {
params = $scope.twoFactorProviders[constants.twoFactorProvider.duo];
$window.Duo.init({
host: params.Host,
sig_request: params.Signature,
submit_callback: function (theForm) {
var response = $(theForm).find('input[name="sig_response"]').val();
$scope.twoFactor(response);
}
});
}
else if ($scope.twoFactorProvider === constants.twoFactorProvider.u2f) {
stopU2fCheck = false;
params = $scope.twoFactorProviders[constants.twoFactorProvider.u2f];
var challenges = JSON.parse(params.Challenges);
initU2f(challenges);
}
else if ($scope.twoFactorProvider === constants.twoFactorProvider.email) {
params = $scope.twoFactorProviders[constants.twoFactorProvider.email];
$scope.twoFactorEmail = params.Email;
if (Object.keys($scope.twoFactorProviders).length > 1) {
$scope.sendEmail(false);
}
}
}
function initU2f(challenges) {
if (stopU2fCheck) {
return;
}
if (challenges.length < 1 || $scope.twoFactorProvider !== constants.twoFactorProvider.u2f) {
return;
}
console.log('listening for u2f key...');
$window.u2f.sign(challenges[0].appId, challenges[0].challenge, [{
version: challenges[0].version,
keyHandle: challenges[0].keyHandle
}], function (data) {
if ($scope.twoFactorProvider !== constants.twoFactorProvider.u2f) {
return;
}
if (data.errorCode) {
console.log(data.errorCode);
$timeout(function () {
initU2f(challenges);
}, data.errorCode === 5 ? 0 : 1000);
return;
}
$scope.twoFactor(JSON.stringify(data));
}, 10);
}
});

View File

@@ -1,8 +0,0 @@
angular
.module('bit.accounts')
.controller('accountsLogoutController', function ($scope, authService, $state, $analytics) {
authService.logOut();
$analytics.eventTrack('Logged Out');
$state.go('frontend.login.info');
});

View File

@@ -1,2 +0,0 @@
angular
.module('bit.accounts', ['ui.bootstrap', 'ngCookies']);

View File

@@ -1,45 +0,0 @@
angular
.module('bit.accounts')
.controller('accountsOrganizationAcceptController', function ($scope, $state, apiService, authService, toastr, $analytics) {
$scope.state = {
name: $state.current.name,
params: $state.params
};
if (!$state.params.organizationId || !$state.params.organizationUserId || !$state.params.token ||
!$state.params.email || !$state.params.organizationName) {
$state.go('frontend.login.info').then(function () {
toastr.error('Invalid parameters.');
});
return;
}
$scope.$on('$viewContentLoaded', function () {
if (authService.isAuthenticated()) {
$scope.accepting = true;
apiService.organizationUsers.accept(
{
orgId: $state.params.organizationId,
id: $state.params.organizationUserId
},
{
token: $state.params.token
}, function () {
$analytics.eventTrack('Accepted Invitation');
$state.go('backend.user.vault', null, { location: 'replace' }).then(function () {
toastr.success('You can access this organization once an administrator confirms your membership.' +
' We\'ll send an email when that happens.', 'Invite Accepted', { timeOut: 10000 });
});
}, function () {
$analytics.eventTrack('Failed To Accept Invitation');
$state.go('backend.user.vault', null, { location: 'replace' }).then(function () {
toastr.error('Unable to accept invitation.', 'Error');
});
});
}
else {
$scope.loading = false;
}
});
});

View File

@@ -1,13 +0,0 @@
angular
.module('bit.accounts')
.controller('accountsPasswordHintController', function ($scope, $rootScope, apiService, $analytics) {
$scope.success = false;
$scope.submit = function (model) {
$scope.submitPromise = apiService.accounts.postPasswordHint({ email: model.email }, function () {
$analytics.eventTrack('Requested Password Hint');
$scope.success = true;
}).$promise;
};
});

View File

@@ -1,21 +0,0 @@
angular
.module('bit.accounts')
.controller('accountsRecoverController', function ($scope, apiService, cryptoService, $analytics) {
$scope.success = false;
$scope.submit = function (model) {
var email = model.email.toLowerCase();
$scope.submitPromise = cryptoService.makeKeyAndHash(model.email, model.masterPassword).then(function (result) {
return apiService.twoFactor.recover({
email: email,
masterPasswordHash: result.hash,
recoveryCode: model.code.replace(/\s/g, '').toLowerCase()
}).$promise;
}).then(function () {
$analytics.eventTrack('Recovered 2FA');
$scope.success = true;
});
};
});

View File

@@ -1,13 +0,0 @@
angular
.module('bit.accounts')
.controller('accountsRecoverDeleteController', function ($scope, $rootScope, apiService, $analytics) {
$scope.success = false;
$scope.submit = function (model) {
$scope.submitPromise = apiService.accounts.postDeleteRecover({ email: model.email }, function () {
$analytics.eventTrack('Started Delete Recovery');
$scope.success = true;
}).$promise;
};
});

View File

@@ -1,92 +0,0 @@
angular
.module('bit.accounts')
.controller('accountsRegisterController', function ($scope, $location, apiService, cryptoService, validationService,
$analytics, $state, $timeout) {
var params = $location.search();
var stateParams = $state.params;
$scope.createOrg = stateParams.org;
if (!stateParams.returnState && stateParams.org) {
$scope.returnState = {
name: 'backend.user.settingsCreateOrg',
params: { plan: $state.params.org }
};
}
else if (!stateParams.returnState && stateParams.premium) {
$scope.returnState = {
name: 'backend.user.settingsPremium',
params: { plan: $state.params.org }
};
}
else {
$scope.returnState = stateParams.returnState;
}
$scope.success = false;
$scope.model = {
email: params.email ? params.email : stateParams.email
};
$scope.readOnlyEmail = stateParams.email !== null;
$timeout(function () {
if ($scope.model.email) {
$("#name").focus();
}
else {
$("#email").focus();
}
});
$scope.registerPromise = null;
$scope.register = function (form) {
var error = false;
if ($scope.model.masterPassword.length < 8) {
validationService.addError(form, 'MasterPassword', 'Master password must be at least 8 characters long.', true);
error = true;
}
if ($scope.model.masterPassword !== $scope.model.confirmMasterPassword) {
validationService.addError(form, 'ConfirmMasterPassword', 'Master password confirmation does not match.', true);
error = true;
}
if (error) {
return;
}
var email = $scope.model.email.toLowerCase();
var makeResult, encKey;
$scope.registerPromise = cryptoService.makeKeyAndHash(email, $scope.model.masterPassword).then(function (result) {
makeResult = result;
encKey = cryptoService.makeEncKey(result.key);
return cryptoService.makeKeyPair(encKey.encKey);
}).then(function (result) {
var request = {
name: $scope.model.name,
email: email,
masterPasswordHash: makeResult.hash,
masterPasswordHint: $scope.model.masterPasswordHint,
key: encKey.encKeyEnc,
keys: {
publicKey: result.publicKey,
encryptedPrivateKey: result.privateKeyEnc
}
};
return apiService.accounts.register(request).$promise;
}, function (errors) {
validationService.addError(form, null, 'Problem generating keys.', true);
return false;
}).then(function (result) {
if (result === false) {
return;
}
$scope.success = true;
$analytics.eventTrack('Registered');
});
};
});

View File

@@ -1,40 +0,0 @@
angular
.module('bit.accounts')
.controller('accountsTwoFactorMethodsController', function ($scope, $uibModalInstance, $analytics, providers, constants) {
$analytics.eventTrack('accountsTwoFactorMethodsController', { category: 'Modal' });
$scope.providers = [];
if (providers.hasOwnProperty(constants.twoFactorProvider.authenticator)) {
add(constants.twoFactorProvider.authenticator);
}
if (providers.hasOwnProperty(constants.twoFactorProvider.yubikey)) {
add(constants.twoFactorProvider.yubikey);
}
if (providers.hasOwnProperty(constants.twoFactorProvider.email)) {
add(constants.twoFactorProvider.email);
}
if (providers.hasOwnProperty(constants.twoFactorProvider.duo)) {
add(constants.twoFactorProvider.duo);
}
if (providers.hasOwnProperty(constants.twoFactorProvider.u2f) && u2f.isSupported) {
add(constants.twoFactorProvider.u2f);
}
$scope.choose = function (provider) {
$uibModalInstance.close(provider.type);
};
$scope.close = function () {
$uibModalInstance.dismiss('close');
};
function add(type) {
for (var i = 0; i < constants.twoFactorProviderInfo.length; i++) {
if (constants.twoFactorProviderInfo[i].type === type) {
$scope.providers.push(constants.twoFactorProviderInfo[i]);
}
}
}
});

View File

@@ -1,28 +0,0 @@
angular
.module('bit.accounts')
.controller('accountsVerifyEmailController', function ($scope, $state, apiService, toastr, $analytics) {
if (!$state.params.userId || !$state.params.token) {
$state.go('frontend.login.info').then(function () {
toastr.error('Invalid parameters.');
});
return;
}
$scope.$on('$viewContentLoaded', function () {
apiService.accounts.verifyEmailToken({},
{
token: $state.params.token,
userId: $state.params.userId
}, function () {
$analytics.eventTrack('Verified Email');
$state.go('frontend.login.info', null, { location: 'replace' }).then(function () {
toastr.success('Your email has been verified. Thank you.', 'Success');
});
}, function () {
$state.go('frontend.login.info', null, { location: 'replace' }).then(function () {
toastr.error('Unable to verify email.', 'Error');
});
});
});
});

View File

@@ -1,36 +0,0 @@
angular
.module('bit.accounts')
.controller('accountsVerifyRecoverDeleteController', function ($scope, $state, apiService, toastr, $analytics) {
if (!$state.params.userId || !$state.params.token || !$state.params.email) {
$state.go('frontend.login.info').then(function () {
toastr.error('Invalid parameters.');
});
return;
}
$scope.email = $state.params.email;
$scope.delete = function () {
if (!confirm('Are you sure you want to delete this account? This cannot be undone.')) {
return;
}
$scope.deleting = true;
apiService.accounts.postDeleteRecoverToken({},
{
token: $state.params.token,
userId: $state.params.userId
}, function () {
$analytics.eventTrack('Recovered Delete');
$state.go('frontend.login.info', null, { location: 'replace' }).then(function () {
toastr.success('Your account has been deleted. You can register a new account again if you like.',
'Success');
});
}, function () {
$state.go('frontend.login.info', null, { location: 'replace' }).then(function () {
toastr.error('Unable to delete account.', 'Error');
});
});
};
});

View File

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

View File

@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { HintComponent as BaseHintComponent } from 'jslib/angular/components/hint.component';
@Component({
selector: 'app-hint',
templateUrl: 'hint.component.html',
})
export class HintComponent extends BaseHintComponent {
constructor(router: Router, analytics: Angulartics2,
toasterService: ToasterService, i18nService: I18nService,
apiService: ApiService) {
super(router, analytics, toasterService, i18nService, apiService);
}
}

View File

@@ -0,0 +1,34 @@
<form (ngSubmit)="submit()" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="text-center mb-4">
<i class="fa fa-lock fa-4x text-muted"></i>
</p>
<p class="lead text-center mx-4 mb-4">{{'yourVaultIsLocked' | i18n}}</p>
<div class="card d-block">
<div class="card-body">
<div class="form-group">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<div class="d-flex">
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword" class="text-monospace form-control"
[(ngModel)]="masterPassword" required appAutofocus appInputVerbatim>
<button type="button" class="ml-1 btn btn-link" title="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
<hr>
<div class="d-flex">
<button type="submit" class="btn btn-primary btn-block">
<i class="fa fa-unlock-alt"></i>
{{'unlock' | i18n}}
</button>
<button type="button" class="btn btn-outline-secondary btn-block ml-2 mt-0" (click)="logOut()">
{{'logOut' | i18n}}
</button>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -0,0 +1,47 @@
import {
Component,
OnInit,
} from '@angular/core';
import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { UserService } from 'jslib/abstractions/user.service';
import { RouterService } from '../services/router.service';
import { LockComponent as BaseLockComponent } from 'jslib/angular/components/lock.component';
@Component({
selector: 'app-lock',
templateUrl: 'lock.component.html',
})
export class LockComponent extends BaseLockComponent implements OnInit {
constructor(router: Router, analytics: Angulartics2,
toasterService: ToasterService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
userService: UserService, cryptoService: CryptoService,
private routerService: RouterService) {
super(router, analytics, toasterService, i18nService, platformUtilsService,
messagingService, userService, cryptoService);
}
async ngOnInit() {
const authed = await this.userService.isAuthenticated();
if (!authed) {
this.router.navigate(['/']);
} else if (await this.cryptoService.hasKey()) {
this.router.navigate(['vault']);
}
const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl !== '/' && previousUrl.indexOf('lock') === -1) {
this.successRoute = previousUrl;
}
}
}

View File

@@ -0,0 +1,45 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<img src="../../images/logo-dark@2x.png" class="logo mb-2" alt="Bitwarden">
<p class="lead text-center mx-4 mb-4">{{'loginOrCreateNewAccount' | i18n}}</p>
<div class="card d-block">
<div class="card-body">
<div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required inputmode="email" appInputVerbatim="false">
</div>
<div class="form-group">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<div class="d-flex">
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword" class="text-monospace form-control"
[(ngModel)]="masterPassword" required appInputVerbatim>
<button type="button" class="ml-1 btn btn-link" title="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
<small class="form-text">
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
</small>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="rememberEmail" name="RememberEmail" [(ngModel)]="rememberEmail">
<label class="form-check-label" for="rememberEmail">{{'rememberEmail' | i18n}}</label>
</div>
<hr>
<div class="d-flex">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
<span>
<i class="fa fa-sign-in"></i> {{'logIn' | i18n}}
</span>
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
</button>
<a routerLink="/register" [queryParams]="{email: email}" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<i class="fa fa-pencil-square-o"></i> {{'createAccount' | i18n}}
</a>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -0,0 +1,59 @@
import { Component } from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { AuthService } from 'jslib/abstractions/auth.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { LoginComponent as BaseLoginComponent } from 'jslib/angular/components/login.component';
@Component({
selector: 'app-login',
templateUrl: 'login.component.html',
})
export class LoginComponent extends BaseLoginComponent {
constructor(authService: AuthService, router: Router,
analytics: Angulartics2, toasterService: ToasterService,
i18nService: I18nService, private route: ActivatedRoute,
storageService: StorageService, private stateService: StateService) {
super(authService, router, analytics, toasterService, i18nService, storageService);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
async ngOnInit() {
this.route.queryParams.subscribe(async (qParams) => {
if (qParams.email != null && qParams.email.indexOf('@') > -1) {
this.email = qParams.email;
}
if (qParams.premium != null) {
this.stateService.save('loginRedirect', { route: '/settings/premium' });
} else if (qParams.org != null) {
this.stateService.save('loginRedirect',
{ route: '/settings/create-organization', qParams: { plan: qParams.org } });
}
await super.ngOnInit();
});
}
async goAfterLogIn() {
const invite = await this.stateService.get<any>('orgInvitation');
if (invite != null) {
this.router.navigate(['accept-organization'], { queryParams: invite });
} else {
const loginRedirect = await this.stateService.get<any>('loginRedirect');
if (loginRedirect != null) {
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
await this.stateService.remove('loginRedirect');
} else {
this.router.navigate([this.successRoute]);
}
}
}
}

View File

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

View File

@@ -0,0 +1,36 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { DeleteRecoverRequest } from 'jslib/models/request/deleteRecoverRequest';
@Component({
selector: 'app-recover-delete',
templateUrl: 'recover-delete.component.html',
})
export class RecoverDeleteComponent {
email: string;
formPromise: Promise<any>;
constructor(private router: Router, private apiService: ApiService,
private analytics: Angulartics2, private toasterService: ToasterService,
private i18nService: I18nService) {
}
async submit() {
try {
const request = new DeleteRecoverRequest();
request.email = this.email.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

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

View File

@@ -0,0 +1,43 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { ApiService } from 'jslib/abstractions/api.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { TwoFactorRecoveryRequest } from 'jslib/models/request/twoFactorRecoveryRequest';
@Component({
selector: 'app-recover-two-factor',
templateUrl: 'recover-two-factor.component.html',
})
export class RecoverTwoFactorComponent {
email: string;
masterPassword: string;
recoveryCode: string;
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) { }
async submit() {
try {
const request = new TwoFactorRecoveryRequest();
request.recoveryCode = this.recoveryCode.replace(/\s/g, '').toLowerCase();
request.email = this.email.toLowerCase();
const key = await this.authService.makePreloginKey(this.masterPassword, request.email);
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

@@ -0,0 +1,66 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{'createAccount' | i18n}}</p>
<div class="card d-block">
<div class="card-body">
<app-callout title="{{'createOrganizationStep1' | i18n}}" type="info" icon="fa-thumb-tack" *ngIf="showCreateOrgMessage">
{{'createOrganizationCreatePersonalAccount' | i18n}}
</app-callout>
<div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required [appAutofocus]="email === ''"
inputmode="email" appInputVerbatim="false">
<small class="form-text text-muted">{{'emailAddressDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="name">{{'yourName' | i18n}}</label>
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name" [appAutofocus]="email !== ''">
<small class="form-text text-muted">{{'yourNameDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<div class="d-flex">
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword" class="text-monospace form-control"
[(ngModel)]="masterPassword" required appInputVerbatim>
<button type="button" class="ml-1 btn btn-link" title="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg" [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
<small class="form-text text-muted">{{'masterPassDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<div class="d-flex">
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}" name="MasterPasswordRetype" class="text-monospace form-control"
[(ngModel)]="confirmMasterPassword" required appInputVerbatim>
<button type="button" class="ml-1 btn btn-link" title="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
<div class="form-group">
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint">
<small class="form-text text-muted">{{'masterPassHintDesc' | i18n}}</small>
</div>
<hr>
<div class="d-flex mb-2">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
<span>{{'submit' | i18n}}</span>
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}}
</a>
</div>
<small class="text-muted" *ngIf="showTerms">
{{'submitAgreePolicies' | i18n}}
<a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{'termsOfService' | i18n}}</a>,
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{'privacyPolicy' | i18n}}</a>
</small>
</div>
</div>
</div>
</div>
</form>

View File

@@ -0,0 +1,50 @@
import { Component } from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { ApiService } from 'jslib/abstractions/api.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { RegisterComponent as BaseRegisterComponent } from 'jslib/angular/components/register.component';
@Component({
selector: 'app-register',
templateUrl: 'register.component.html',
})
export class RegisterComponent extends BaseRegisterComponent {
showCreateOrgMessage = false;
showTerms = true;
constructor(authService: AuthService, router: Router,
analytics: Angulartics2, toasterService: ToasterService,
i18nService: I18nService, cryptoService: CryptoService,
apiService: ApiService, private route: ActivatedRoute,
stateService: StateService, platformUtilsService: PlatformUtilsService) {
super(authService, router, analytics, toasterService, i18nService, cryptoService, apiService, stateService);
this.showTerms = !platformUtilsService.isSelfHost();
}
ngOnInit() {
this.route.queryParams.subscribe((qParams) => {
if (qParams.email != null && qParams.email.indexOf('@') > -1) {
this.email = qParams.email;
}
if (qParams.premium != null) {
this.stateService.save('loginRedirect', { route: '/settings/premium' });
} else if (qParams.org != null) {
this.showCreateOrgMessage = true;
this.stateService.save('loginRedirect',
{ route: '/settings/create-organization', qParams: { plan: qParams.org } });
}
});
}
}

View File

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

View File

@@ -0,0 +1,25 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { AuthService } from 'jslib/abstractions/auth.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import {
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
} from 'jslib/angular/components/two-factor-options.component';
@Component({
selector: 'app-two-factor-options',
templateUrl: 'two-factor-options.component.html',
})
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
constructor(authService: AuthService, router: Router,
analytics: Angulartics2, toasterService: ToasterService,
i18nService: I18nService, platformUtilsService: PlatformUtilsService) {
super(authService, router, analytics, toasterService, i18nService, platformUtilsService, window);
}
}

View File

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

View File

@@ -0,0 +1,75 @@
import {
Component,
ComponentFactoryResolver,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { TwoFactorOptionsComponent } from './two-factor-options.component';
import { ModalComponent } from '../modal.component';
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
import { ApiService } from 'jslib/abstractions/api.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/components/two-factor.component';
@Component({
selector: 'app-two-factor',
templateUrl: 'two-factor.component.html',
})
export class TwoFactorComponent extends BaseTwoFactorComponent {
@ViewChild('twoFactorOptions', { read: ViewContainerRef }) twoFactorOptionsModal: ViewContainerRef;
constructor(authService: AuthService, router: Router,
analytics: Angulartics2, toasterService: ToasterService,
i18nService: I18nService, apiService: ApiService,
platformUtilsService: PlatformUtilsService, private stateService: StateService,
environmentService: EnvironmentService, private componentFactoryResolver: ComponentFactoryResolver) {
super(authService, router, analytics, toasterService, i18nService, apiService,
platformUtilsService, window, environmentService);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
anotherMethod() {
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
const modal = this.twoFactorOptionsModal.createComponent(factory).instance;
const childComponent = modal.show<TwoFactorOptionsComponent>(TwoFactorOptionsComponent,
this.twoFactorOptionsModal);
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close();
this.selectedProviderType = provider;
await this.init();
});
childComponent.onRecoverSelected.subscribe(() => {
modal.close();
});
}
async goAfterLogIn() {
const invite = await this.stateService.get<any>('orgInvitation');
if (invite != null) {
this.router.navigate(['accept-organization'], { queryParams: invite });
} else {
const loginRedirect = await this.stateService.get<any>('loginRedirect');
if (loginRedirect != null) {
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
await this.stateService.remove('loginRedirect');
} else {
this.router.navigate([this.successRoute]);
}
}
}
}

View File

@@ -0,0 +1,8 @@
<div class="mt-5 d-flex justify-content-center">
<div>
<img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
<p class="text-center">
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}"></i>
</p>
</div>
</div>

View File

@@ -0,0 +1,51 @@
import {
Component,
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { UserService } from 'jslib/abstractions/user.service';
import { VerifyEmailRequest } from 'jslib/models/request/verifyEmailRequest';
@Component({
selector: 'app-verify-email-token',
templateUrl: 'verify-email-token.component.html',
})
export class VerifyEmailTokenComponent implements OnInit {
constructor(private router: Router, private toasterService: ToasterService,
private i18nService: I18nService, private route: ActivatedRoute,
private apiService: ApiService, private userService: UserService) { }
ngOnInit() {
let fired = false;
this.route.queryParams.subscribe(async (qParams) => {
if (fired) {
return;
}
fired = true;
if (qParams.userId != null && qParams.token != null) {
try {
await this.apiService.postAccountVerifyEmailToken(
new VerifyEmailRequest(qParams.userId, qParams.token));
const authed = await this.userService.isAuthenticated();
if (authed) {
await this.apiService.refreshIdentityToken();
}
this.toasterService.popAsync('success', null, this.i18nService.t('emailVerified'));
this.router.navigate(['/']);
return;
} catch { }
}
this.toasterService.popAsync('error', null, this.i18nService.t('emailVerifiedFailed'));
this.router.navigate(['/']);
});
}
}

View File

@@ -0,0 +1,26 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{'deleteAccount' | i18n}}</p>
<div class="card">
<div class="card-body">
<app-callout type="warning">{{'deleteAccountWarning' | i18n}}</app-callout>
<p class="text-center">
<strong>{{email}}</strong>
</p>
<p>{{'deleteRecoverConfirmDesc' | i18n}}</p>
<hr>
<div class="d-flex">
<button type="submit" class="btn btn-danger btn-block btn-submit" [disabled]="form.loading">
<span>{{'deleteAccount' | i18n}}</span>
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}}
</a>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -0,0 +1,62 @@
import {
Component,
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { ApiService } from 'jslib/abstractions/api.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { VerifyDeleteRecoverRequest } from 'jslib/models/request/verifyDeleteRecoverRequest';
@Component({
selector: 'app-verify-recover-delete',
templateUrl: 'verify-recover-delete.component.html',
})
export class VerifyRecoverDeleteComponent implements OnInit {
email: string;
formPromise: Promise<any>;
private userId: string;
private token: string;
constructor(private router: Router, private apiService: ApiService,
private analytics: Angulartics2, private toasterService: ToasterService,
private i18nService: I18nService, private route: ActivatedRoute) {
}
ngOnInit() {
let fired = false;
this.route.queryParams.subscribe(async (qParams) => {
if (fired) {
return;
}
fired = true;
if (qParams.userId != null && qParams.token != null && qParams.email != null) {
this.userId = qParams.userId;
this.token = qParams.token;
this.email = qParams.email;
} else {
this.router.navigate(['/']);
}
});
}
async submit() {
try {
const request = new VerifyDeleteRecoverRequest(this.userId, this.token);
this.formPromise = this.apiService.postAccountRecoverDeleteToken(request);
await this.formPromise;
this.analytics.eventTrack.next({ action: 'Recovered Delete' });
this.toasterService.popAsync('success', this.i18nService.t('accountDeleted'),
this.i18nService.t('accountDeletedDesc'));
this.router.navigate(['/']);
} catch { }
}
}

View File

@@ -1,7 +0,0 @@
<div class="login-box">
<div class="login-logo">
<i class="fa fa-shield"></i> <b>bit</b>warden
</div>
<div class="login-box-body" ui-view>
</div>
</div>

View File

@@ -1,49 +0,0 @@
<p class="login-box-msg">Log in to access your vault.</p>
<form name="loginForm" ng-submit="loginForm.$valid && login(model)" api-form="loginPromise">
<div class="callout callout-danger validation-errors" ng-show="loginForm.$errors">
<h4>Errors have occurred</h4>
<ul>
<li ng-repeat="e in loginForm.$errors">{{e}}</li>
</ul>
</div>
<div class="form-group has-feedback" show-errors>
<label for="email" class="sr-only">Email</label>
<input type="email" id="email" name="Email" class="form-control" placeholder="Email" ng-model="model.email"
required api-field />
<span class="fa fa-envelope form-control-feedback"></span>
</div>
<div class="form-group has-feedback" show-errors>
<label for="masterPassword" class="sr-only">Master Password</label>
<input type="password" id="masterPassword" name="MasterPasswordHash" class="form-control" placeholder="Master Password"
ng-model="model.masterPassword"
required api-field />
<span class="fa fa-lock form-control-feedback"></span>
</div>
<div class="row">
<div class="col-xs-7">
<div class="checkbox">
<label>
<input type="checkbox" id="rememberEmail" ng-model="model.rememberEmail" /> Remember Email
</label>
</div>
</div>
<div class="col-xs-5">
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="loginForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="loginForm.$loading"></i>Log In
</button>
</div>
</div>
<hr />
<ul>
<li>
<a ui-sref="frontend.register({returnState: returnState, email: stateEmail})">
Create a new account
</a>
</li>
<li>
<a ui-sref="frontend.passwordHint">
Get master password hint
</a>
</li>
</ul>
</form>

View File

@@ -1,166 +0,0 @@
<div ng-if="twoFactorProvider === twoFactorProviderConstants.authenticator ||
twoFactorProvider === twoFactorProviderConstants.email">
<p class="login-box-msg" ng-if="twoFactorProvider === twoFactorProviderConstants.authenticator">
Enter the 6 digit verification code from your authenticator app.
</p>
<div ng-if="twoFactorProvider === twoFactorProviderConstants.email" class="text-center">
<p class="login-box-msg">
Enter the 6 digit verification code that was emailed to <b>{{twoFactorEmail}}</b>.
</p>
<p>
Didn't get the email?
<a href="#" stop-click ng-click="sendEmail(true)" ng-if="twoFactorProvider === twoFactorProviderConstants.email">
Send it again
</a>
</p>
</div>
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise">
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
<h4>Errors have occurred</h4>
<ul>
<li ng-repeat="e in twoFactorForm.$errors">{{e}}</li>
</ul>
</div>
<div class="form-group has-feedback" show-errors>
<label for="code" class="sr-only">Code</label>
<input type="text" id="code" name="Code" class="form-control" placeholder="Verification code"
ng-model="token" required api-field autocomplete="off" autocorrect="off" autocapitalize="off"
spellcheck="false" />
<span class="fa fa-lock form-control-feedback"></span>
</div>
<div class="row">
<div class="col-xs-7">
<div class="checkbox">
<label>
<input type="checkbox" id="rememberMe" ng-model="rememberTwoFactor.checked" /> Remember Me
</label>
</div>
</div>
<div class="col-xs-5">
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="twoFactorForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="twoFactorForm.$loading"></i>Log In
</button>
</div>
</div>
</form>
</div>
<div ng-if="twoFactorProvider === twoFactorProviderConstants.yubikey">
<p class="login-box-msg">
Complete logging in with YubiKey.
</p>
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
autocomplete="off">
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
<h4>Errors have occurred</h4>
<ul>
<li ng-repeat="e in twoFactorForm.$errors">{{e}}</li>
</ul>
</div>
<p>Insert your YubiKey into your computer's USB port, then touch its button.</p>
<p>
<img src="images/two-factor/yubikey.jpg" alt="" class="img-rounded img-responsive" />
</p>
<div class="form-group" show-errors>
<label for="code" class="sr-only">Token</label>
<input type="password" id="code" name="Token" class="form-control" ng-model="token" required api-field />
</div>
<div class="row">
<div class="col-xs-7">
<div class="checkbox">
<label>
<input type="checkbox" id="rememberMe" ng-model="rememberTwoFactor.checked" /> Remember Me
</label>
</div>
</div>
<div class="col-xs-5">
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="twoFactorForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="twoFactorForm.$loading"></i>Log In
</button>
</div>
</div>
</form>
</div>
<div ng-if="twoFactorProvider === twoFactorProviderConstants.duo">
<p class="login-box-msg">
Complete logging in with Duo.
</p>
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
autocomplete="off">
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
<h4>Errors have occurred</h4>
<ul>
<li ng-repeat="e in twoFactorForm.$errors">{{e}}</li>
</ul>
</div>
<div id="duoFrameWrapper">
<iframe id="duo_iframe"></iframe>
</div>
<div class="row">
<div class="col-xs-7">
<div class="checkbox">
<label>
<input type="checkbox" id="rememberMe" ng-model="rememberTwoFactor.checked" /> Remember Me
</label>
</div>
</div>
<div class="col-xs-5">
<span ng-show="twoFactorForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon"></i> Logging in...
</span>
</div>
</div>
</form>
</div>
<div ng-if="twoFactorProvider === twoFactorProviderConstants.u2f">
<p class="login-box-msg">
Complete logging in with FIDO U2F.
</p>
<form name="twoFactorForm" api-form="twoFactorPromise" autocomplete="off">
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
<h4>Errors have occurred</h4>
<ul>
<li ng-repeat="e in twoFactorForm.$errors">{{e}}</li>
</ul>
</div>
<p>Insert your Security Key into your computer's USB port. If it has a button, touch it.</p>
<p>
<img src="images/two-factor/u2fkey.jpg" alt="" class="img-rounded img-responsive" />
</p>
<div class="row">
<div class="col-xs-7">
<div class="checkbox">
<label>
<input type="checkbox" id="rememberMe" ng-model="rememberTwoFactor.checked" /> Remember Me
</label>
</div>
</div>
<div class="col-xs-5">
<span ng-show="twoFactorForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon"></i> Logging in...
</span>
</div>
</div>
</form>
</div>
<div ng-if="twoFactorProvider === null">
<p>
This account has two-step login enabled, however, none of the configured two-step providers are supported by this
web browser.
</p>
Please use a supported web browser (such as Chrome) and/or add additional providers that are better supported
across web browsers (such as an authenticator app).
</div>
<hr />
<ul>
<li>
<a stop-click href="#" ng-click="anotherMethod()">Use another two-step login method</a>
</li>
<li>
<a ui-sref="frontend.login.info({returnState: returnState})">Back to log in</a>
</li>
</ul>

View File

@@ -1,32 +0,0 @@
<div class="login-box">
<div class="login-logo">
<i class="fa fa-shield"></i> <b>bit</b>warden
</div>
<div class="login-box-body">
<div ng-show="loading">
Loading...
</div>
<div ng-show="accepting">
Accepting invitation...
</div>
<div ng-show="!loading && !accepting">
<p class="login-box-msg">Join {{state.params.organizationName}}</p>
<p class="text-center"><strong>{{state.params.email}}</strong></p>
<p>
You've been invited to join the organization listed above.
To accept the invitation, you need to log in or create a new bitwarden account.
</p>
<hr />
<div class="row">
<div class="col-sm-6">
<a ui-sref="frontend.login.info({returnState: state, email: state.params.email})"
class="btn btn-primary btn-block btn-flat">Log In</a>
</div>
<div class="col-sm-6">
<a ui-sref="frontend.register({returnState: state, email: state.params.email})"
class="btn btn-primary btn-block btn-flat">Create Account</a>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,39 +0,0 @@
<div class="login-box">
<div class="login-logo">
<i class="fa fa-shield"></i> <b>bit</b>warden
</div>
<div class="login-box-body">
<p class="login-box-msg">Get your master password hint.</p>
<div class="text-center" ng-show="success">
<div class="callout callout-success">
If your account exists ({{model.email}}) we've sent you an email with your master password hint.
</div>
<a ui-sref="frontend.login.info">Ready to log in?</a>
</div>
<form name="passwordHintForm" ng-submit="passwordHintForm.$valid && submit(model)" ng-show="!success"
api-form="submitPromise">
<div class="callout callout-danger validation-errors" ng-show="passwordHintForm.$errors">
<h4>Errors have occurred</h4>
<ul>
<li ng-repeat="e in passwordHintForm.$errors">{{e}}</li>
</ul>
</div>
<div class="form-group has-feedback" show-errors>
<label for="email" class="sr-only">Your account email address</label>
<input type="email" id="email" name="Email" class="form-control" placeholder="Your account email address"
ng-model="model.email" required api-field />
<span class="fa fa-envelope form-control-feedback"></span>
</div>
<div class="row">
<div class="col-xs-7">
<a ui-sref="frontend.login.info">Ready to log in?</a>
</div>
<div class="col-xs-5">
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="passwordHintForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="passwordHintForm.$loading"></i>Submit
</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -1,56 +0,0 @@
<div class="login-box">
<div class="login-logo">
<i class="fa fa-shield"></i> <b>bit</b>warden
</div>
<div class="login-box-body">
<p class="login-box-msg">
In the event that you cannot access your account through your normal two-step login methods, you can use your
two-step login recovery code to disable all two-step providers on your account.
<a href="https://help.bitwarden.com/article/lost-two-step-device/" target="_blank">Learn more</a>
</p>
<div class="text-center" ng-show="success">
<div class="callout callout-success">
Two-step login has been successfully disabled on your account.
</div>
<a ui-sref="frontend.login.info">Ready to log in?</a>
</div>
<form name="recoverForm" ng-submit="recoverForm.$valid && submit(model)" ng-show="!success"
api-form="submitPromise">
<div class="callout callout-danger validation-errors" ng-show="recoverForm.$errors">
<h4>Errors have occurred</h4>
<ul>
<li ng-repeat="e in recoverForm.$errors">{{e}}</li>
</ul>
</div>
<div class="form-group has-feedback" show-errors>
<label for="email" class="sr-only">Email</label>
<input type="email" id="email" name="Email" class="form-control" placeholder="Email" ng-model="model.email"
required api-field />
<span class="fa fa-envelope form-control-feedback"></span>
</div>
<div class="form-group has-feedback" show-errors>
<label for="masterPassword" class="sr-only">Master Password</label>
<input type="password" id="masterPassword" name="MasterPasswordHash" class="form-control" placeholder="Master Password"
ng-model="model.masterPassword"
required api-field />
<span class="fa fa-lock form-control-feedback"></span>
</div>
<div class="form-group has-feedback" show-errors>
<label for="code" class="sr-only">Recovery code</label>
<input type="text" id="code" name="RecoveryCode" class="form-control" placeholder="Recovery code"
ng-model="model.code" required api-field />
<span class="fa fa-key form-control-feedback"></span>
</div>
<div class="row">
<div class="col-xs-7">
<a ui-sref="frontend.login.info">Ready to log in?</a>
</div>
<div class="col-xs-5">
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="recoverForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="recoverForm.$loading"></i>Submit
</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -1,39 +0,0 @@
<div class="login-box">
<div class="login-logo">
<i class="fa fa-shield"></i> <b>bit</b>warden
</div>
<div class="login-box-body">
<p class="login-box-msg">Enter your email address below to recover &amp; delete your bitwarden account.</p>
<div ng-show="success" class="text-center">
<div class="callout callout-success">
If your account exists ({{model.email}}) we've sent you an email with further instructions.
</div>
<a ui-sref="frontend.login.info">Return to log in</a>
</div>
<form name="form" ng-submit="form.$valid && submit(model)" ng-show="!success"
api-form="submitPromise">
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
<h4>Errors have occurred</h4>
<ul>
<li ng-repeat="e in form.$errors">{{e}}</li>
</ul>
</div>
<div class="form-group has-feedback" show-errors>
<label for="email" class="sr-only">Your account email address</label>
<input type="email" id="email" name="Email" class="form-control" placeholder="Your account email address"
ng-model="model.email" required api-field />
<span class="fa fa-envelope form-control-feedback"></span>
</div>
<div class="row">
<div class="col-xs-7">
<a ui-sref="frontend.login.info">Return to log in</a>
</div>
<div class="col-xs-5">
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="form.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="form.$loading"></i>Submit
</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -1,82 +0,0 @@
<div class="register-box">
<div class="register-logo">
<i class="fa fa-shield"></i> <b>bit</b>warden
</div>
<div class="register-box-body">
<p class="login-box-msg">Create a new account.</p>
<div class="text-center" ng-show="success">
<div class="callout callout-success">
<h4>Account Created!</h4>
<p>You may now log in to your new account.</p>
</div>
<a ui-sref="frontend.login.info({returnState: returnState, email: model.email})">Ready to log in?</a>
</div>
<form name="registerForm" ng-submit="registerForm.$valid && register(registerForm)" ng-show="!success"
api-form="registerPromise">
<div class="callout callout-default" ng-show="createOrg">
<h4>Create Organization, Step 1</h4>
<p>Before creating your organization, you first need to create a free personal account.</p>
</div>
<div class="callout callout-danger validation-errors" ng-show="registerForm.$errors">
<h4>Errors have occurred</h4>
<ul>
<li ng-repeat="e in registerForm.$errors">{{e}}</li>
</ul>
</div>
<div class="form-group has-feedback" show-errors>
<label for="email" class="sr-only">Email</label>
<input type="email" id="email" name="Email" class="form-control" placeholder="Email" ng-model="model.email"
ng-readonly="readOnlyEmail" required api-field />
<span class="fa fa-envelope form-control-feedback"></span>
<p class="help-block">You'll use your email address to log in.</p>
</div>
<div class="form-group has-feedback" show-errors>
<label for="name" class="sr-only">Your Name</label>
<input type="text" id="name" name="Name" class="form-control" ng-model="model.name"
placeholder="Your Name" api-field>
<span class="fa fa-user form-control-feedback"></span>
<p class="help-block">What should we call you?</p>
</div>
<div class="form-group has-feedback" show-errors>
<label for="masterPassword" class="sr-only">Master Password</label>
<input type="password" id="masterPassword" name="MasterPasswordHash" class="form-control"
ng-model="model.masterPassword" placeholder="Master Password" required api-field>
<span class="fa fa-lock form-control-feedback"></span>
<p class="help-block">The master password is the password you use to access your vault.</p>
</div>
<div class="form-group has-feedback" show-errors>
<label form="confirmMasterPassword" class="sr-only">Re-type Master Password</label>
<input type="password" id="confirmMasterPassword" name="ConfirmMasterPassword" class="form-control"
placeholder="Re-type Master Password"
ng-model="model.confirmMasterPassword" required api-field>
<span class="fa fa-lock form-control-feedback"></span>
<p class="help-block">
It is very important that you do not forget your master password.
There is <u>no way</u> to recover the password in the event that you forget it.
</p>
</div>
<div class="form-group has-feedback" show-errors>
<label for="hint" class="sr-only">Master Password Hint (optional)</label>
<input type="text" id="hint" name="MasterPasswordHint" class="form-control" ng-model="model.masterPasswordHint"
placeholder="Master Password Hint (optional)" api-field>
<span class="fa fa-lightbulb-o form-control-feedback"></span>
<p class="help-block">A master password hint can help you remember your password if you forget it.</p>
</div>
<div class="row">
<div class="col-xs-7">
<a ui-sref="frontend.login.info({returnState: returnState})">Already have an account?</a>
</div>
<div class="col-xs-5">
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="registerForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="registerForm.$loading"></i>Submit
</button>
</div>
</div>
<hr />
By clicking the above "Submit" button, you are agreeing to the
<a href="https://bitwarden.com/terms/" target="_blank">Terms of Service</a>
and the
<a href="https://bitwarden.com/privacy/" target="_blank">Privacy Policy</a>.
</form>
</div>
</div>

View File

@@ -1,25 +0,0 @@
<div class="modal-header">
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title"><i class="fa fa-key"></i> Two-step Providers</h4>
</div>
<div class="modal-body">
<div class="list-group" ng-repeat="provider in providers | orderBy: 'displayOrder'">
<a href="#" stop-click class="list-group-item" ng-click="choose(provider)">
<img alt="{{::provider.name}}" ng-src="{{'images/two-factor/' + provider.image}}" class="pull-right hidden-xs" />
<h4 class="list-group-item-heading">{{::provider.name}}</h4>
<p class="list-group-item-text">{{::provider.description}}</p>
</a>
</div>
<div class="list-group" style="margin-bottom: 0;">
<a href="https://help.bitwarden.com/article/lost-two-step-device/" target="_blank" class="list-group-item">
<h4 class="list-group-item-heading">Recovery Code</h4>
<p class="list-group-item-text">
Lost access to all of your two-factor providers? Use your recovery code to disable
all two-factor providers from your account.
</p>
</a>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
</div>

View File

@@ -1,8 +0,0 @@
<div class="login-box">
<div class="login-logo">
<i class="fa fa-shield"></i> <b>bit</b>warden
</div>
<div class="login-box-body">
Verifying email...
</div>
</div>

View File

@@ -1,21 +0,0 @@
<div class="login-box">
<div class="login-logo">
<i class="fa fa-shield"></i> <b>bit</b>warden
</div>
<div class="login-box-body">
<div ng-if="deleting">
Deleting account...
</div>
<div ng-if="!deleting">
<div class="callout callout-warning">
<h4><i class="fa fa-warning fa-fw"></i> Warning</h4>
This will permanently delete your account. This cannot be undone.
</div>
<p>
You have requested to delete your bitwarden account (<b>{{email}}</b>).
Click the button below to confirm and proceed.
</p>
<button ng-click="delete()" class="btn btn-danger btn-block btn-flat">Delete Account</button>
</div>
</div>
</div>

View File

@@ -1,29 +0,0 @@
angular
.module('bit')
.factory('apiInterceptor', function ($injector, $q, toastr) {
return {
request: function (config) {
return config;
},
response: function (response) {
if (response.status === 401 || response.status === 403) {
$injector.get('authService').logOut();
$injector.get('$state').go('frontend.login.info').then(function () {
toastr.warning('Your login session has expired.', 'Logged out');
});
}
return response || $q.when(response);
},
responseError: function (rejection) {
if (rejection.status === 401 || rejection.status === 403) {
$injector.get('authService').logOut();
$injector.get('$state').go('frontend.login.info').then(function () {
toastr.warning('Your login session has expired.', 'Logged out');
});
}
return $q.reject(rejection);
}
};
});

View File

@@ -0,0 +1,214 @@
import { NgModule } from '@angular/core';
import {
RouterModule,
Routes,
} from '@angular/router';
import { FrontendLayoutComponent } from './layouts/frontend-layout.component';
import { OrganizationLayoutComponent } from './layouts/organization-layout.component';
import { UserLayoutComponent } from './layouts/user-layout.component';
import { AcceptOrganizationComponent } from './accounts/accept-organization.component';
import { HintComponent } from './accounts/hint.component';
import { LockComponent } from './accounts/lock.component';
import { LoginComponent } from './accounts/login.component';
import { RecoverDeleteComponent } from './accounts/recover-delete.component';
import { RecoverTwoFactorComponent } from './accounts/recover-two-factor.component';
import { RegisterComponent } from './accounts/register.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { VerifyEmailTokenComponent } from './accounts/verify-email-token.component';
import { VerifyRecoverDeleteComponent } from './accounts/verify-recover-delete.component';
import { CollectionsComponent as OrgManageCollectionsComponent } from './organizations/manage/collections.component';
import { EventsComponent as OrgEventsComponent } from './organizations/manage/events.component';
import { GroupsComponent as OrgGroupsComponent } from './organizations/manage/groups.component';
import { ManageComponent as OrgManageComponent } from './organizations/manage/manage.component';
import { PeopleComponent as OrgPeopleComponent } from './organizations/manage/people.component';
import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
import { OrganizationBillingComponent } from './organizations/settings/organization-billing.component';
import { SettingsComponent as OrgSettingsComponent } from './organizations/settings/settings.component';
import {
TwoFactorSetupComponent as OrgTwoFactorSetupComponent,
} from './organizations/settings/two-factor-setup.component';
import { ExportComponent as OrgExportComponent } from './organizations/tools/export.component';
import { ImportComponent as OrgImportComponent } from './organizations/tools/import.component';
import { ToolsComponent as OrgToolsComponent } from './organizations/tools/tools.component';
import { VaultComponent as OrgVaultComponent } from './organizations/vault/vault.component';
import { AccountComponent } from './settings/account.component';
import { CreateOrganizationComponent } from './settings/create-organization.component';
import { DomainRulesComponent } from './settings/domain-rules.component';
import { OptionsComponent } from './settings/options.component';
import { OrganizationsComponent } from './settings/organizations.component';
import { PremiumComponent } from './settings/premium.component';
import { SettingsComponent } from './settings/settings.component';
import { TwoFactorSetupComponent } from './settings/two-factor-setup.component';
import { UserBillingComponent } from './settings/user-billing.component';
import { BreachReportComponent } from './tools/breach-report.component';
import { ExportComponent } from './tools/export.component';
import { ImportComponent } from './tools/import.component';
import { PasswordGeneratorComponent } from './tools/password-generator.component';
import { ToolsComponent } from './tools/tools.component';
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 { OrganizationUserType } from 'jslib/enums/organizationUserType';
const routes: Routes = [
{
path: '',
component: FrontendLayoutComponent,
children: [
{ path: '', pathMatch: 'full', component: LoginComponent, canActivate: [UnauthGuardService] },
{ path: '2fa', component: TwoFactorComponent, canActivate: [UnauthGuardService] },
{
path: 'register', component: RegisterComponent,
canActivate: [UnauthGuardService],
data: { titleId: 'createAccount' },
},
{
path: 'hint', component: HintComponent,
canActivate: [UnauthGuardService],
data: { titleId: 'passwordHint' },
},
{ path: 'lock', component: LockComponent },
{ path: 'verify-email', component: VerifyEmailTokenComponent },
{
path: 'accept-organization',
component: AcceptOrganizationComponent,
data: { titleId: 'joinOrganization' },
},
{ path: 'recover', pathMatch: 'full', redirectTo: 'recover-2fa' },
{
path: 'recover-2fa',
component: RecoverTwoFactorComponent,
canActivate: [UnauthGuardService],
data: { titleId: 'recoverAccountTwoStep' },
},
{
path: 'recover-delete',
component: RecoverDeleteComponent,
canActivate: [UnauthGuardService],
data: { titleId: 'deleteAccount' },
},
{
path: 'verify-recover-delete',
component: VerifyRecoverDeleteComponent,
canActivate: [UnauthGuardService],
data: { titleId: 'deleteAccount' },
},
],
},
{
path: '',
component: UserLayoutComponent,
canActivate: [AuthGuardService],
children: [
{ path: 'vault', component: VaultComponent, data: { titleId: 'myVault' } },
{
path: 'settings',
component: SettingsComponent,
children: [
{ path: '', pathMatch: 'full', redirectTo: 'account' },
{ path: 'account', component: AccountComponent, data: { titleId: 'myAccount' } },
{ path: 'options', component: OptionsComponent, data: { titleId: 'options' } },
{ path: 'domain-rules', component: DomainRulesComponent, data: { titleId: 'domainRules' } },
{ path: 'two-factor', component: TwoFactorSetupComponent, data: { titleId: 'twoStepLogin' } },
{ path: 'premium', component: PremiumComponent, data: { titleId: 'goPremium' } },
{ path: 'billing', component: UserBillingComponent, data: { titleId: 'billingAndLicensing' } },
{ path: 'organizations', component: OrganizationsComponent, data: { titleId: 'organizations' } },
{
path: 'create-organization',
component: CreateOrganizationComponent,
data: { titleId: 'newOrganization' },
},
],
},
{
path: 'tools',
component: ToolsComponent,
canActivate: [AuthGuardService],
children: [
{ path: '', pathMatch: 'full', redirectTo: 'generator' },
{ path: 'import', component: ImportComponent, data: { titleId: 'importData' } },
{ path: 'export', component: ExportComponent, data: { titleId: 'exportVault' } },
{
path: 'generator',
component: PasswordGeneratorComponent,
data: { titleId: 'passwordGenerator' },
},
{ path: 'breach-report', component: BreachReportComponent, data: { titleId: 'dataBreachReport' } },
],
},
],
},
{
path: 'organizations/:organizationId',
component: OrganizationLayoutComponent,
canActivate: [AuthGuardService, OrganizationGuardService],
children: [
{ path: '', pathMatch: 'full', redirectTo: 'vault' },
{ path: 'vault', component: OrgVaultComponent, data: { titleId: 'vault' } },
{
path: 'tools',
component: OrgToolsComponent,
canActivate: [OrganizationTypeGuardService],
data: { allowedTypes: [OrganizationUserType.Owner, OrganizationUserType.Admin] },
children: [
{ path: '', pathMatch: 'full', redirectTo: 'import' },
{ path: 'import', component: OrgImportComponent, data: { titleId: 'importData' } },
{ path: 'export', component: OrgExportComponent, data: { titleId: 'exportVault' } },
],
},
{
path: 'manage',
component: OrgManageComponent,
canActivate: [OrganizationTypeGuardService],
data: { allowedTypes: [OrganizationUserType.Owner, OrganizationUserType.Admin] },
children: [
{ path: '', pathMatch: 'full', redirectTo: 'people' },
{ path: 'collections', component: OrgManageCollectionsComponent, data: { titleId: 'collections' } },
{ path: 'events', component: OrgEventsComponent, data: { titleId: 'eventLogs' } },
{ path: 'groups', component: OrgGroupsComponent, data: { titleId: 'groups' } },
{ path: 'people', component: OrgPeopleComponent, data: { titleId: 'people' } },
],
},
{
path: 'settings',
component: OrgSettingsComponent,
canActivate: [OrganizationTypeGuardService],
data: { allowedTypes: [OrganizationUserType.Owner] },
children: [
{ path: '', pathMatch: 'full', redirectTo: 'account' },
{ path: 'account', component: OrgAccountComponent, data: { titleId: 'myOrganization' } },
{ path: 'two-factor', component: OrgTwoFactorSetupComponent, data: { titleId: 'twoStepLogin' } },
{
path: 'billing',
component: OrganizationBillingComponent,
data: { titleId: 'billingAndLicensing' },
},
],
},
],
},
{ path: '**', redirectTo: '' },
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
useHash: true,
/*enableTracing: true,*/
})],
exports: [RouterModule],
})
export class AppRoutingModule { }

View File

@@ -0,0 +1,2 @@
<toaster-container [toasterconfig]="toasterConfig"></toaster-container>
<router-outlet></router-outlet>

212
src/app/app.component.ts Normal file
View File

@@ -0,0 +1,212 @@
import * as jq from 'jquery';
import * as _swal from 'sweetalert';
import { SweetAlert } from 'sweetalert/typings/core';
import {
ToasterConfig,
ToasterService,
} from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import {
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import {
NavigationEnd,
Router,
} from '@angular/router';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { AuthService } from 'jslib/abstractions/auth.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { FolderService } from 'jslib/abstractions/folder.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { LockService } from 'jslib/abstractions/lock.service';
import { NotificationsService } from 'jslib/abstractions/notifications.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { SettingsService } from 'jslib/abstractions/settings.service';
import { SyncService } from 'jslib/abstractions/sync.service';
import { TokenService } from 'jslib/abstractions/token.service';
import { UserService } from 'jslib/abstractions/user.service';
import { ConstantsService } from 'jslib/services/constants.service';
import { RouterService } from './services/router.service';
const BroadcasterSubscriptionId = 'AppComponent';
// Hack due to Angular 5.2 bug
const swal: SweetAlert = _swal as any;
const IdleTimeout = 60000 * 10; // 10 minutes
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
})
export class AppComponent implements OnDestroy, OnInit {
toasterConfig: ToasterConfig = new ToasterConfig({
showCloseButton: true,
mouseoverTimerStop: true,
animation: 'flyRight',
limit: 5,
});
private lastActivity: number = null;
private idleTimer: number = null;
private isIdle = false;
constructor(private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
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 toasterService: ToasterService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private ngZone: NgZone,
private lockService: LockService, private storageService: StorageService,
private cryptoService: CryptoService, private collectionService: CollectionService,
private routerService: RouterService, private searchService: SearchService,
private notificationsService: NotificationsService) { }
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
window.onmousemove = () => this.recordActivity();
window.onmousedown = () => this.recordActivity();
window.ontouchstart = () => this.recordActivity();
window.onclick = () => this.recordActivity();
window.onscroll = () => this.recordActivity();
window.onkeypress = () => this.recordActivity();
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'loggedIn':
case 'loggedOut':
case 'unlocked':
this.notificationsService.updateConnection(false);
break;
case 'logout':
this.logOut(!!message.expired);
break;
case 'lockVault':
await this.lockService.lock();
break;
case 'locked':
this.notificationsService.updateConnection(false);
this.router.navigate(['lock']);
break;
case 'syncStarted':
break;
case 'syncCompleted':
break;
case 'upgradeOrganization':
const upgradeConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('upgradeOrganizationDesc'), this.i18nService.t('upgradeOrganization'),
this.i18nService.t('upgradeOrganization'), this.i18nService.t('cancel'));
if (upgradeConfirmed) {
this.router.navigate(['organizations', message.organizationId, 'settings', 'billing']);
}
break;
case 'premiumRequired':
const premiumConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('premiumRequiredDesc'), this.i18nService.t('premiumRequired'),
this.i18nService.t('learnMore'), this.i18nService.t('cancel'));
if (premiumConfirmed) {
this.router.navigate(['settings/premium']);
}
break;
default:
break;
}
});
});
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
const modals = Array.from(document.querySelectorAll('.modal'));
for (const modal of modals) {
(jq(modal) as any).modal('hide');
}
if (document.querySelector('.swal-modal') != null) {
swal.close(undefined);
}
}
});
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
private async logOut(expired: boolean) {
const userId = await this.userService.getUserId();
await Promise.all([
this.syncService.setLastSync(new Date(0)),
this.tokenService.clearToken(),
this.cryptoService.clearKeys(),
this.userService.clear(),
this.settingsService.clear(userId),
this.cipherService.clear(userId),
this.folderService.clear(userId),
this.collectionService.clear(userId),
this.passwordGenerationService.clear(),
]);
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'));
}
this.router.navigate(['/']);
});
}
private async recordActivity() {
const now = (new Date()).getTime();
if (this.lastActivity != null && now - this.lastActivity < 250) {
return;
}
this.lastActivity = now;
this.storageService.save(ConstantsService.lastActiveKey, now);
// Idle states
if (this.isIdle) {
this.isIdle = false;
this.idleStateChanged();
}
if (this.idleTimer != null) {
window.clearTimeout(this.idleTimer);
this.idleTimer = null;
}
this.idleTimer = window.setTimeout(() => {
if (!this.isIdle) {
this.isIdle = true;
this.idleStateChanged();
}
}, IdleTimeout);
}
private idleStateChanged() {
if (this.isIdle) {
this.notificationsService.disconnectFromInactivity();
} else {
this.notificationsService.reconnectFromActivity();
}
}
}

View File

@@ -1,27 +0,0 @@
angular
.module('bit', [
'ui.router',
'ngMessages',
'angular-jwt',
'ui.bootstrap.showErrors',
'toastr',
'angulartics',
// @if !selfHosted
'angulartics.google.analytics',
'angular-stripe',
'credit-cards',
// @endif
'angular-promise-polyfill',
'bit.directives',
'bit.filters',
'bit.services',
'bit.global',
'bit.accounts',
'bit.vault',
'bit.settings',
'bit.tools',
'bit.organization',
'bit.reports'
]);

329
src/app/app.module.ts Normal file
View File

@@ -0,0 +1,329 @@
import 'core-js';
import { ToasterModule } from 'angular2-toaster';
import { Angulartics2Module } from 'angulartics2';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import { AppRoutingModule } from './app-routing.module';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ServicesModule } from './services/services.module';
import { AppComponent } from './app.component';
import { ModalComponent } from './modal.component';
import { AvatarComponent } from './components/avatar.component';
import { CalloutComponent } from './components/callout.component';
import { FooterComponent } from './layouts/footer.component';
import { FrontendLayoutComponent } from './layouts/frontend-layout.component';
import { NavbarComponent } from './layouts/navbar.component';
import { OrganizationLayoutComponent } from './layouts/organization-layout.component';
import { UserLayoutComponent } from './layouts/user-layout.component';
import { AcceptOrganizationComponent } from './accounts/accept-organization.component';
import { HintComponent } from './accounts/hint.component';
import { LockComponent } from './accounts/lock.component';
import { LoginComponent } from './accounts/login.component';
import { RecoverDeleteComponent } from './accounts/recover-delete.component';
import { RecoverTwoFactorComponent } from './accounts/recover-two-factor.component';
import { RegisterComponent } from './accounts/register.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { VerifyEmailTokenComponent } from './accounts/verify-email-token.component';
import { VerifyRecoverDeleteComponent } from './accounts/verify-recover-delete.component';
import {
CollectionAddEditComponent as OrgCollectionAddEditComponent,
} from './organizations/manage/collection-add-edit.component';
import { CollectionsComponent as OrgManageCollectionsComponent } from './organizations/manage/collections.component';
import { EntityEventsComponent as OrgEntityEventsComponent } from './organizations/manage/entity-events.component';
import { EntityUsersComponent as OrgEntityUsersComponent } from './organizations/manage/entity-users.component';
import { EventsComponent as OrgEventsComponent } from './organizations/manage/events.component';
import { GroupAddEditComponent as OrgGroupAddEditComponent } from './organizations/manage/group-add-edit.component';
import { GroupsComponent as OrgGroupsComponent } from './organizations/manage/groups.component';
import { ManageComponent as OrgManageComponent } from './organizations/manage/manage.component';
import { PeopleComponent as OrgPeopleComponent } from './organizations/manage/people.component';
import { UserAddEditComponent as OrgUserAddEditComponent } from './organizations/manage/user-add-edit.component';
import { UserGroupsComponent as OrgUserGroupsComponent } from './organizations/manage/user-groups.component';
import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
import { AdjustSeatsComponent } from './organizations/settings/adjust-seats.component';
import { DeleteOrganizationComponent } from './organizations/settings/delete-organization.component';
import { OrganizationBillingComponent } from './organizations/settings/organization-billing.component';
import { SettingsComponent as OrgSettingComponent } from './organizations/settings/settings.component';
import {
TwoFactorSetupComponent as OrgTwoFactorSetupComponent,
} from './organizations/settings/two-factor-setup.component';
import { ExportComponent as OrgExportComponent } from './organizations/tools/export.component';
import { ImportComponent as OrgImportComponent } from './organizations/tools/import.component';
import { ToolsComponent as OrgToolsComponent } from './organizations/tools/tools.component';
import { AddEditComponent as OrgAddEditComponent } from './organizations/vault/add-edit.component';
import { AttachmentsComponent as OrgAttachmentsComponent } from './organizations/vault/attachments.component';
import { CiphersComponent as OrgCiphersComponent } from './organizations/vault/ciphers.component';
import { CollectionsComponent as OrgCollectionsComponent } from './organizations/vault/collections.component';
import { GroupingsComponent as OrgGroupingsComponent } from './organizations/vault/groupings.component';
import { VaultComponent as OrgVaultComponent } from './organizations/vault/vault.component';
import { AccountComponent } from './settings/account.component';
import { AdjustPaymentComponent } from './settings/adjust-payment.component';
import { AdjustStorageComponent } from './settings/adjust-storage.component';
import { ChangeEmailComponent } from './settings/change-email.component';
import { ChangePasswordComponent } from './settings/change-password.component';
import { CreateOrganizationComponent } from './settings/create-organization.component';
import { DeauthorizeSessionsComponent } from './settings/deauthorize-sessions.component';
import { DeleteAccountComponent } from './settings/delete-account.component';
import { DomainRulesComponent } from './settings/domain-rules.component';
import { OptionsComponent } from './settings/options.component';
import { OrganizationsComponent } from './settings/organizations.component';
import { PaymentComponent } from './settings/payment.component';
import { PremiumComponent } from './settings/premium.component';
import { ProfileComponent } from './settings/profile.component';
import { PurgeVaultComponent } from './settings/purge-vault.component';
import { SettingsComponent } from './settings/settings.component';
import { TwoFactorAuthenticatorComponent } from './settings/two-factor-authenticator.component';
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 { TwoFactorYubiKeyComponent } from './settings/two-factor-yubikey.component';
import { UpdateKeyComponent } from './settings/update-key.component';
import { UpdateLicenseComponent } from './settings/update-license.component';
import { UserBillingComponent } from './settings/user-billing.component';
import { VerifyEmailComponent } from './settings/verify-email.component';
import { BreachReportComponent } from './tools/breach-report.component';
import { ExportComponent } from './tools/export.component';
import { ImportComponent } from './tools/import.component';
import { PasswordGeneratorHistoryComponent } from './tools/password-generator-history.component';
import { PasswordGeneratorComponent } from './tools/password-generator.component';
import { ToolsComponent } from './tools/tools.component';
import { AddEditComponent } from './vault/add-edit.component';
import { AttachmentsComponent } from './vault/attachments.component';
import { BulkDeleteComponent } from './vault/bulk-delete.component';
import { BulkMoveComponent } from './vault/bulk-move.component';
import { BulkShareComponent } from './vault/bulk-share.component';
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 { ShareComponent } from './vault/share.component';
import { VaultComponent } from './vault/vault.component';
import { IconComponent } from 'jslib/angular/components/icon.component';
import { ApiActionDirective } from 'jslib/angular/directives/api-action.directive';
import { AutofocusDirective } from 'jslib/angular/directives/autofocus.directive';
import { BlurClickDirective } from 'jslib/angular/directives/blur-click.directive';
import { BoxRowDirective } from 'jslib/angular/directives/box-row.directive';
import { FallbackSrcDirective } from 'jslib/angular/directives/fallback-src.directive';
import { InputVerbatimDirective } from 'jslib/angular/directives/input-verbatim.directive';
import { StopClickDirective } from 'jslib/angular/directives/stop-click.directive';
import { StopPropDirective } from 'jslib/angular/directives/stop-prop.directive';
import { TrueFalseValueDirective } from 'jslib/angular/directives/true-false-value.directive';
import { I18nPipe } from 'jslib/angular/pipes/i18n.pipe';
import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
import { registerLocaleData } from '@angular/common';
import localeCs from '@angular/common/locales/cs';
import localeDa from '@angular/common/locales/da';
import localeDe from '@angular/common/locales/de';
import localeEs from '@angular/common/locales/es';
import localeEt from '@angular/common/locales/et';
import localeFr from '@angular/common/locales/fr';
import localeIt from '@angular/common/locales/it';
import localeNb from '@angular/common/locales/nb';
import localeNl from '@angular/common/locales/nl';
import localePl from '@angular/common/locales/pl';
import localePtBr from '@angular/common/locales/pt';
import localePtPt from '@angular/common/locales/pt-PT';
import localeRu from '@angular/common/locales/ru';
import localeSk from '@angular/common/locales/sk';
import localeSv from '@angular/common/locales/sv';
import localeZhCn from '@angular/common/locales/zh-Hans';
registerLocaleData(localeCs, 'cs');
registerLocaleData(localeDa, 'da');
registerLocaleData(localeDe, 'de');
registerLocaleData(localeEs, 'es');
registerLocaleData(localeEt, 'et');
registerLocaleData(localeFr, 'fr');
registerLocaleData(localeIt, 'it');
registerLocaleData(localeNb, 'nb');
registerLocaleData(localeNl, 'nl');
registerLocaleData(localePl, 'pl');
registerLocaleData(localePtBr, 'pt-BR');
registerLocaleData(localePtPt, 'pt-PT');
registerLocaleData(localeRu, 'ru');
registerLocaleData(localeSk, 'sk');
registerLocaleData(localeSv, 'sv');
registerLocaleData(localeZhCn, 'zh-CN');
@NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
AppRoutingModule,
ServicesModule,
Angulartics2Module.forRoot([Angulartics2GoogleAnalytics], {
pageTracking: {
clearQueryParams: true,
},
}),
ToasterModule,
],
declarations: [
AcceptOrganizationComponent,
AccountComponent,
AddEditComponent,
AdjustPaymentComponent,
AdjustSeatsComponent,
AdjustStorageComponent,
ApiActionDirective,
AppComponent,
AttachmentsComponent,
AutofocusDirective,
AvatarComponent,
BlurClickDirective,
BoxRowDirective,
BreachReportComponent,
BulkDeleteComponent,
BulkMoveComponent,
BulkShareComponent,
CalloutComponent,
ChangeEmailComponent,
ChangePasswordComponent,
CiphersComponent,
CollectionsComponent,
CreateOrganizationComponent,
DeauthorizeSessionsComponent,
DeleteAccountComponent,
DeleteOrganizationComponent,
DomainRulesComponent,
ExportComponent,
FallbackSrcDirective,
FolderAddEditComponent,
FooterComponent,
FrontendLayoutComponent,
GroupingsComponent,
HintComponent,
IconComponent,
I18nPipe,
ImportComponent,
InputVerbatimDirective,
LockComponent,
LoginComponent,
ModalComponent,
NavbarComponent,
OptionsComponent,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
OrgAttachmentsComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
OrgEntityUsersComponent,
OrgEventsComponent,
OrgExportComponent,
OrgImportComponent,
OrgGroupAddEditComponent,
OrgGroupingsComponent,
OrgGroupsComponent,
OrgManageCollectionsComponent,
OrgManageComponent,
OrgPeopleComponent,
OrgSettingComponent,
OrgToolsComponent,
OrgTwoFactorSetupComponent,
OrgUserAddEditComponent,
OrgUserGroupsComponent,
OrganizationsComponent,
OrganizationLayoutComponent,
OrgVaultComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
PaymentComponent,
PremiumComponent,
ProfileComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RegisterComponent,
SearchCiphersPipe,
SearchPipe,
SettingsComponent,
ShareComponent,
StopClickDirective,
StopPropDirective,
ToolsComponent,
TrueFalseValueDirective,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorU2fComponent,
TwoFactorVerifyComponent,
TwoFactorYubiKeyComponent,
UpdateKeyComponent,
UpdateLicenseComponent,
UserBillingComponent,
UserLayoutComponent,
VaultComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
],
entryComponents: [
AddEditComponent,
AttachmentsComponent,
BulkDeleteComponent,
BulkMoveComponent,
BulkShareComponent,
CollectionsComponent,
DeauthorizeSessionsComponent,
DeleteAccountComponent,
DeleteOrganizationComponent,
FolderAddEditComponent,
ModalComponent,
OrgAddEditComponent,
OrgAttachmentsComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
OrgEntityEventsComponent,
OrgEntityUsersComponent,
OrgGroupAddEditComponent,
OrgUserAddEditComponent,
OrgUserGroupsComponent,
PasswordGeneratorHistoryComponent,
PurgeVaultComponent,
ShareComponent,
TwoFactorAuthenticatorComponent,
TwoFactorDuoComponent,
TwoFactorEmailComponent,
TwoFactorOptionsComponent,
TwoFactorRecoveryComponent,
TwoFactorU2fComponent,
TwoFactorYubiKeyComponent,
UpdateKeyComponent,
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule { }

View File

@@ -0,0 +1,128 @@
import {
Component,
Input,
OnChanges,
OnInit,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { StateService } from 'jslib/abstractions/state.service';
import { Utils } from 'jslib/misc/utils';
@Component({
selector: 'app-avatar',
template: '<img [src]="sanitizer.bypassSecurityTrustResourceUrl(src)" title="{{data}}" ' +
'[ngClass]="{\'rounded-circle\': circle}">',
})
export class AvatarComponent implements OnChanges, OnInit {
@Input() data: string;
@Input() email: string;
@Input() size = 45;
@Input() charCount = 2;
@Input() textColor = '#ffffff';
@Input() fontSize = 20;
@Input() fontWeight = 300;
@Input() dynamic = false;
@Input() circle = false;
src: string;
constructor(public sanitizer: DomSanitizer, private cryptoFunctionService: CryptoFunctionService,
private stateService: StateService) { }
ngOnInit() {
if (!this.dynamic) {
this.generate();
}
}
ngOnChanges() {
if (this.dynamic) {
this.generate();
}
}
private async generate() {
const enableGravatars = await this.stateService.get<boolean>('enableGravatars');
if (enableGravatars && this.email != null) {
const hashBytes = await this.cryptoFunctionService.hash(this.email.toLowerCase().trim(), 'md5');
const hash = Utils.fromBufferToHex(hashBytes).toLowerCase();
this.src = 'https://www.gravatar.com/avatar/' + hash + '?s=' + this.size + '&r=pg&d=retro';
} else {
let chars: string = null;
const upperData = this.data.toUpperCase();
if (this.charCount > 1) {
chars = this.getFirstLetters(upperData, this.charCount);
}
if (chars == null) {
chars = upperData.substr(0, this.charCount);
}
const charObj = this.getCharText(chars);
const color = this.stringToColor(upperData);
const svg = this.getSvg(this.size, color);
svg.appendChild(charObj);
const html = window.document.createElement('div').appendChild(svg).outerHTML;
const svgHtml = window.btoa(unescape(encodeURIComponent(html)));
this.src = 'data:image/svg+xml;base64,' + svgHtml;
}
}
private stringToColor(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
// tslint:disable-next-line
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let color = '#';
for (let i = 0; i < 3; i++) {
// tslint:disable-next-line
const value = (hash >> (i * 8)) & 0xFF;
color += ('00' + value.toString(16)).substr(-2);
}
return color;
}
private getFirstLetters(data: string, count: number): string {
const parts = data.split(' ');
if (parts.length > 1) {
let text = '';
for (let i = 0; i < count; i++) {
text += parts[i].substr(0, 1);
}
return text;
}
return null;
}
private getSvg(size: number, color: string): HTMLElement {
const svgTag = window.document.createElement('svg');
svgTag.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgTag.setAttribute('pointer-events', 'none');
svgTag.setAttribute('width', size.toString());
svgTag.setAttribute('height', size.toString());
svgTag.style.backgroundColor = color;
svgTag.style.width = size + 'px';
svgTag.style.height = size + 'px';
return svgTag;
}
private getCharText(character: string): HTMLElement {
const textTag = window.document.createElement('text');
textTag.setAttribute('text-anchor', 'middle');
textTag.setAttribute('y', '50%');
textTag.setAttribute('x', '50%');
textTag.setAttribute('dy', '0.35em');
textTag.setAttribute('pointer-events', 'auto');
textTag.setAttribute('fill', this.textColor);
textTag.setAttribute('font-family', '"Open Sans","Helvetica Neue",Helvetica,Arial,' +
'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"');
textTag.textContent = character;
textTag.style.fontWeight = this.fontWeight.toString();
textTag.style.fontSize = this.fontSize + 'px';
return textTag;
}
}

View File

@@ -0,0 +1,7 @@
<div class="callout callout-{{calloutStyle}}" role="alert">
<h3 class="callout-heading" *ngIf="title">
<i class="fa {{icon}}" *ngIf="icon"></i>
{{title}}
</h3>
<ng-content></ng-content>
</div>

View File

@@ -0,0 +1,53 @@
import {
Component,
Input,
OnInit,
} from '@angular/core';
import { I18nService } from 'jslib/abstractions/i18n.service';
@Component({
selector: 'app-callout',
templateUrl: 'callout.component.html',
})
export class CalloutComponent implements OnInit {
@Input() type = 'info';
@Input() icon: string;
@Input() title: string;
calloutStyle: string;
constructor(private i18nService: I18nService) { }
ngOnInit() {
this.calloutStyle = this.type;
if (this.type === 'warning' || this.type === 'danger') {
if (this.type === 'danger') {
this.calloutStyle = 'danger';
}
if (this.title === undefined) {
this.title = this.i18nService.t('warning');
}
if (this.icon === undefined) {
this.icon = 'fa-warning';
}
} else if (this.type === 'error') {
this.calloutStyle = 'danger';
if (this.title === undefined) {
this.title = this.i18nService.t('error');
}
if (this.icon === undefined) {
this.icon = 'fa-bolt';
}
} else if (this.type === 'tip') {
this.calloutStyle = 'success';
if (this.title === undefined) {
this.title = this.i18nService.t('tip');
}
if (this.icon === undefined) {
this.icon = 'fa-lightbulb-o';
}
}
}
}

View File

@@ -1,390 +0,0 @@
angular
.module('bit')
.config(function ($stateProvider, $urlRouterProvider, $httpProvider, jwtInterceptorProvider, jwtOptionsProvider,
$uibTooltipProvider, toastrConfig, $locationProvider, $qProvider, appSettings
// @if !selfHosted
/* jshint ignore:start */
, stripeProvider
/* jshint ignore:end */
// @endif
) {
angular.extend(appSettings, window.bitwardenAppSettings);
$qProvider.errorOnUnhandledRejections(false);
$locationProvider.hashPrefix('');
var jwtConfig = {
whiteListedDomains: appSettings.whitelistDomains
};
if (!appSettings.selfHosted) {
var userAgent = navigator.userAgent.toLowerCase();
if (userAgent.indexOf('safari') > -1 && userAgent.indexOf('chrome') === -1) {
// Safari doesn't work with unconventional "Content-Language" header for CORS.
// See notes here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
jwtConfig.urlParam = 'access_token';
}
else {
// Using Content-Language header since it is unused and is a CORS-safelisted header. This avoids pre-flights.
jwtConfig.authHeader = 'Content-Language';
}
}
jwtOptionsProvider.config(jwtConfig);
var refreshPromise;
jwtInterceptorProvider.tokenGetter = /*@ngInject*/ function (options, tokenService, authService) {
if (options.url.indexOf(appSettings.apiUri) !== 0) {
return;
}
if (refreshPromise) {
return refreshPromise;
}
var token = tokenService.getToken();
if (!token) {
return;
}
if (!tokenService.tokenNeedsRefresh(token)) {
return token;
}
var p = authService.refreshAccessToken();
if (!p) {
return;
}
refreshPromise = p.then(function (newToken) {
refreshPromise = null;
return newToken || token;
});
return refreshPromise;
};
// @if !selfHosted
stripeProvider.setPublishableKey(appSettings.stripeKey);
// @endif
angular.extend(toastrConfig, {
closeButton: true,
progressBar: true,
showMethod: 'slideDown',
target: '.toast-target'
});
$uibTooltipProvider.options({
popupDelay: 600,
appendToBody: true
});
if ($httpProvider.defaults.headers.post) {
$httpProvider.defaults.headers.post = {};
}
$httpProvider.defaults.headers.post['Content-Type'] = 'text/plain; charset=utf-8';
// stop IE from caching get requests
if (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0) {
if (!$httpProvider.defaults.headers.get) {
$httpProvider.defaults.headers.get = {};
}
$httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
$httpProvider.defaults.headers.get.Pragma = 'no-cache';
}
$httpProvider.interceptors.push('apiInterceptor');
$httpProvider.interceptors.push('jwtInterceptor');
$urlRouterProvider.otherwise('/');
$stateProvider
// Backend
.state('backend', {
templateUrl: 'app/views/backendLayout.html',
abstract: true,
data: {
authorize: true
}
})
.state('backend.user', {
templateUrl: 'app/views/userLayout.html',
abstract: true
})
.state('backend.user.vault', {
url: '^/vault',
templateUrl: 'app/vault/views/vault.html',
controller: 'vaultController',
data: {
pageTitle: 'My Vault',
controlSidebar: true
},
params: {
refreshFromServer: false
}
})
.state('backend.user.shared', {
url: '^/shared',
templateUrl: 'app/vault/views/vaultShared.html',
controller: 'vaultSharedController',
data: { pageTitle: 'Shared' }
})
.state('backend.user.settings', {
url: '^/settings',
templateUrl: 'app/settings/views/settings.html',
controller: 'settingsController',
data: { pageTitle: 'Settings' }
})
.state('backend.user.settingsDomains', {
url: '^/settings/domains',
templateUrl: 'app/settings/views/settingsDomains.html',
controller: 'settingsDomainsController',
data: { pageTitle: 'Domain Settings' }
})
.state('backend.user.settingsTwoStep', {
url: '^/settings/two-step',
templateUrl: 'app/settings/views/settingsTwoStep.html',
controller: 'settingsTwoStepController',
data: { pageTitle: 'Two-step Login' }
})
.state('backend.user.settingsCreateOrg', {
url: '^/settings/create-organization',
templateUrl: 'app/settings/views/settingsCreateOrganization.html',
controller: 'settingsCreateOrganizationController',
data: { pageTitle: 'Create Organization' }
})
.state('backend.user.settingsBilling', {
url: '^/settings/billing',
templateUrl: 'app/settings/views/settingsBilling.html',
controller: 'settingsBillingController',
data: { pageTitle: 'Billing' }
})
.state('backend.user.settingsPremium', {
url: '^/settings/premium',
templateUrl: 'app/settings/views/settingsPremium.html',
controller: 'settingsPremiumController',
data: { pageTitle: 'Go Premium' }
})
.state('backend.user.tools', {
url: '^/tools',
templateUrl: 'app/tools/views/tools.html',
controller: 'toolsController',
data: { pageTitle: 'Tools' }
})
.state('backend.user.reportsBreach', {
url: '^/reports/breach',
templateUrl: 'app/reports/views/reportsBreach.html',
controller: 'reportsBreachController',
data: { pageTitle: 'Data Breach Report' }
})
.state('backend.user.apps', {
url: '^/apps',
templateUrl: 'app/views/apps.html',
controller: 'appsController',
data: { pageTitle: 'Get the Apps' }
})
.state('backend.org', {
templateUrl: 'app/views/organizationLayout.html',
abstract: true
})
.state('backend.org.dashboard', {
url: '^/organization/:orgId',
templateUrl: 'app/organization/views/organizationDashboard.html',
controller: 'organizationDashboardController',
data: { pageTitle: 'Organization Dashboard' }
})
.state('backend.org.people', {
url: '/organization/:orgId/people',
templateUrl: 'app/organization/views/organizationPeople.html',
controller: 'organizationPeopleController',
data: { pageTitle: 'Organization People' }
})
.state('backend.org.collections', {
url: '/organization/:orgId/collections',
templateUrl: 'app/organization/views/organizationCollections.html',
controller: 'organizationCollectionsController',
data: { pageTitle: 'Organization Collections' }
})
.state('backend.org.settings', {
url: '/organization/:orgId/settings',
templateUrl: 'app/organization/views/organizationSettings.html',
controller: 'organizationSettingsController',
data: { pageTitle: 'Organization Settings' }
})
.state('backend.org.billing', {
url: '/organization/:orgId/billing',
templateUrl: 'app/organization/views/organizationBilling.html',
controller: 'organizationBillingController',
data: { pageTitle: 'Organization Billing' }
})
.state('backend.org.vault', {
url: '/organization/:orgId/vault',
templateUrl: 'app/organization/views/organizationVault.html',
controller: 'organizationVaultController',
data: { pageTitle: 'Organization Vault' }
})
.state('backend.org.groups', {
url: '/organization/:orgId/groups',
templateUrl: 'app/organization/views/organizationGroups.html',
controller: 'organizationGroupsController',
data: { pageTitle: 'Organization Groups' }
})
// Frontend
.state('frontend', {
templateUrl: 'app/views/frontendLayout.html',
abstract: true,
data: {
authorize: false
}
})
.state('frontend.login', {
templateUrl: 'app/accounts/views/accountsLogin.html',
controller: 'accountsLoginController',
params: {
returnState: null,
email: null,
premium: null,
org: null
},
data: {
bodyClass: 'login-page'
}
})
.state('frontend.login.info', {
url: '^/?org&premium&email',
templateUrl: 'app/accounts/views/accountsLoginInfo.html',
data: {
pageTitle: 'Log In'
}
})
.state('frontend.login.twoFactor', {
url: '^/two-step?org&premium&email',
templateUrl: 'app/accounts/views/accountsLoginTwoFactor.html',
data: {
pageTitle: 'Log In (Two-step)'
}
})
.state('frontend.logout', {
url: '^/logout',
controller: 'accountsLogoutController',
data: {
authorize: true
}
})
.state('frontend.passwordHint', {
url: '^/password-hint',
templateUrl: 'app/accounts/views/accountsPasswordHint.html',
controller: 'accountsPasswordHintController',
data: {
pageTitle: 'Master Password Hint',
bodyClass: 'login-page'
}
})
.state('frontend.recover', {
url: '^/recover',
templateUrl: 'app/accounts/views/accountsRecover.html',
controller: 'accountsRecoverController',
data: {
pageTitle: 'Recover Account',
bodyClass: 'login-page'
}
})
.state('frontend.recover-delete', {
url: '^/recover-delete',
templateUrl: 'app/accounts/views/accountsRecoverDelete.html',
controller: 'accountsRecoverDeleteController',
data: {
pageTitle: 'Delete Account',
bodyClass: 'login-page'
}
})
.state('frontend.verify-recover-delete', {
url: '^/verify-recover-delete?userId&token&email',
templateUrl: 'app/accounts/views/accountsVerifyRecoverDelete.html',
controller: 'accountsVerifyRecoverDeleteController',
data: {
pageTitle: 'Confirm Delete Account',
bodyClass: 'login-page'
}
})
.state('frontend.register', {
url: '^/register?org&premium',
templateUrl: 'app/accounts/views/accountsRegister.html',
controller: 'accountsRegisterController',
params: {
returnState: null,
email: null,
org: null,
premium: null
},
data: {
pageTitle: 'Register',
bodyClass: 'register-page'
}
})
.state('frontend.organizationAccept', {
url: '^/accept-organization?organizationId&organizationUserId&token&email&organizationName',
templateUrl: 'app/accounts/views/accountsOrganizationAccept.html',
controller: 'accountsOrganizationAcceptController',
data: {
pageTitle: 'Accept Organization Invite',
bodyClass: 'login-page',
skipAuthorize: true
}
})
.state('frontend.verifyEmail', {
url: '^/verify-email?userId&token',
templateUrl: 'app/accounts/views/accountsVerifyEmail.html',
controller: 'accountsVerifyEmailController',
data: {
pageTitle: 'Verifying Email',
bodyClass: 'login-page',
skipAuthorize: true
}
});
})
.run(function ($rootScope, authService, $state) {
$rootScope.$on('$stateChangeSuccess', function () {
$('html, body').animate({ scrollTop: 0 }, 200);
});
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
if (!toState.data || !toState.data.authorize) {
if (toState.data && toState.data.skipAuthorize) {
return;
}
if (!authService.isAuthenticated()) {
return;
}
event.preventDefault();
$state.go('backend.user.vault');
return;
}
if (!authService.isAuthenticated()) {
event.preventDefault();
authService.logOut();
$state.go('frontend.login.info');
return;
}
// user is guaranteed to be authenticated becuase of previous check
if (toState.name.indexOf('backend.org.') > -1 && toParams.orgId) {
// clear vault rootScope when visiting org admin section
$rootScope.vaultCiphers = $rootScope.vaultFolders = null;
authService.getUserProfile().then(function (profile) {
var orgs = profile.organizations;
if (!orgs || !(toParams.orgId in orgs) || orgs[toParams.orgId].status !== 2 ||
orgs[toParams.orgId].type === 2) {
event.preventDefault();
$state.go('backend.user.vault');
}
});
}
});
});

View File

@@ -1,147 +0,0 @@
angular.module('bit')
.constant('constants', {
rememberedEmailCookieName: 'bit.rememberedEmail',
encType: {
AesCbc256_B64: 0,
AesCbc128_HmacSha256_B64: 1,
AesCbc256_HmacSha256_B64: 2,
Rsa2048_OaepSha256_B64: 3,
Rsa2048_OaepSha1_B64: 4,
Rsa2048_OaepSha256_HmacSha256_B64: 5,
Rsa2048_OaepSha1_HmacSha256_B64: 6
},
orgUserType: {
owner: 0,
admin: 1,
user: 2
},
orgUserStatus: {
invited: 0,
accepted: 1,
confirmed: 2
},
twoFactorProvider: {
u2f: 4,
yubikey: 3,
duo: 2,
authenticator: 0,
email: 1,
remember: 5
},
cipherType: {
login: 1,
secureNote: 2,
card: 3,
identity: 4
},
fieldType: {
text: 0,
hidden: 1,
boolean: 2
},
twoFactorProviderInfo: [
{
type: 0,
name: 'Authenticator App',
description: 'Use an authenticator app (such as Authy or Google Authenticator) to generate time-based ' +
'verification codes.',
enabled: false,
active: true,
free: true,
image: 'authapp.png',
displayOrder: 0,
priority: 1,
requiresUsb: false
},
{
type: 3,
name: 'YubiKey OTP Security Key',
description: 'Use a YubiKey to access your account. Works with YubiKey 4, 4 Nano, 4C, and NEO devices.',
enabled: false,
active: true,
image: 'yubico.png',
displayOrder: 1,
priority: 3,
requiresUsb: true
},
{
type: 2,
name: 'Duo',
description: 'Verify with Duo Security using the Duo Mobile app, SMS, phone call, or U2F security key.',
enabled: false,
active: true,
image: 'duo.png',
displayOrder: 2,
priority: 2,
requiresUsb: false
},
{
type: 4,
name: 'FIDO U2F Security Key',
description: 'Use any FIDO U2F enabled security key to access your account.',
enabled: false,
active: true,
image: 'fido.png',
displayOrder: 3,
priority: 4,
requiresUsb: true
},
{
type: 1,
name: 'Email',
description: 'Verification codes will be emailed to you.',
enabled: false,
active: true,
free: true,
image: 'gmail.png',
displayOrder: 4,
priority: 0,
requiresUsb: false
}
],
plans: {
free: {
basePrice: 0,
noAdditionalSeats: true,
noPayment: true,
upgradeSortOrder: -1
},
families: {
basePrice: 1,
annualBasePrice: 12,
baseSeats: 5,
noAdditionalSeats: true,
annualPlanType: 'familiesAnnually',
upgradeSortOrder: 1
},
teams: {
basePrice: 5,
annualBasePrice: 60,
monthlyBasePrice: 8,
baseSeats: 5,
seatPrice: 2,
annualSeatPrice: 24,
monthlySeatPrice: 2.5,
monthPlanType: 'teamsMonthly',
annualPlanType: 'teamsAnnually',
upgradeSortOrder: 2
},
enterprise: {
seatPrice: 3,
annualSeatPrice: 36,
monthlySeatPrice: 4,
monthPlanType: 'enterpriseMonthly',
annualPlanType: 'enterpriseAnnually',
upgradeSortOrder: 3
}
},
storageGb: {
price: 0.33,
monthlyPrice: 0.50,
yearlyPrice: 4
},
premium: {
price: 10,
yearlyPrice: 10
}
});

View File

@@ -1,30 +0,0 @@
angular
.module('bit.directives')
.directive('apiField', function () {
var linkFn = function (scope, element, attrs, ngModel) {
ngModel.$registerApiError = registerApiError;
ngModel.$validators.apiValidate = apiValidator;
function apiValidator() {
ngModel.$setValidity('api', true);
return true;
}
function registerApiError() {
ngModel.$setValidity('api', false);
}
};
return {
require: 'ngModel',
restrict: 'A',
compile: function (elem, attrs) {
if (!attrs.name || attrs.name === '') {
throw 'api-field element does not have a valid name attribute';
}
return linkFn;
}
};
});

View File

@@ -1,45 +0,0 @@
angular
.module('bit.directives')
.directive('apiForm', function ($rootScope, validationService, $timeout) {
return {
require: 'form',
restrict: 'A',
link: function (scope, element, attrs, formCtrl) {
var watchPromise = attrs.apiForm || null;
if (watchPromise !== void 0) {
scope.$watch(watchPromise, formSubmitted.bind(null, formCtrl, scope));
}
}
};
function formSubmitted(form, scope, promise) {
if (!promise || !promise.then) {
return;
}
// reset errors
form.$errors = null;
// start loading
form.$loading = true;
promise.then(function success(response) {
$timeout(function () {
form.$loading = false;
});
}, function failure(reason) {
$timeout(function () {
form.$loading = false;
if (typeof reason === 'string') {
validationService.addError(form, null, reason, true);
}
else {
validationService.addErrors(form, reason);
}
scope.$broadcast('show-errors-check-validity');
$('html, body').animate({ scrollTop: 0 }, 200);
});
});
}
});

View File

@@ -1,2 +0,0 @@
angular
.module('bit.directives', []);

View File

@@ -1,11 +0,0 @@
angular
.module('bit.directives')
.directive('fallbackSrc', function () {
return function (scope, element, attrs) {
var el = $(element);
el.bind('error', function (event) {
el.attr('src', attrs.fallbackSrc);
});
};
});

View File

@@ -1,151 +0,0 @@
angular
.module('bit.directives')
// adaptation of https://github.com/uttesh/ngletteravatar
.directive('letterAvatar', function () {
// ref: http://stackoverflow.com/a/16348977/1090359
function stringToColor(str) {
var hash = 0,
i = 0;
for (i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
var color = '#';
for (i = 0; i < 3; i++) {
var value = (hash >> (i * 8)) & 0xFF;
color += ('00' + value.toString(16)).substr(-2);
}
return color;
}
function getFirstLetters(data, count) {
var parts = data.split(' ');
if (parts && parts.length > 1) {
var text = '';
for (var i = 0; i < count; i++) {
text += parts[i].substr(0, 1);
}
return text;
}
return null;
}
function getSvg(width, height, color) {
var svgTag = angular.element('<svg></svg>')
.attr({
'xmlns': 'http://www.w3.org/2000/svg',
'pointer-events': 'none',
'width': width,
'height': height
})
.css({
'background-color': color,
'width': width + 'px',
'height': height + 'px'
});
return svgTag;
}
function getCharText(character, textColor, fontFamily, fontWeight, fontsize) {
var textTag = angular.element('<text text-anchor="middle"></text>')
.attr({
'y': '50%',
'x': '50%',
'dy': '0.35em',
'pointer-events': 'auto',
'fill': textColor,
'font-family': fontFamily
})
.text(character)
.css({
'font-weight': fontWeight,
'font-size': fontsize + 'px',
});
return textTag;
}
return {
restrict: 'AE',
replace: true,
scope: {
data: '@'
},
link: function (scope, element, attrs) {
var params = {
charCount: attrs.charcount || 2,
data: attrs.data,
textColor: attrs.textcolor || '#ffffff',
bgColor: attrs.bgcolor,
height: attrs.avheight || 45,
width: attrs.avwidth || 45,
fontSize: attrs.fontsize || 20,
fontWeight: attrs.fontweight || 300,
fontFamily: attrs.fontfamily || 'Open Sans, HelveticaNeue-Light, Helvetica Neue Light, ' +
'Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif',
round: attrs.round || 'true',
dynamic: attrs.dynamic || 'true',
class: attrs.avclass || '',
border: attrs.avborder || 'false',
borderStyle: attrs.borderstyle || '3px solid white'
};
if (params.dynamic === 'true') {
scope.$watch('data', function () {
generateLetterAvatar();
});
}
else {
generateLetterAvatar();
}
function generateLetterAvatar() {
var c = null,
upperData = scope.data.toUpperCase();
if (params.charCount > 1) {
c = getFirstLetters(upperData, params.charCount);
}
if (!c) {
c = upperData.substr(0, params.charCount);
}
var cobj = getCharText(c, params.textColor, params.fontFamily, params.fontWeight, params.fontSize);
var color = params.bgColor ? params.bgColor : stringToColor(upperData);
var svg = getSvg(params.width, params.height, color);
svg.append(cobj);
var lvcomponent = angular.element('<div>').append(svg).html();
var svgHtml = window.btoa(unescape(encodeURIComponent(lvcomponent)));
var src = 'data:image/svg+xml;base64,' + svgHtml;
var img = angular.element('<img>').attr({ src: src, title: scope.data });
if (params.round === 'true') {
img.css('border-radius', '50%');
}
if (params.border === 'true') {
img.css('border', params.borderStyle);
}
if (params.class) {
img.addClass(params.class);
}
if (params.dynamic === 'true') {
element.empty();
element.append(img);
}
else {
element.replaceWith(img);
}
}
}
};
});

View File

@@ -1,38 +0,0 @@
angular
.module('bit.directives')
.directive('masterPassword', function (cryptoService, authService) {
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, elem, attr, ngModel) {
authService.getUserProfile().then(function (profile) {
// For DOM -> model validation
ngModel.$parsers.unshift(function (value) {
if (!value) {
return undefined;
}
return cryptoService.makeKey(value, profile.email).then(function (result) {
var valid = result.keyB64 === cryptoService.getKey().keyB64;
ngModel.$setValidity('masterPassword', valid);
return valid ? value : undefined;
});
});
// For model -> DOM validation
ngModel.$formatters.unshift(function (value) {
if (!value) {
return undefined;
}
return cryptoService.makeKey(value, profile.email).then(function (result) {
var valid = result.keyB64 === cryptoService.getKey().keyB64;
ngModel.$setValidity('masterPassword', valid);
return value;
});
});
});
}
};
});

View File

@@ -1,22 +0,0 @@
angular
.module('bit.directives')
.directive('pageTitle', function ($rootScope, $timeout, appSettings) {
return {
link: function (scope, element) {
var listener = function (event, toState, toParams, fromState, fromParams) {
// Default title
var title = 'bitwarden Web Vault';
if (toState.data && toState.data.pageTitle) {
title = toState.data.pageTitle + ' - ' + title;
}
$timeout(function () {
element.text(title);
});
};
$rootScope.$on('$stateChangeStart', listener);
}
};
});

View File

@@ -1,73 +0,0 @@
angular
.module('bit.directives')
.directive('passwordMeter', function () {
return {
template: '<div class="progress {{outerClass}}"><div class="progress-bar progress-bar-{{valueClass}}" ' +
'role="progressbar" aria-valuenow="{{value}}" aria-valuemin="0" aria-valuemax="100" ' +
'ng-style="{width : ( value + \'%\' ) }"><span class="sr-only">{{value}}%</span></div></div>',
restrict: 'A',
scope: {
password: '=passwordMeter',
username: '=passwordMeterUsername',
outerClass: '@?'
},
link: function (scope) {
var measureStrength = function (username, password) {
if (!password || password === username) {
return 0;
}
var strength = password.length;
if (username && username !== '') {
if (username.indexOf(password) !== -1) strength -= 15;
if (password.indexOf(username) !== -1) strength -= username.length;
}
if (password.length > 0 && password.length <= 4) strength += password.length;
else if (password.length >= 5 && password.length <= 7) strength += 6;
else if (password.length >= 8 && password.length <= 15) strength += 12;
else if (password.length >= 16) strength += 18;
if (password.match(/[a-z]/)) strength += 1;
if (password.match(/[A-Z]/)) strength += 5;
if (password.match(/\d/)) strength += 5;
if (password.match(/.*\d.*\d.*\d/)) strength += 5;
if (password.match(/[!,@,#,$,%,^,&,*,?,_,~]/)) strength += 5;
if (password.match(/.*[!,@,#,$,%,^,&,*,?,_,~].*[!,@,#,$,%,^,&,*,?,_,~]/)) strength += 5;
if (password.match(/(?=.*[a-z])(?=.*[A-Z])/)) strength += 2;
if (password.match(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/)) strength += 2;
if (password.match(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!,@,#,$,%,^,&,*,?,_,~])/)) strength += 2;
strength = Math.round(strength * 2);
return Math.max(0, Math.min(100, strength));
};
var getClass = function (strength) {
switch (Math.round(strength / 33)) {
case 0:
case 1:
return 'danger';
case 2:
return 'warning';
case 3:
return 'success';
}
};
var updateMeter = function (scope) {
scope.value = measureStrength(scope.username, scope.password);
scope.valueClass = getClass(scope.value);
};
scope.$watch('password', function () {
updateMeter(scope);
});
scope.$watch('username', function () {
updateMeter(scope);
});
},
};
});

View File

@@ -1,27 +0,0 @@
angular
.module('bit.directives')
.directive('passwordViewer', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
var passwordViewer = attr.passwordViewer;
if (!passwordViewer) {
return;
}
element.onclick = function (event) { };
element.on('click', function (event) {
var passwordElement = $(passwordViewer);
if (passwordElement && passwordElement.attr('type') === 'password') {
element.removeClass('fa-eye').addClass('fa-eye-slash');
passwordElement.attr('type', 'text');
}
else if (passwordElement && passwordElement.attr('type') === 'text') {
element.removeClass('fa-eye-slash').addClass('fa-eye');
passwordElement.attr('type', 'password');
}
});
}
};
});

View File

@@ -1,11 +0,0 @@
angular
.module('bit.directives')
// ref: https://stackoverflow.com/a/14165848/1090359
.directive('stopClick', function () {
return function (scope, element, attrs) {
$(element).click(function (event) {
event.preventDefault();
});
};
});

View File

@@ -1,10 +0,0 @@
angular
.module('bit.directives')
.directive('stopProp', function () {
return function (scope, element, attrs) {
$(element).click(function (event) {
event.stopPropagation();
});
};
});

View File

@@ -1,193 +0,0 @@
angular
.module('bit.directives')
.directive('totp', function ($timeout, $q) {
return {
template: '<div class="totp{{(low ? \' low\' : \'\')}}" ng-if="code">' +
'<span class="totp-countdown"><span class="totp-sec">{{sec}}</span>' +
'<svg><g><circle class="totp-circle inner" r="12.6" cy="16" cx="16" style="stroke-dashoffset: {{dash}}px;"></circle>' +
'<circle class="totp-circle outer" r="14" cy="16" cx="16"></circle></g></svg></span>' +
'<span class="totp-code" id="totp-code">{{codeFormatted}}</span>' +
'<a href="#" stop-click class="btn btn-link" ngclipboard ngclipboard-error="clipboardError(e)" ' +
'data-clipboard-text="{{code}}" uib-tooltip="Copy Code" tooltip-placement="right">' +
'<i class="fa fa-clipboard"></i></a>' +
'</div>',
restrict: 'A',
scope: {
key: '=totp'
},
link: function (scope) {
var interval = null;
var Totp = function () {
var b32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
var leftpad = function (s, l, p) {
if (l + 1 >= s.length) {
s = Array(l + 1 - s.length).join(p) + s;
}
return s;
};
var dec2hex = function (d) {
return (d < 15.5 ? '0' : '') + Math.round(d).toString(16);
};
var hex2dec = function (s) {
return parseInt(s, 16);
};
var hex2bytes = function (s) {
var bytes = new Uint8Array(s.length / 2);
for (var i = 0; i < s.length; i += 2) {
bytes[i / 2] = parseInt(s.substr(i, 2), 16);
}
return bytes;
};
var buff2hex = function (buff) {
var bytes = new Uint8Array(buff);
var hex = [];
for (var i = 0; i < bytes.length; i++) {
hex.push((bytes[i] >>> 4).toString(16));
hex.push((bytes[i] & 0xF).toString(16));
}
return hex.join('');
};
var b32tohex = function (s) {
s = s.toUpperCase();
var cleanedInput = '';
var i;
for (i = 0; i < s.length; i++) {
if (b32Chars.indexOf(s[i]) < 0) {
continue;
}
cleanedInput += s[i];
}
s = cleanedInput;
var bits = '';
var hex = '';
for (i = 0; i < s.length; i++) {
var byteIndex = b32Chars.indexOf(s.charAt(i));
if (byteIndex < 0) {
continue;
}
bits += leftpad(byteIndex.toString(2), 5, '0');
}
for (i = 0; i + 4 <= bits.length; i += 4) {
var chunk = bits.substr(i, 4);
hex = hex + parseInt(chunk, 2).toString(16);
}
return hex;
};
var b32tobytes = function (s) {
return hex2bytes(b32tohex(s));
};
var sign = function (keyBytes, timeBytes) {
return window.crypto.subtle.importKey('raw', keyBytes,
{ name: 'HMAC', hash: { name: 'SHA-1' } }, false, ['sign']).then(function (key) {
return window.crypto.subtle.sign({ name: 'HMAC', hash: { name: 'SHA-1' } }, key, timeBytes);
}).then(function (signature) {
return buff2hex(signature);
}).catch(function (err) {
return null;
});
};
this.getCode = function (keyb32) {
var epoch = Math.round(new Date().getTime() / 1000.0);
var timeHex = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0');
var timeBytes = hex2bytes(timeHex);
var keyBytes = b32tobytes(keyb32);
if (!keyBytes.length || !timeBytes.length) {
return $q(function (resolve, reject) {
resolve(null);
});
}
return sign(keyBytes, timeBytes).then(function (hashHex) {
if (!hashHex) {
return null;
}
var offset = hex2dec(hashHex.substring(hashHex.length - 1));
var otp = (hex2dec(hashHex.substr(offset * 2, 8)) & hex2dec('7fffffff')) + '';
otp = (otp).substr(otp.length - 6, 6);
return otp;
});
};
};
var totp = new Totp();
var updateCode = function (scope) {
totp.getCode(scope.key).then(function (code) {
$timeout(function () {
if (code) {
scope.codeFormatted = code.substring(0, 3) + ' ' + code.substring(3);
scope.code = code;
}
else {
scope.code = null;
if (interval) {
clearInterval(interval);
}
}
});
});
};
var tick = function (scope) {
$timeout(function () {
var epoch = Math.round(new Date().getTime() / 1000.0);
var mod = epoch % 30;
var sec = 30 - mod;
scope.sec = sec;
scope.dash = (2.62 * mod).toFixed(2);
scope.low = sec <= 7;
if (mod === 0) {
updateCode(scope);
}
});
};
scope.$watch('key', function () {
if (!scope.key) {
scope.code = null;
if (interval) {
clearInterval(interval);
}
return;
}
updateCode(scope);
tick(scope);
if (interval) {
clearInterval(interval);
}
interval = setInterval(function () {
tick(scope);
}, 1000);
});
scope.$on('$destroy', function () {
if (interval) {
clearInterval(interval);
}
});
scope.clipboardError = function (e) {
alert('Your web browser does not support easy clipboard copying.');
};
},
};
});

12
src/app/dummy.module.ts Normal file
View File

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

View File

@@ -1,32 +0,0 @@
angular
.module('bit.filters')
.filter('enumLabelClass', function () {
return function (input, name) {
if (typeof input !== 'number') {
return input.toString();
}
var output;
switch (name) {
case 'OrgUserStatus':
switch (input) {
case 0:
output = 'label-default';
break;
case 1:
output = 'label-warning';
break;
case 2:
/* falls through */
default:
output = 'label-success';
}
break;
default:
output = 'label-default';
}
return output;
};
});

View File

@@ -1,46 +0,0 @@
angular
.module('bit.filters')
.filter('enumName', function () {
return function (input, name) {
if (typeof input !== 'number') {
return input.toString();
}
var output;
switch (name) {
case 'OrgUserStatus':
switch (input) {
case 0:
output = 'Invited';
break;
case 1:
output = 'Accepted';
break;
case 2:
/* falls through */
default:
output = 'Confirmed';
}
break;
case 'OrgUserType':
switch (input) {
case 0:
output = 'Owner';
break;
case 1:
output = 'Admin';
break;
case 2:
/* falls through */
default:
output = 'User';
}
break;
default:
output = input.toString();
}
return output;
};
});

View File

@@ -1,2 +0,0 @@
angular
.module('bit.filters', []);

View File

@@ -1,6 +0,0 @@
angular
.module('bit.global')
.controller('appsController', function ($scope, $state) {
});

View File

@@ -1,2 +0,0 @@
angular
.module('bit.global', []);

View File

@@ -1,187 +0,0 @@
angular
.module('bit.global')
.controller('mainController', function ($scope, $state, authService, appSettings, toastr, $window, $document,
cryptoService, $uibModal, apiService) {
var vm = this;
vm.skinClass = appSettings.selfHosted ? 'skin-blue-light' : 'skin-blue';
vm.bodyClass = '';
vm.usingControlSidebar = vm.openControlSidebar = false;
vm.searchVaultText = null;
vm.version = appSettings.version;
vm.outdatedBrowser = $window.navigator.userAgent.indexOf('MSIE') !== -1 ||
$window.navigator.userAgent.indexOf('SamsungBrowser') !== -1;
$scope.currentYear = new Date().getFullYear();
$scope.$on('$viewContentLoaded', function () {
authService.getUserProfile().then(function (profile) {
vm.userProfile = profile;
});
if ($.AdminLTE) {
if ($.AdminLTE.layout) {
$.AdminLTE.layout.fix();
$.AdminLTE.layout.fixSidebar();
}
if ($.AdminLTE.pushMenu) {
$.AdminLTE.pushMenu.expandOnHover();
}
$document.off('click', '.sidebar li a');
}
});
$scope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
vm.usingEncKey = !!cryptoService.getEncKey();
vm.searchVaultText = null;
if (toState.data.bodyClass) {
vm.bodyClass = toState.data.bodyClass;
return;
}
else {
vm.bodyClass = '';
}
vm.usingControlSidebar = !!toState.data.controlSidebar;
vm.openControlSidebar = vm.usingControlSidebar && $document.width() > 768;
});
$scope.addCipher = function () {
$scope.$broadcast('vaultAddCipher');
};
$scope.addFolder = function () {
$scope.$broadcast('vaultAddFolder');
};
$scope.addOrganizationCipher = function () {
$scope.$broadcast('organizationVaultAddCipher');
};
$scope.addOrganizationCollection = function () {
$scope.$broadcast('organizationCollectionsAdd');
};
$scope.inviteOrganizationUser = function () {
$scope.$broadcast('organizationPeopleInvite');
};
$scope.addOrganizationGroup = function () {
$scope.$broadcast('organizationGroupsAdd');
};
$scope.updateKey = function () {
$uibModal.open({
animation: true,
templateUrl: 'app/settings/views/settingsUpdateKey.html',
controller: 'settingsUpdateKeyController'
});
};
$scope.verifyEmail = function () {
if ($scope.sendingVerify) {
return;
}
$scope.sendingVerify = true;
apiService.accounts.verifyEmail({}, null).$promise.then(function () {
toastr.success('Verification email sent.');
$scope.sendingVerify = false;
$scope.verifyEmailSent = true;
}).catch(function () {
toastr.success('Verification email failed.');
$scope.sendingVerify = false;
});
};
$scope.updateBrowser = function () {
$window.open('https://browser-update.org/update.html', '_blank');
};
// Append dropdown menu somewhere else
var bodyScrollbarWidth,
appendedDropdownMenu,
appendedDropdownMenuParent;
var dropdownHelpers = {
scrollbarWidth: function () {
if (!bodyScrollbarWidth) {
var bodyElem = $('body');
bodyElem.addClass('bit-position-body-scrollbar-measure');
bodyScrollbarWidth = $window.innerWidth - bodyElem[0].clientWidth;
bodyScrollbarWidth = isFinite(bodyScrollbarWidth) ? bodyScrollbarWidth : 0;
bodyElem.removeClass('bit-position-body-scrollbar-measure');
}
return bodyScrollbarWidth;
},
scrollbarInfo: function () {
return {
width: dropdownHelpers.scrollbarWidth(),
visible: $document.height() > $($window).height()
};
}
};
$(window).on('show.bs.dropdown', function (e) {
/*jshint -W120 */
var target = appendedDropdownMenuParent = $(e.target);
var appendTo = target.data('appendTo');
if (!appendTo) {
return true;
}
appendedDropdownMenu = target.find('.dropdown-menu');
var appendToEl = $(appendTo);
appendToEl.append(appendedDropdownMenu.detach());
var offset = target.offset();
var css = {
display: 'block',
top: offset.top + target.outerHeight() - (appendTo !== 'body' ? $(window).scrollTop() : 0)
};
if (appendedDropdownMenu.hasClass('dropdown-menu-right')) {
var scrollbarInfo = dropdownHelpers.scrollbarInfo();
var scrollbarWidth = 0;
if (scrollbarInfo.visible && scrollbarInfo.width) {
scrollbarWidth = scrollbarInfo.width;
}
css.right = $window.innerWidth - scrollbarWidth - (offset.left + target.prop('offsetWidth')) + 'px';
css.left = 'auto';
}
else {
css.left = offset.left + 'px';
css.right = 'auto';
}
appendedDropdownMenu.css(css);
});
$(window).on('hide.bs.dropdown', function (e) {
if (!appendedDropdownMenu) {
return true;
}
$(e.target).append(appendedDropdownMenu.detach());
appendedDropdownMenu.hide();
appendedDropdownMenu = null;
appendedDropdownMenuParent = null;
});
$scope.$on('removeAppendedDropdownMenu', function (event, args) {
if (!appendedDropdownMenu && !appendedDropdownMenuParent) {
return true;
}
appendedDropdownMenuParent.append(appendedDropdownMenu.detach());
appendedDropdownMenu.hide();
appendedDropdownMenu = null;
appendedDropdownMenuParent = null;
});
});

View File

@@ -1,26 +0,0 @@
angular
.module('bit.global')
.controller('paidOrgRequiredController', function ($scope, $state, $uibModalInstance, $analytics, $uibModalStack, orgId,
constants, authService) {
$analytics.eventTrack('paidOrgRequiredController', { category: 'Modal' });
authService.getUserProfile().then(function (profile) {
$scope.admin = profile.organizations[orgId].type !== constants.orgUserType.user;
});
$scope.go = function () {
if (!$scope.admin) {
return;
}
$analytics.eventTrack('Get Paid Org');
$state.go('backend.org.billing', { orgId: orgId }).then(function () {
$uibModalStack.dismissAll();
});
};
$scope.close = function () {
$uibModalInstance.dismiss('close');
};
});

View File

@@ -1,17 +0,0 @@
angular
.module('bit.global')
.controller('premiumRequiredController', function ($scope, $state, $uibModalInstance, $analytics, $uibModalStack) {
$analytics.eventTrack('premiumRequiredController', { category: 'Modal' });
$scope.go = function () {
$analytics.eventTrack('Get Premium');
$state.go('backend.user.settingsPremium').then(function () {
$uibModalStack.dismissAll();
});
};
$scope.close = function () {
$uibModalInstance.dismiss('close');
};
});

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