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

Compare commits

...

579 Commits

Author SHA1 Message Date
Kyle Spearrin
c134986bbf version bump 2017-09-12 22:32:37 -04:00
Kyle Spearrin
d9981e1d71 cleaned providers should be an obj, not array 2017-09-09 12:25:35 -04:00
Kyle Spearrin
2b6d7ec361 org import from lastpass 2017-09-06 10:50:05 -04:00
Kyle Spearrin
aaa91e50b7 org export/import 2017-09-06 09:05:53 -04:00
Kyle Spearrin
ff9030e7af disable autocomplete on verification code input 2017-09-04 23:10:31 -04:00
Kyle Spearrin
cc39e6402e version bump 2017-09-01 14:17:40 -04:00
Kyle Spearrin
c89b641b88 default collection on org create 2017-08-30 21:27:04 -04:00
Kyle Spearrin
465304b004 only show selected collection that are writeable 2017-08-30 17:09:22 -04:00
Kyle Spearrin
63033ca12d pull only writable collections when editing 2017-08-30 15:58:51 -04:00
Kyle Spearrin
f019dc6575 lint fix 2017-08-30 15:06:24 -04:00
Kyle Spearrin
d15e3a64e7 update libs 2017-08-30 15:04:05 -04:00
Kyle Spearrin
7099b0579a named args to server 2017-08-25 11:00:19 -04:00
Kyle Spearrin
2c2d08c7cc make sure key is generated on self host create 2017-08-22 08:37:07 -04:00
Kyle Spearrin
671e9ccb1c script fixes 2017-08-19 22:36:09 -04:00
Kyle Spearrin
f93c5cb9a1 finalize create properly 2017-08-17 00:57:25 -04:00
Kyle Spearrin
8c7f1c4359 copy updates 2017-08-16 15:18:30 -04:00
Kyle Spearrin
d7c1c6efa1 can only edit org when not self hosted 2017-08-16 14:08:11 -04:00
Kyle Spearrin
30a2301697 prompt for installation id and download license 2017-08-15 16:18:31 -04:00
Kyle Spearrin
c639186c60 correct billing icon 2017-08-15 15:37:59 -04:00
Kyle Spearrin
5618cfb031 use btiwarden kestrel server isntead of node 2017-08-15 11:57:04 -04:00
Kyle Spearrin
7e97c04d1e web vault page title 2017-08-15 10:12:08 -04:00
Kyle Spearrin
4d25077108 more preprocessing for self host 2017-08-15 10:05:39 -04:00
Kyle Spearrin
635caa9ad0 preprocess dist for self hosted 2017-08-15 09:16:19 -04:00
Kyle Spearrin
2772bffd09 qr code size and clean token on delete 2017-08-15 08:24:14 -04:00
Kyle Spearrin
995fc96a5d create and mange org through licensing 2017-08-14 22:06:51 -04:00
Kyle Spearrin
4660ad824d on premise feature on enterprise list 2017-08-14 13:13:39 -04:00
Kyle Spearrin
801049cbd0 billing & licensing 2017-08-14 13:08:48 -04:00
Kyle Spearrin
09a7b4ea90 billing license management when self hosted 2017-08-14 12:10:00 -04:00
Kyle Spearrin
226c201925 bank account payment method for orgs 2017-08-14 10:21:08 -04:00
Kyle Spearrin
4749a3da89 import 1password fields even if no name 2017-08-12 12:14:59 -04:00
Kyle Spearrin
ae567ab462 import totp keys from 1password 1pif export 2017-08-12 12:06:00 -04:00
Kyle Spearrin
bf382889d3 enpass import TOTP field resolves #8 2017-08-11 23:31:48 -04:00
Kyle Spearrin
2272bcac71 licensing options when self hosted 2017-08-11 23:23:14 -04:00
Kyle Spearrin
a209c9450a delete recovery token apis 2017-08-10 10:15:10 -04:00
Kyle Spearrin
2539a9c23f account recovery with delete 2017-08-09 10:44:49 -04:00
Kyle Spearrin
e95ede73ba fix bug with password going into username field 2017-08-09 08:24:16 -04:00
Kyle Spearrin
ad970b1cb7 dockerignore 2017-08-08 17:50:48 -04:00
Kyle Spearrin
161e7d1763 copy app-id.json for u2f 2017-08-08 00:44:58 -04:00
Kyle Spearrin
3a823d32b5 copy appsettings on entrypoint 2017-08-08 00:03:10 -04:00
Kyle Spearrin
4c46317f24 extension appsettings with runtime loadable props 2017-08-07 21:08:15 -04:00
Kyle Spearrin
0271c223a6 false dir listing command 2017-08-07 17:17:00 -04:00
Kyle Spearrin
9a4669067d docker image 2017-08-07 17:07:56 -04:00
Kyle Spearrin
53f3124345 paypal option 2017-08-04 13:11:25 -04:00
Kyle Spearrin
b49a40b077 unhide paypal option with braintree 2017-08-04 13:09:34 -04:00
Kyle Spearrin
fb10da8ce3 terms links 2017-08-04 11:43:21 -04:00
Kyle Spearrin
b286c1a29b version bump 2017-08-01 00:14:09 -04:00
Kyle Spearrin
e5e7712716 catch decryption failure on login previews 2017-08-01 00:13:10 -04:00
Kyle Spearrin
2beb22e8cf added error logs for decrypt methods 2017-07-31 23:19:02 -04:00
Kyle Spearrin
747b5608e8 re-worked change password, email, and update key 2017-07-31 22:53:27 -04:00
Kyle Spearrin
dad3cd9414 add samsung to unsupported browsers 2017-07-31 13:24:58 -04:00
Kyle Spearrin
0c1fb3e118 catch and throw proper stripe error message 2017-07-29 16:44:21 -04:00
Kyle Spearrin
afe223f410 version bump 2017-07-28 21:26:10 -04:00
Kyle Spearrin
e1ec50bcad hide paypal until ready 2017-07-28 21:16:33 -04:00
Kyle Spearrin
04da844b22 radio styling 2017-07-28 21:13:03 -04:00
Kyle Spearrin
f944910975 error handling for no payment method 2017-07-28 16:44:36 -04:00
Kyle Spearrin
96b8467859 support for paypal through braintree 2017-07-28 14:29:25 -04:00
Kyle Spearrin
84554174ac fix attachments for org edit 2017-07-27 22:14:42 -04:00
Kyle Spearrin
65e03e707c new duo path 2017-07-26 13:32:17 -04:00
Kyle Spearrin
fd9fcbea38 validation summary on payment 2017-07-26 10:12:20 -04:00
Kyle Spearrin
a1dfd7493a premium check updates 2017-07-26 10:07:12 -04:00
Kyle Spearrin
d4759d4056 fixes 2017-07-26 09:35:30 -04:00
Kyle Spearrin
d879518233 typo 2017-07-26 00:31:57 -04:00
Kyle Spearrin
ef6cb3779b local duo for iframe fixes 2017-07-25 22:54:08 -04:00
Kyle Spearrin
fc22114855 version bump 2017-07-25 22:39:17 -04:00
Kyle Spearrin
6b1eb5a479 cancellation notices 2017-07-25 15:53:17 -04:00
Kyle Spearrin
bbd8a1265b attachments for shared view 2017-07-25 15:45:52 -04:00
Kyle Spearrin
444f63db42 callback whenever closing modal 2017-07-25 15:00:20 -04:00
Kyle Spearrin
f46a6aefea update enc key article 2017-07-25 08:55:01 -04:00
Kyle Spearrin
10792f714e focus master password field on load 2017-07-24 12:08:21 -04:00
Kyle Spearrin
d6d535ed9e stop listening for u2f on destroy 2017-07-24 12:02:57 -04:00
Kyle Spearrin
55a50fac83 timeout when trying u2f again 2017-07-24 11:52:31 -04:00
Kyle Spearrin
a7beed334f u2f fixes and mobile filter for 2fa methods 2017-07-24 11:48:19 -04:00
Kyle Spearrin
83274ad7a4 duo lib should be copied 2017-07-24 11:10:31 -04:00
Kyle Spearrin
24056163dd premium required for attachments 2017-07-21 17:14:40 -04:00
Kyle Spearrin
79383ed693 limitations 2017-07-15 11:03:13 -04:00
Kyle Spearrin
d2da3f6e00 use better monospace font for code 2017-07-14 22:31:53 -04:00
Kyle Spearrin
c40193c861 no config in u2f build 2017-07-14 15:52:27 -04:00
Kyle Spearrin
715835c12f lint fixes 2017-07-14 14:32:26 -04:00
Kyle Spearrin
0242de9145 new preview repo 2017-07-14 14:32:26 -04:00
Kyle Spearrin
b075f25d7c add params for two-factor page 2017-07-14 14:32:26 -04:00
Kyle Spearrin
0b34b7a980 Update README.md 2017-07-14 08:32:58 -04:00
Kyle Spearrin
f291b24a7a Update README.md 2017-07-14 08:32:30 -04:00
Kyle Spearrin
9707fa34e4 login returnState conditions 2017-07-13 22:28:52 -04:00
Kyle Spearrin
cd19e0c9e4 totp code updates 2017-07-13 14:45:57 -04:00
Kyle Spearrin
38883b9550 add totp to import/export 2017-07-13 11:22:16 -04:00
Kyle Spearrin
f761733d0b Show file after upload and reset input 2017-07-12 14:17:21 -04:00
Kyle Spearrin
842b157955 provide callback functions 2017-07-12 10:57:17 -04:00
Kyle Spearrin
87f0e2be0e cleanup 2017-07-12 10:00:36 -04:00
Kyle Spearrin
c3bea80ec7 gnome importer 2017-07-11 12:05:44 -04:00
Kyle Spearrin
a1529bc4e9 change payment for premium 2017-07-11 11:17:43 -04:00
Kyle Spearrin
ccb7ede4fa storage percentage fix 2017-07-11 11:05:19 -04:00
Kyle Spearrin
1dbf831bda storage adjustment 2017-07-11 10:59:49 -04:00
Kyle Spearrin
ea4d772dda storage for org billing & signup 2017-07-11 10:24:46 -04:00
Kyle Spearrin
25536e10ef toasts and error handling 2017-07-10 23:16:34 -04:00
Kyle Spearrin
51e30b2f7a capture attachment in closure 2017-07-10 16:21:39 -04:00
Kyle Spearrin
47cb20f01e share login with attachments 2017-07-10 14:30:33 -04:00
Kyle Spearrin
204ee72926 outdated browser and edge checks for pbkdf2 2017-07-09 00:23:26 -04:00
Kyle Spearrin
b9cbc1546c undefined checks 2017-07-08 23:48:08 -04:00
Kyle Spearrin
bc8892a237 move pbkdf2 to web crypto with shim fallback 2017-07-08 23:41:02 -04:00
Kyle Spearrin
b62950fa2b IE fixes and crypto shims 2017-07-08 00:12:57 -04:00
Kyle Spearrin
ab12c990bc offset scroll 2017-07-07 16:15:40 -04:00
Kyle Spearrin
abed4df973 attachments for org logins 2017-07-07 15:43:24 -04:00
Kyle Spearrin
76da9b1f18 dont copy formatted code 2017-07-07 14:25:08 -04:00
Kyle Spearrin
11cbe3b7bb allow totp if from an org with totp 2017-07-07 14:16:15 -04:00
Kyle Spearrin
08b432775e totp flag on logins 2017-07-07 14:07:30 -04:00
Kyle Spearrin
49dbf4945f totp access for orgs 2017-07-07 12:12:08 -04:00
Kyle Spearrin
ff729608e1 delete attachments 2017-07-07 10:58:51 -04:00
Kyle Spearrin
b380d723b7 UI adjustments for premium adverts 2017-07-07 09:11:45 -04:00
Kyle Spearrin
ed13644a02 totp generator directive 2017-07-07 00:13:26 -04:00
Kyle Spearrin
8a90f562ef add field for totp to login 2017-07-06 21:22:06 -04:00
Kyle Spearrin
dfd791ecf9 premium required messages 2017-07-06 16:15:28 -04:00
Kyle Spearrin
8df16f28e7 premium signup and billing settings pages 2017-07-06 15:00:04 -04:00
Kyle Spearrin
1fb220c25e attachment errors 2017-07-05 16:27:28 -04:00
Kyle Spearrin
b24f892f60 verify email 2017-07-05 15:36:40 -04:00
Kyle Spearrin
5d81ed6a96 update key and verify email notification 2017-07-01 22:44:10 -04:00
Kyle Spearrin
7ff79a0fdd download and decrypt attachments 2017-06-30 22:34:26 -04:00
Kyle Spearrin
7b4cf53ec4 encrypt, upload, and view attachments 2017-06-30 16:22:39 -04:00
Kyle Spearrin
9c7b47c277 rename to duo-connector 2017-06-29 14:56:54 -04:00
Kyle Spearrin
547c7b8b70 nfc flag for yubi and duo mobile page 2017-06-29 12:35:10 -04:00
Kyle Spearrin
1d70434ed1 urls for appid 2017-06-27 14:49:39 -04:00
Kyle Spearrin
06d53d350d app id to json extension 2017-06-27 13:51:32 -04:00
Kyle Spearrin
742d7240f7 android facet 2017-06-27 13:49:39 -04:00
Kyle Spearrin
9b3ca76934 fido app id 2017-06-27 12:26:53 -04:00
Kyle Spearrin
9f1c445214 not supported scenario 2017-06-27 09:04:51 -04:00
Kyle Spearrin
075ba931ea added recovery code option to methods 2017-06-27 08:30:58 -04:00
Kyle Spearrin
29cbe48eb5 lint fixes 2017-06-27 08:26:00 -04:00
Kyle Spearrin
be1cc945a2 enabled fix 2017-06-27 08:23:00 -04:00
Kyle Spearrin
3e61d938bc token sanitization and adjust timeouts on u2f 2017-06-27 08:14:03 -04:00
Kyle Spearrin
0ee928cdce u2f connector updates 2017-06-26 23:52:49 -04:00
Kyle Spearrin
5d87fae906 U2f support 2017-06-26 15:52:50 -04:00
Kyle Spearrin
afcc5ceb5b adjust priorities 2017-06-26 15:32:34 -04:00
Kyle Spearrin
74d8e595f2 u2f connector frame 2017-06-26 14:49:20 -04:00
Kyle Spearrin
bc988181f9 update messages 2017-06-24 17:20:27 -04:00
Kyle Spearrin
1030654ce2 android with NFC 2017-06-24 17:15:36 -04:00
Kyle Spearrin
1c25143a75 platform warnings 2017-06-24 17:12:10 -04:00
Kyle Spearrin
39281811f5 recovery code 2017-06-24 16:59:01 -04:00
Kyle Spearrin
2f07d22a9e touch it 2017-06-24 15:49:45 -04:00
Kyle Spearrin
1d1b9706ce show redacted email 2017-06-24 11:55:39 -04:00
Kyle Spearrin
7a19d444f1 update 2fa setup pages 2017-06-24 11:26:24 -04:00
Kyle Spearrin
73eb743f54 2fa cleanup 2017-06-24 10:49:53 -04:00
Kyle Spearrin
181ee74ba3 email 2fa login 2017-06-24 09:19:04 -04:00
Kyle Spearrin
b8e9567501 u2f cleanup 2017-06-23 16:31:55 -04:00
Kyle Spearrin
dda64b301e 2fa cleanup 2017-06-23 12:39:56 -04:00
Kyle Spearrin
af56551fd2 remember two factor 2017-06-23 10:41:57 -04:00
Kyle Spearrin
c55d0449cb fido u2f login flow 2017-06-22 23:16:02 -04:00
Kyle Spearrin
0135476b68 configure u2f device 2017-06-22 17:02:24 -04:00
Kyle Spearrin
e366b7c7a7 u2f api 2017-06-21 22:47:42 -04:00
Kyle Spearrin
ca9a0b072e duo 2fa config and login with web sdk 2017-06-21 15:17:44 -04:00
Kyle Spearrin
2f3035a08f 2fa method selection 2017-06-20 17:06:14 -04:00
Kyle Spearrin
cf5b0635e4 Yubikey 2fa setup 2017-06-20 14:00:55 -04:00
Kyle Spearrin
4db5c96781 send key with auth app setup 2017-06-20 10:12:18 -04:00
Kyle Spearrin
e49948b512 two factor email setup 2017-06-20 09:21:53 -04:00
Kyle Spearrin
1298d42b09 login 2017-06-19 22:33:12 -04:00
Kyle Spearrin
00e74dd2c8 new two-factor management page 2017-06-19 22:26:57 -04:00
Kyle Spearrin
10fe79c558 stubbed out new two-step settings page 2017-06-19 15:29:33 -04:00
Kyle Spearrin
cddabebe86 lint fix 2017-06-19 10:23:50 -04:00
Kyle Spearrin
9a7dac706c sign rsa "me" encrypted data with enc key 2017-06-19 10:00:42 -04:00
Kyle Spearrin
2e2998bb8b cdn integrity checks 2017-06-17 10:14:44 -04:00
Kyle Spearrin
ce1352cb9f remove inline fallback code for CSP 2017-06-17 10:08:47 -04:00
Kyle Spearrin
00007c20a7 verison bump 2017-06-16 15:35:12 -04:00
Kyle Spearrin
cdaf3cb428 ux improvements for bulk actions 2017-06-09 12:30:47 -04:00
Kyle Spearrin
d640bb5a04 small modal 2017-06-09 12:05:22 -04:00
Kyle Spearrin
b1ebcb76f0 bulk move logins 2017-06-09 09:46:25 -04:00
Kyle Spearrin
488dbb6715 toggle checkboxes by clicking whole cell 2017-06-09 01:10:53 -04:00
Kyle Spearrin
f170157817 bulk actions with move and delete 2017-06-09 00:44:56 -04:00
Kyle Spearrin
c094a26cbf copy password from vault listings 2017-06-08 22:25:01 -04:00
Kyle Spearrin
366506555a lint fixes 2017-06-07 21:19:37 -04:00
Kyle Spearrin
9eb4043595 csp adjustments for angular 2017-06-07 21:18:29 -04:00
Kyle Spearrin
3359e78047 stop click directive to prevent CSP errors 2017-06-07 19:01:27 -04:00
Kyle Spearrin
7ebafaf0fc Content-Security-Policy 2017-06-07 17:03:23 -04:00
Kyle Spearrin
fadd070663 control sidebar adjustments 2017-06-06 12:18:43 -04:00
Kyle Spearrin
27d291b0e9 min height control sidebar 2017-06-05 23:38:31 -04:00
Kyle Spearrin
f07f58733c fix layout after filtering 2017-06-05 11:19:01 -04:00
Kyle Spearrin
b5521425ae folder icon. remove tags 2017-06-05 10:46:56 -04:00
Kyle Spearrin
b191ecd29e control sidebar for vault with filters 2017-06-05 10:38:37 -04:00
Kyle Spearrin
5989918300 try again 2017-05-31 16:25:22 -04:00
Kyle Spearrin
f5720cf20e new change email api with enc key 2017-05-31 16:16:21 -04:00
Kyle Spearrin
2106e48e0e move updateKey to cipher service for re-use 2017-05-31 14:49:18 -04:00
Kyle Spearrin
1dd9e459c6 change password with new enc key 2017-05-31 12:21:06 -04:00
Kyle Spearrin
138b57b33d always add header to ciphers on encrypt 2017-05-31 11:06:57 -04:00
Kyle Spearrin
3845c55155 generate enc key on registration 2017-05-31 11:05:52 -04:00
Kyle Spearrin
9aa2014e85 crypto adjustments for new account enc key 2017-05-31 10:25:25 -04:00
Kyle Spearrin
9239588757 recover help article 2017-05-25 23:37:09 -04:00
Kyle Spearrin
5904b269e7 version bump 2017-05-25 18:29:35 -04:00
Kyle Spearrin
9bf3e31d6f no paragraph 2017-05-25 18:29:11 -04:00
Kyle Spearrin
618cb07ead move reports to their own module 2017-05-25 18:22:19 -04:00
Kyle Spearrin
1e3a39defc data breach report. resolves #53 2017-05-25 17:41:29 -04:00
Kyle Spearrin
0aab548b87 new import article urls 2017-05-23 16:43:51 -04:00
Kyle Spearrin
489b93d5df fix lint errors 2017-05-20 08:55:43 -04:00
Kyle Spearrin
cfb2a4d404 version bump 2017-05-20 08:55:04 -04:00
Kyle Spearrin
3b8ad132bc ui adjustments 2017-05-19 20:33:13 -04:00
Kyle Spearrin
8510711e5d organize import dropdown. added opera and vivaldi 2017-05-19 16:03:39 -04:00
Kyle Spearrin
9918e903b2 add support for passkeep csv import 2017-05-19 14:10:45 -04:00
Kyle Spearrin
6a292d6905 Update README.md 2017-05-19 13:27:22 -04:00
Kyle Spearrin
62926d6e28 Update SECURITY.md 2017-05-19 12:03:37 -04:00
Kyle Spearrin
51edf80e48 allow bulk invite CSV list of email addresses 2017-05-18 12:19:49 -04:00
Kyle Spearrin
804f1f5610 meldium importer resolves #68 2017-05-17 16:20:22 -04:00
Kyle Spearrin
3f0b14e48a Create SECURITY.md 2017-05-17 11:34:51 -04:00
Kyle Spearrin
3e0ce5544c primary worker for forge key generation 2017-05-15 20:58:16 -04:00
Kyle Spearrin
933cbb72aa manage external ids 2017-05-15 14:42:24 -04:00
Kyle Spearrin
6bda5d5983 collection user refactor 2017-05-11 14:52:51 -04:00
Kyle Spearrin
bfae8e7def collection add/edit groups 2017-05-11 12:22:03 -04:00
Kyle Spearrin
96a91b97e9 cleanup and model changes 2017-05-11 10:32:39 -04:00
Kyle Spearrin
12096a8fb3 space out the icon a bit 2017-05-10 14:33:48 -04:00
Kyle Spearrin
e03d4d52c4 resolve issues with id on api calls 2017-05-10 14:20:45 -04:00
Kyle Spearrin
ea24d72f01 group accessall and readonly 2017-05-10 12:17:26 -04:00
Kyle Spearrin
a4473ad739 catch refresh token error 2017-05-10 11:47:53 -04:00
Kyle Spearrin
08c28950f4 dashlane importer fix for 6 cols 2017-05-10 11:37:27 -04:00
Kyle Spearrin
5cc8439f5b dont scroll to top with # on click resolves #62 2017-05-10 07:58:51 -04:00
Kyle Spearrin
eb7fd4a015 conditionally show groups option 2017-05-09 20:35:18 -04:00
Kyle Spearrin
dce609d141 no need to clean up card 2017-05-09 19:28:12 -04:00
Kyle Spearrin
f31360ecbf remove user from group 2017-05-09 19:23:49 -04:00
Kyle Spearrin
93e88d8b23 group user assignment 2017-05-09 19:04:26 -04:00
Kyle Spearrin
816cc0b17b occurred typo 2017-05-09 14:23:39 -04:00
Kyle Spearrin
1f73269480 manage groups from collection add/edit 2017-05-09 14:06:44 -04:00
Kyle Spearrin
f7d1b8821c ui tweaks 2017-05-08 22:18:07 -04:00
Kyle Spearrin
cd5ad9f85b select collections on group add/edit 2017-05-08 22:13:31 -04:00
Kyle Spearrin
9c706f07f0 groups list/add/edit 2017-05-08 16:01:36 -04:00
Kyle Spearrin
ea82925e14 new props for org profile 2017-05-08 15:28:40 -04:00
Kyle Spearrin
1c5f208ef1 enterprise plan signup 2017-05-08 15:20:01 -04:00
Kyle Spearrin
aeae0ba535 stripe key in app settings 2017-05-08 14:45:14 -04:00
Kyle Spearrin
f59b227c44 version bump 2017-05-08 12:39:46 -04:00
Kyle Spearrin
4518e7056c fixed to collection sharing. observe login edit. 2017-05-08 11:36:11 -04:00
Kyle Spearrin
565c6bafae version bump 2017-05-08 08:15:39 -04:00
Kyle Spearrin
584e8131cd version bump 2017-05-06 21:32:56 -04:00
Kyle Spearrin
20e958b1ee new identity server uri for auth 2017-05-06 21:32:51 -04:00
Kyle Spearrin
21ca3abc7e importer fixes for ipif and safe in cloud 2017-05-04 15:56:45 -04:00
Kyle Spearrin
612ad32722 update forge 2017-05-04 00:13:01 -04:00
Kyle Spearrin
8ec07266b9 trimleft on first lastpass chunk 2017-05-03 14:48:29 -04:00
Kyle Spearrin
a9a7b0b317 typo on export 2017-05-03 11:47:09 -04:00
Kyle Spearrin
e634e3e28f change stripe key to live 2017-05-03 10:34:23 -04:00
Kyle Spearrin
86de4b721f callout when registering for org create 2017-05-03 10:23:01 -04:00
Kyle Spearrin
1d95a78e75 payment page UI updates 2017-04-28 21:50:08 -04:00
Kyle Spearrin
f5e44163be style sweaks 2017-04-28 21:39:16 -04:00
Kyle Spearrin
1ffc005479 adjusted warning color to be darker 2017-04-28 21:36:03 -04:00
Kyle Spearrin
31f67d412b Two-step login UI tweaks 2017-04-28 21:31:57 -04:00
Kyle Spearrin
cc62237ab5 UI/UX tweaks 2017-04-28 15:28:00 -04:00
Kyle Spearrin
f11d4a92df notes 2017-04-27 16:40:45 -04:00
Kyle Spearrin
0be6249c2b shared bugs 2017-04-27 16:34:04 -04:00
Kyle Spearrin
a083fc9084 user vault collections changed to show all shared 2017-04-27 16:24:38 -04:00
Kyle Spearrin
54172c441f rename AccessAllCollections => AccessAll 2017-04-27 15:39:24 -04:00
Kyle Spearrin
b5f8b1014e add/edit logins from org admin vault 2017-04-27 14:47:44 -04:00
Kyle Spearrin
df42c6176d comment update 2017-04-27 12:14:11 -04:00
Kyle Spearrin
7d0a34fceb protect mac comparisons from timing attacks 2017-04-27 12:00:32 -04:00
Kyle Spearrin
b3e94b13f7 constant time equality for mac check on decrypt 2017-04-27 11:35:30 -04:00
Kyle Spearrin
4eee908f2f subvault => collections file renames 2017-04-27 09:35:21 -04:00
Kyle Spearrin
1ebae5c284 rename subvault => collection 2017-04-27 09:33:12 -04:00
Kyle Spearrin
361f03eb5f remove audits controller ref 2017-04-26 10:39:34 -04:00
Kyle Spearrin
d8f54fc15a telemetry for organizations 2017-04-26 10:32:14 -04:00
Kyle Spearrin
90b0f3201e telemetry events 2017-04-26 10:21:06 -04:00
Kyle Spearrin
b0d2374960 misc cleanup 2017-04-25 16:26:25 -04:00
Kyle Spearrin
5c471e43dd return state for org create on register/login 2017-04-25 10:46:54 -04:00
Kyle Spearrin
c69169cbf9 rename CryptoKey to SymmetricCryptoKey 2017-04-22 14:39:40 -04:00
Kyle Spearrin
f2c670dfd0 whitelist desktop IP 2017-04-21 22:40:21 -04:00
Kyle Spearrin
cfdd6dc0d9 Clear selected subvaults when changing orgs 2017-04-21 16:02:46 -04:00
Kyle Spearrin
d61b6c2faa force vault refresh upon importing 2017-04-21 14:24:24 -04:00
Kyle Spearrin
e010995b19 Add support for OAEP SHA1 digest.
Note that iOS does not support any other OAEP format, such as SHA256.
2017-04-21 13:46:07 -04:00
Kyle Spearrin
053a1c1394 arrange icons better 2017-04-20 23:58:38 -04:00
Kyle Spearrin
581184e2ae wording update 2017-04-20 23:55:07 -04:00
Kyle Spearrin
84e617b201 list details about user w/ access to all subvaults 2017-04-20 23:49:33 -04:00
Kyle Spearrin
4ba21638b1 access all subvaults option for org users 2017-04-20 22:19:18 -04:00
Kyle Spearrin
f92c5a214f crypto fix for mac 2017-04-20 16:32:03 -04:00
Kyle Spearrin
180101400f groups pages 2017-04-20 16:31:52 -04:00
Kyle Spearrin
ede10677f9 includeShared for backwards compat APIs 2017-04-19 17:03:47 -04:00
Kyle Spearrin
7627601ff8 handle legacy encrypt-then-mac scheme 2017-04-19 16:45:16 -04:00
Kyle Spearrin
cb120d2e75 opt out of backwards compat folder ciphers 2017-04-19 16:44:21 -04:00
Kyle Spearrin
ec86ccd956 org block styling 2017-04-19 13:56:11 -04:00
Kyle Spearrin
63a657cac5 encrypt key bytes when confirming, not object 2017-04-19 11:21:58 -04:00
Kyle Spearrin
c3eb6bb972 check that chunks has length 2017-04-19 10:10:27 -04:00
Kyle Spearrin
eab5c0db12 org admin delete cipher 2017-04-19 10:06:59 -04:00
Kyle Spearrin
0b9083915a remove login from individual subvault 2017-04-19 09:57:47 -04:00
Kyle Spearrin
051703234c cleanup crypto API 2017-04-19 09:27:38 -04:00
Kyle Spearrin
6d555bcf84 fix lint errors 2017-04-19 09:03:47 -04:00
Kyle Spearrin
d99fcd8e59 fix promise on register 2017-04-18 22:58:14 -04:00
Kyle Spearrin
04eee919e8 preview domain adjustments 2017-04-18 22:56:41 -04:00
Kyle Spearrin
0926c82878 wrap key into new CryptoKey object 2017-04-18 22:28:49 -04:00
Kyle Spearrin
79744d89ce constants for orguser type/status 2017-04-18 20:40:17 -04:00
Kyle Spearrin
214274f495 track by on repeats 2017-04-18 15:34:16 -04:00
Kyle Spearrin
2425eb0ff8 whitelist preview api url 2017-04-18 14:10:03 -04:00
Kyle Spearrin
c8931cde6e gulp fix for env 2017-04-18 14:01:28 -04:00
Kyle Spearrin
af698c7628 adjust configs 2017-04-18 13:54:46 -04:00
Kyle Spearrin
52745993cb preview deploy fix 2017-04-18 12:51:11 -04:00
Kyle Spearrin
7a8d23ba84 rework configs to accomedate preview env 2017-04-18 12:33:21 -04:00
Kyle Spearrin
34559f0dbd re-org menu 2017-04-18 12:10:06 -04:00
Kyle Spearrin
b34a205ace proper count for org subvaults 2017-04-18 12:05:51 -04:00
Kyle Spearrin
799fbeba72 cleanup styles and pluralize vault counts 2017-04-18 12:03:06 -04:00
Kyle Spearrin
0e36abe1ad of 2017-04-18 11:53:21 -04:00
Kyle Spearrin
69ce07ef01 no org callout on sidebar 2017-04-18 11:52:44 -04:00
Kyle Spearrin
9f32e76a99 clear vault rootScope when visiting org admin 2017-04-18 11:31:43 -04:00
Kyle Spearrin
e89e48014c manage root scope from subvault list edits 2017-04-18 11:27:44 -04:00
Kyle Spearrin
9863a95a71 root scope bug fixes 2017-04-18 10:45:35 -04:00
Kyle Spearrin
dc0bf54401 org existance check 2017-04-18 10:24:47 -04:00
Kyle Spearrin
3728f012d7 make dropdown append more generic 2017-04-18 10:19:42 -04:00
Kyle Spearrin
f904558315 manage cipher subvaults from org admin 2017-04-17 23:11:24 -04:00
Kyle Spearrin
a79556dfce org vault listing 2017-04-17 17:01:12 -04:00
Kyle Spearrin
901332dbee change from deprecated sites endpoint to logins 2017-04-17 15:48:02 -04:00
Kyle Spearrin
1ab75115f0 filter out org logins 2017-04-17 15:47:24 -04:00
Kyle Spearrin
bc431b896b change email/password adjustments 2017-04-17 14:53:26 -04:00
Kyle Spearrin
aa7a3c442c adjust vault login chunking 2017-04-15 01:02:56 -04:00
Kyle Spearrin
6825967cb9 domain rules style updates 2017-04-15 01:00:25 -04:00
Kyle Spearrin
cdc06a2b49 convert listings from uib-tooltip to title 2017-04-14 23:48:51 -04:00
Kyle Spearrin
309c73a972 update org after share 2017-04-14 23:36:43 -04:00
Kyle Spearrin
c4a3e5c4fd body dropdown tweaks 2017-04-14 23:30:58 -04:00
Kyle Spearrin
8d6cbe8e1e append dropdown menus to body 2017-04-14 22:49:51 -04:00
Kyle Spearrin
ff4e76b723 convert dropdowns back to regular bootstrap 2017-04-14 22:37:41 -04:00
Kyle Spearrin
acdbc6b9a3 undo comments 2017-04-14 14:37:36 -04:00
Kyle Spearrin
6714390890 clear root scope vault data on logout 2017-04-14 12:38:44 -04:00
Kyle Spearrin
249d00b285 cache vault data in root scope 2017-04-14 12:35:46 -04:00
Kyle Spearrin
e4ffdf6815 promisify makekeypair and generate keys on login 2017-04-13 18:18:32 -04:00
Kyle Spearrin
2228263b9f remove orderby on fav list 2017-04-13 17:25:02 -04:00
Kyle Spearrin
ee1c884ef1 load vault in chunks so that it appears faster 2017-04-13 17:19:54 -04:00
Kyle Spearrin
0d29c75e7f handle null condition when decrypting 2017-04-13 11:53:07 -04:00
Kyle Spearrin
7042f4bca8 labels in nav 2017-04-13 10:39:11 -04:00
Kyle Spearrin
ea42ed5381 move apps menu item up one 2017-04-13 10:12:48 -04:00
Kyle Spearrin
ba6ca4a6bb lowercase the 2017-04-13 10:10:46 -04:00
Kyle Spearrin
ce68c1599f apps page 2017-04-13 10:09:19 -04:00
Kyle Spearrin
ce64601e38 ui tweaks 2017-04-12 21:58:36 -04:00
Kyle Spearrin
b9f6351720 import bitwarden fav fix 2017-04-12 16:47:53 -04:00
Kyle Spearrin
da8b31533a export data fixes due to api cahnges 2017-04-12 16:41:31 -04:00
Kyle Spearrin
0591f106d3 syntax fixes 2017-04-12 16:14:29 -04:00
Kyle Spearrin
40f9961541 export and import favorites for bitwarden csv 2017-04-12 16:12:28 -04:00
Kyle Spearrin
5c8117539c add back exposify package for gulp build 2017-04-12 15:55:26 -04:00
Kyle Spearrin
af7400642b password gen message 2017-04-12 13:28:11 -04:00
Kyle Spearrin
f8c5f31f97 org owner check on side nav menu 2017-04-12 13:06:18 -04:00
Kyle Spearrin
5f2c2a8064 copy updates 2017-04-12 13:01:38 -04:00
Kyle Spearrin
08aa53748e manage subvaults for login in vault 2017-04-12 12:41:43 -04:00
Kyle Spearrin
673485b5c4 fix card scope 2017-04-12 11:16:14 -04:00
Kyle Spearrin
18bea7edb2 updates to change payment form 2017-04-12 11:13:41 -04:00
Kyle Spearrin
cdf029bc84 fix null check on subvault management 2017-04-12 11:11:01 -04:00
Kyle Spearrin
31ce92fa9d info text on invite 2017-04-12 11:01:03 -04:00
Kyle Spearrin
f6b1666cd7 leave organization 2017-04-12 10:07:16 -04:00
Kyle Spearrin
5f130bdda7 notes about sharing 2017-04-11 17:29:45 -04:00
Kyle Spearrin
d619167c02 disabled org labeling 2017-04-11 15:56:57 -04:00
Kyle Spearrin
400932c6de refresh access token after creating org 2017-04-11 15:00:53 -04:00
Kyle Spearrin
8984ec3127 change plan modal and adjust seat callouts 2017-04-11 14:26:17 -04:00
Kyle Spearrin
02076fadf4 some styling on org create form 2017-04-11 13:05:17 -04:00
Kyle Spearrin
1d93d5c687 show errors on payment form page 2017-04-11 12:27:03 -04:00
Kyle Spearrin
5f028ea65f delete organization 2017-04-11 10:52:16 -04:00
Kyle Spearrin
cf22ea2b78 move some values to constants for better sharing 2017-04-10 18:55:18 -04:00
Kyle Spearrin
58df3e692b rename to reinstate 2017-04-10 18:31:01 -04:00
Kyle Spearrin
80ca89b3f6 cancel/uncancel sub 2017-04-10 16:43:24 -04:00
Kyle Spearrin
4209d91c43 obj change fix 2017-04-10 12:45:46 -04:00
Kyle Spearrin
79b878209d revert settings commit 2017-04-10 12:30:16 -04:00
Kyle Spearrin
24cbe13ca7 billing seat adjustments 2017-04-10 12:29:06 -04:00
Kyle Spearrin
f8fcbbea85 change payment 2017-04-10 11:30:23 -04:00
Kyle Spearrin
40d38ec0db users => seats 2017-04-10 10:43:18 -04:00
Kyle Spearrin
f63f4e0aa3 change payment method for org 2017-04-08 16:42:05 -04:00
Kyle Spearrin
d4b4c7bd71 max additional users for personal plan 2017-04-08 11:05:32 -04:00
Kyle Spearrin
bdef522da7 org create styling 2017-04-07 16:13:52 -04:00
Kyle Spearrin
bb1ba1dbc4 move finalizeCreate to scope of shareKey 2017-04-07 15:09:09 -04:00
Kyle Spearrin
2b880d322a use ngif so that form elements are not on page 2017-04-07 14:15:11 -04:00
Kyle Spearrin
60f62b2b50 set teams plan when business is checked 2017-04-07 13:54:03 -04:00
Kyle Spearrin
b11d7be990 fix subvault collapse and add org plan details 2017-04-07 13:50:34 -04:00
Kyle Spearrin
05d153e1d2 org styling 2017-04-07 12:50:56 -04:00
Kyle Spearrin
eaba45369b org create desc and page scroll on state changes 2017-04-07 12:39:52 -04:00
Kyle Spearrin
71adf31f7b org create form on it's own page instead of modal 2017-04-07 12:32:15 -04:00
Kyle Spearrin
d39d49fb8f create org form styling 2017-04-07 11:39:56 -04:00
Kyle Spearrin
7c91066618 turn off enc header until all clients are updated 2017-04-07 09:26:43 -04:00
Kyle Spearrin
57116c4f54 added encType header to ciphers 2017-04-06 23:00:33 -04:00
Kyle Spearrin
80e4d2329a org settings and billing 2017-04-06 16:52:25 -04:00
Kyle Spearrin
7591843220 stub out org billing 2017-04-06 13:13:54 -04:00
Kyle Spearrin
653afe9f8b stub out org settings 2017-04-06 13:10:43 -04:00
Kyle Spearrin
8f007a70db dropdown options and iconography for subvaults 2017-04-06 11:00:53 -04:00
Kyle Spearrin
0feea6091b subvault messages when sharing 2017-04-06 10:24:15 -04:00
Kyle Spearrin
b27b4bef44 border options for avatars 2017-04-06 00:00:04 -04:00
Kyle Spearrin
2798a05e8e avatar tweaks. sidebar org avatars 2017-04-05 23:53:17 -04:00
Kyle Spearrin
fe039f7b35 custom letter avatar directive 2017-04-05 23:20:51 -04:00
Kyle Spearrin
ea5dc4b7fc remove gravatar for letter avatars #4 2017-04-05 17:59:48 -04:00
Kyle Spearrin
acc214d7c1 refactor to remove deprecated apis 2017-04-05 16:14:52 -04:00
Kyle Spearrin
83c232ecb5 edit logins from subvaults page 2017-04-05 11:37:22 -04:00
Kyle Spearrin
157875f7d5 use checkboxes for subvault selection 2017-04-04 22:08:04 -04:00
Kyle Spearrin
ef00e57f72 load cipher subvaults 2017-04-04 17:21:47 -04:00
Kyle Spearrin
8098ab50e8 organization signup plan details 2017-04-04 12:57:31 -04:00
Kyle Spearrin
ebb1044c43 cc details on org create 2017-04-04 10:14:54 -04:00
Kyle Spearrin
751935e90b persist folder/subvault collapse 2017-04-03 14:07:39 -04:00
Kyle Spearrin
a81572914a Manage subvault users 2017-04-03 12:26:43 -04:00
Kyle Spearrin
e00f033ffd resolve lint errors 2017-04-03 09:30:21 -04:00
Kyle Spearrin
bf9414199c subvault list UI updates 2017-04-01 22:17:28 -04:00
Kyle Spearrin
3011e9a804 use uib-dropdowns 2017-04-01 10:26:33 -04:00
Kyle Spearrin
a678f03284 button groups for vault 2017-03-30 23:49:35 -04:00
Kyle Spearrin
11002c2881 enum filters and org accept state 2017-03-30 22:06:01 -04:00
Kyle Spearrin
2692bbaa63 subvault operations 2017-03-30 21:08:07 -04:00
Kyle Spearrin
1db6d7f32b import via textarea 2017-03-30 00:07:26 -04:00
Kyle Spearrin
61cce7e8e7 subvault listing search and edit subvault 2017-03-29 22:23:00 -04:00
Kyle Spearrin
616a442fcb handle errors in org people edit 2017-03-29 21:26:48 -04:00
Kyle Spearrin
916519a43a org name from mail invite link 2017-03-29 20:58:27 -04:00
Kyle Spearrin
af2f7a7a5a organization listing from side menu 2017-03-29 19:21:06 -04:00
Kyle Spearrin
9ab9fcd577 adjust table label 2017-03-29 18:59:14 -04:00
Kyle Spearrin
853d1f4cfa status label 2017-03-29 18:05:56 -04:00
Kyle Spearrin
cbcfdafef6 UI updates for org pages 2017-03-28 22:09:27 -04:00
Kyle Spearrin
b156a27d1f api form 2017-03-28 22:04:09 -04:00
Kyle Spearrin
f6ce6426f1 add search to people listing 2017-03-28 21:44:12 -04:00
Kyle Spearrin
e12582c2c2 UI tweaks for org invites 2017-03-28 21:16:44 -04:00
Kyle Spearrin
4d2cae0b0f share profile promise result when called at same
time
2017-03-27 22:22:56 -04:00
Kyle Spearrin
35e0f27f52 access control on orgs pages 2017-03-27 21:55:39 -04:00
Kyle Spearrin
77ddc83a04 check status and types for org management 2017-03-25 21:52:27 -04:00
Kyle Spearrin
3c83741b13 ui updates for vault logins list 2017-03-25 16:09:06 -04:00
Kyle Spearrin
636c709671 hide favorites box when loading 2017-03-25 15:58:39 -04:00
Kyle Spearrin
f3f1b413b7 hide favorites box when no search results 2017-03-25 15:56:43 -04:00
Kyle Spearrin
8eaad64dd6 added favorites box to top of my vault listing 2017-03-25 15:50:24 -04:00
Kyle Spearrin
f80ba6b87c share promises and readonly check 2017-03-25 11:41:06 -04:00
Kyle Spearrin
5e5e3b5359 set profile after auth logIn 2017-03-25 11:03:11 -04:00
Kyle Spearrin
19203e976b convert auth service profile methods to promises 2017-03-25 10:43:19 -04:00
Kyle Spearrin
2154607d11 revert settings 2017-03-24 16:10:22 -04:00
Kyle Spearrin
072de1ea44 readonly and partial login updates 2017-03-24 16:09:57 -04:00
Kyle Spearrin
1818dad0d1 remove sharing module. move subvaults 2017-03-23 23:01:22 -04:00
Kyle Spearrin
d51eab779c subvault listing 2017-03-23 18:10:00 -04:00
Kyle Spearrin
9f1ab6f961 accept org invite. return state for login 2017-03-23 16:58:06 -04:00
Ben Brooks
0b875fc6f7 Add link to Firefox addon (#49)
* Add link to Firefox addon

* De-localise URLs

* re-instate media type param for iOS hint
2017-03-23 14:05:00 -04:00
Kyle Spearrin
fd62938db0 fix wrong org user type id 2017-03-23 00:40:23 -04:00
Kyle Spearrin
4499ec6a22 reinvite and remove org users 2017-03-23 00:33:35 -04:00
Kyle Spearrin
dde20f4451 resolve lint errors 2017-03-21 23:07:53 -04:00
Kyle Spearrin
715b91ab96 update all the things 2017-03-21 23:07:53 -04:00
Kyle Spearrin
7d26361680 Update README.md 2017-03-21 18:12:02 -04:00
Kyle Spearrin
b85a45d8f9 Move and list ciphers from org subvaults 2017-03-21 00:05:20 -04:00
Kyle Spearrin
22ab5d334e load folders from it's api 2017-03-18 22:55:54 -04:00
Kyle Spearrin
acf124c81e re-stub frontend sharing center 2017-03-16 22:44:54 -04:00
Kyle Spearrin
51d81dea9f manage user type 2017-03-13 23:31:01 -04:00
Kyle Spearrin
4a6066bb88 user vault associations 2017-03-13 22:54:57 -04:00
Kyle Spearrin
6ece16ccc9 org people subvault selection 2017-03-11 23:02:43 -05:00
Kyle Spearrin
0acab61f2e add new org to profile 2017-03-11 20:46:33 -05:00
Kyle Spearrin
1cbd322105 back to port 4001 2017-03-11 19:51:28 -05:00
Kyle Spearrin
ed9d26fd1b serialize private key to pkcs8 format 2017-03-10 20:49:50 -05:00
Kyle Spearrin
14e290c489 org key fixes 2017-03-09 22:28:14 -05:00
Kyle Spearrin
429b2b8a21 add subvault 2017-03-09 22:08:47 -05:00
Kyle Spearrin
e7707c4826 Set private key from asn1 on initial set 2017-03-09 20:59:10 -05:00
Kyle Spearrin
290cbe6b55 list subvaults for org 2017-03-07 23:05:49 -05:00
Kyle Spearrin
d5708f24e6 settings caret 2017-03-07 00:41:49 -05:00
Kyle Spearrin
3d273f041e do api calls on viewContentLoaded 2017-03-07 00:36:27 -05:00
Kyle Spearrin
22299c03cd list-groups for org box listing 2017-03-07 00:19:00 -05:00
Kyle Spearrin
0ea4b4400f org keys and optimized org profile load for sidenav 2017-03-06 23:54:06 -05:00
Kyle Spearrin
b3c8337f83 routes for org subvaults 2017-03-06 23:01:08 -05:00
Kyle Spearrin
a9e85f8765 org user invites and confirmation 2017-03-04 20:41:45 -05:00
Kyle Spearrin
b36799bf0c subvaults page stubbed out 2017-03-03 22:45:10 -05:00
Kyle Spearrin
4d71a05d2a organization pages and routing 2017-03-03 21:53:02 -05:00
Kyle Spearrin
4fdf2a98bf org dashboard route 2017-03-03 19:14:14 -05:00
Kyle Spearrin
880be03211 organization signup 2017-03-03 00:07:31 -05:00
Kyle Spearrin
27495d5055 Organization profile 2017-03-02 21:51:24 -05:00
Kyle Spearrin
492e2e693c setup new organization layout within backend 2017-03-01 22:47:24 -05:00
Kyle Spearrin
05a92ebd26 remove share login modal and add organizations box 2017-02-28 23:43:54 -05:00
Kyle Spearrin
0d2e296eda lint fixes 2017-02-28 22:53:19 -05:00
Kyle Spearrin
ad25267ed7 folder options 2017-02-28 00:20:03 -05:00
Kyle Spearrin
1ed86899bb share login modal 2017-02-28 00:18:11 -05:00
Kyle Spearrin
63c136a1ff share modal 2017-02-25 23:37:42 -05:00
Kyle Spearrin
3905b2b945 beta badge 2017-02-25 22:41:42 -05:00
Kyle Spearrin
afaaf7d73a modal UI for sharing folders/logins from vault 2017-02-25 22:38:30 -05:00
Kyle Spearrin
642b35582f vault row selectable 2017-02-25 22:22:25 -05:00
Kyle Spearrin
117188769c format vault listing 2017-02-25 22:13:16 -05:00
Kyle Spearrin
bd7aad37e6 copyright update 2017-02-25 22:09:58 -05:00
Kyle Spearrin
08b4e08820 style updates 2017-02-25 21:53:39 -05:00
Kyle Spearrin
aa4f360f59 combine import/export 2017-02-25 02:51:42 -05:00
Kyle Spearrin
2420375d56 remove unused service references 2017-02-23 19:32:56 -05:00
Kyle Spearrin
bc5c738c25 rework share pages 2017-02-23 00:45:54 -05:00
Kyle Spearrin
ccc527f329 Switch vault listing to user ciphers apis instead of calling login and folder separately 2017-02-21 22:50:48 -05:00
Kyle Spearrin
cf144aa2c1 set private key when logging in 2017-02-21 00:30:00 -05:00
Kyle Spearrin
086d924f06 generate keypair on registration 2017-02-21 00:30:00 -05:00
Kyle Spearrin
24862f31b3 tab layout for sharing center 2017-02-21 00:30:00 -05:00
Kyle Spearrin
877eb4d423 setup UI pages for sharing center 2017-02-21 00:30:00 -05:00
Kyle Spearrin
a37a5fa1b5 added rsa to gulp task for forge 2017-02-21 00:30:00 -05:00
Kyle Spearrin
2478a8f3cc updates to cryptoService for rsa keypairs 2017-02-21 00:30:00 -05:00
Kyle Spearrin
3ed69d887f utf8 encode params for key derivation 2017-02-15 19:03:56 -05:00
Kyle Spearrin
f0d440d204 Move tools from side nav into tools page boxes 2017-02-14 00:41:23 -05:00
Kyle Spearrin
3e18f812db lint errors 2017-02-11 18:22:13 -05:00
Kyle Spearrin
8cf02fd59a version bump 2017-02-11 17:12:50 -05:00
Kyle Spearrin
06bfab3afa version bump 2017-02-11 17:12:09 -05:00
Kyle Spearrin
71e4697562 two factor edits 2017-02-11 17:08:06 -05:00
Kyle Spearrin
cf1bffe2f1 change email button 2017-02-11 16:48:52 -05:00
Kyle Spearrin
55a5fd49dc Moved domain rules page out from modal into it's own page 2017-02-11 16:46:24 -05:00
Kyle Spearrin
3f6637eb8f move many account settings into main settings page instead of nav menu 2017-02-11 15:44:22 -05:00
Kyle Spearrin
7373e281ac print recovery code. changed vault and login route 2017-02-11 14:21:21 -05:00
Kyle Spearrin
52b89455d7 replace sjcl cryptoservice implementation with forge 2017-02-11 13:03:48 -05:00
Kyle Spearrin
bca7592c77 production by default settings 2017-02-01 22:56:39 -05:00
Kyle Spearrin
f6ab0bfe82 version bump 2017-02-01 22:53:38 -05:00
Kyle Spearrin
012a5c491d version bump 2017-02-01 22:53:15 -05:00
Kyle Spearrin
7666d6136d updated truekey importer to their new csv format 2017-02-01 22:52:36 -05:00
Kyle Spearrin
7bdda34f14 remove old auth endpoints from apiservice 2017-01-29 21:39:38 -05:00
Kyle Spearrin
df21f89fcb lint fix 2017-01-29 21:24:32 -05:00
Kyle Spearrin
f3b4cdca8a back to enum ints for 2fa providers 2017-01-28 17:27:37 -05:00
Kyle Spearrin
52460bf47b version bump 2017-01-28 17:00:26 -05:00
Kyle Spearrin
e674e7287e token refresh 2017-01-28 16:09:38 -05:00
Kyle Spearrin
a20e8b6228 fix string split bug on 1password 1pif importer 2017-01-28 02:03:49 -05:00
Kyle Spearrin
8d50e96dab splashid importer 2017-01-28 01:57:36 -05:00
Kyle Spearrin
1fe673951b WIP convert web vault to new identity server 2017-01-28 01:19:43 -05:00
Kyle Spearrin
3df5a9454e 1password importer naming adjustments 2017-01-21 02:08:14 -05:00
Kyle Spearrin
79fecd6b03 fix some linter complaints 2017-01-20 23:39:43 -05:00
Kyle Spearrin
f3445c24b9 added example placeholder text 2017-01-10 21:54:32 -05:00
Kyle Spearrin
a3150d8505 cleanup domain add/edit submission 2017-01-10 21:53:03 -05:00
Kyle Spearrin
828b5d8703 Add/edit equivalent domains 2017-01-10 21:38:53 -05:00
Kyle Spearrin
39559e203a react to model restructure on API 2017-01-10 17:01:38 -05:00
Kyle Spearrin
605bdd0ea0 domain rules page setup with new APIs 2017-01-09 22:26:20 -05:00
Kyle Spearrin
74945e03ce linter fixes 2017-01-06 00:03:33 -05:00
Kyle Spearrin
c772502af5 version bump fix for settings 2017-01-05 23:57:45 -05:00
Kyle Spearrin
401d9db0f2 version bump 2017-01-05 23:56:59 -05:00
Kyle Spearrin
2306da94fe More instructions for firefox password exporter. 2017-01-04 22:43:22 -05:00
Kyle Spearrin
9f7ed11082 fix bug from site rename 2017-01-04 22:32:47 -05:00
Kyle Spearrin
fff0efb095 append tooltips to body 2017-01-04 22:23:21 -05:00
Kyle Spearrin
9aa61f4bca complete import options array 2017-01-04 20:39:55 -05:00
Kyle Spearrin
bd70dc5966 Update README.md 2017-01-04 00:17:24 -05:00
Kyle Spearrin
ac6a3caa8f importer instructions 2017-01-04 00:11:27 -05:00
Kyle Spearrin
0914776152 re-ordered fields on site add/edit. marked name as required with asterisk 2017-01-03 22:09:26 -05:00
Kyle Spearrin
45d0f43e90 If searching, only show folder if it has filtered logins 2017-01-03 19:19:54 -05:00
Kyle Spearrin
7264367fa3 Collapse/Expand #32 2017-01-03 19:06:50 -05:00
Kyle Spearrin
022fa34478 switch back to sites enpoint until API is updated 2017-01-03 00:35:04 -05:00
Kyle Spearrin
f7fd28fded refactored naming Site => Login 2017-01-02 22:26:32 -05:00
Kyle Spearrin
e01a22de48 importer fixes 2017-01-02 21:37:20 -05:00
Kyle Spearrin
711c8e63c1 fixes to 1password4 1pif. new uri formatter. added importers for 1password6 csv, zoho vault csv, password boss json, keepassx csv, and ascendo data vault csv. 2017-01-02 18:20:42 -05:00
Kyle Spearrin
f186ec160a saferpass csv importer 2016-12-31 16:24:11 -05:00
Kyle Spearrin
53fcfd13ee folder and site count to vault list 2016-12-31 15:18:40 -05:00
Kyle Spearrin
c684d66ec0 use reference field names on clipperz importer 2016-12-31 14:52:04 -05:00
Kyle Spearrin
6496f750b0 roboform html importer 2016-12-31 14:48:56 -05:00
Kyle Spearrin
6aaa47cccd avira json importer 2016-12-31 01:00:51 -05:00
Kyle Spearrin
1f6677d610 clipperz html importer 2016-12-31 00:38:12 -05:00
Kyle Spearrin
54b659aff0 true key json importer 2016-12-29 15:33:37 -05:00
Kyle Spearrin
b9db21309e split newline for msecure notes 2016-12-29 11:41:13 -05:00
Kyle Spearrin
8649c3b2b1 msecure csv importer 2016-12-29 02:33:37 -05:00
Kyle Spearrin
6bf6cc365b sticky password importer (#1) 2016-12-28 10:36:44 -05:00
Kyle Spearrin
7c2d5448e8 catch bad data on all importers 2016-12-27 10:36:02 -05:00
Kyle Spearrin
a9f2ef7c10 version bump 2016-12-22 01:38:43 -05:00
Kyle Spearrin
f86bce970e dashlane csv importer (#1) 2016-12-22 01:33:47 -05:00
Kyle Spearrin
6cbd618fb8 password safe importer (#1) 2016-12-21 23:30:47 -05:00
Kyle Spearrin
11787193ed Enpass csv importer (#1) 2016-12-21 22:15:37 -05:00
Kyle Spearrin
45aae6810c added opera extension link 2016-12-19 11:06:29 -05:00
Kyle Spearrin
7f6d571ef1 Clear _aesWithMac too with key clear 2016-12-10 10:34:42 -05:00
Kyle Spearrin
908dc4727c encrypt-then-mac support 2016-12-08 22:21:46 -05:00
Kyle Spearrin
264759cfa0 Version bump and CNAME dist fix 2016-12-03 00:56:41 -05:00
Kyle Spearrin
b5d265526a space between badges 2016-12-01 00:15:35 -05:00
Kyle Spearrin
22290eafb8 update readme with appveyor build badge 2016-12-01 00:14:52 -05:00
Kyle Spearrin
3101e57c36 Reorganization a bit more. Updated readme with build/run instructions. 2016-12-01 00:07:03 -05:00
Kyle Spearrin
b72a52232d reorganize project folder structure and remove asp.net dependency 2016-11-30 23:50:00 -05:00
Kyle Spearrin
a5b8e703fc added node server via gulp serve task 2016-11-30 23:30:08 -05:00
Kyle Spearrin
fb26425f17 tweaks to two factor modal 2016-11-30 23:22:25 -05:00
Kyle Spearrin
3114e20aef remove any spaces from authenticator code input 2016-11-26 18:52:46 -05:00
Kyle Spearrin
a52d2f4b7a added account recovery page 2016-11-14 23:31:54 -05:00
Kyle Spearrin
34e484c377 null check recovery code 2016-11-14 22:45:01 -05:00
Kyle Spearrin
08e8e9ff64 Add recovery code information to two-step login modal, also make verification code required for all actions 2016-11-14 22:38:02 -05:00
Kyle Spearrin
ebf55390eb Added password dragon xml importer #1 2016-11-11 19:21:12 -05:00
Kyle Spearrin
c328144a58 keeper csv import (#1) 2016-11-11 00:10:16 -05:00
Kyle Spearrin
4b583bea9b version bump - 1.4.0 2016-11-09 21:50:04 -05:00
Kyle Spearrin
3f0ca412c6 index checking 2016-11-09 21:49:15 -05:00
Kyle Spearrin
0050b570b4 Added delete option to edit site modal #25 2016-11-09 19:26:02 -05:00
Kyle Spearrin
9405be03b0 Added importer for universal password manager csv format 2016-11-09 18:18:46 -05:00
Kyle Spearrin
b5b706fe06 check badDataLength 2016-11-09 00:42:19 -05:00
Kyle Spearrin
8313d9fa90 Better error handling for imports. Check for bad CSV data on lastpass (when no header row is included). 2016-11-09 00:41:17 -05:00
Kyle Spearrin
93b96b3be7 adjusted add/edit site sort functions for folders 2016-10-22 09:08:17 -04:00
blindly
50e6818f2b Sdd site button to each folder (#17) 2016-10-22 08:43:50 -04:00
Kyle Spearrin
104fb57bd8 Added support for Firefox Password Exporter Addon import. ref #1 2016-10-21 00:10:17 -04:00
Kyle Spearrin
7ba6b2f00a version bump 2016-10-20 08:04:40 -04:00
Kyle Spearrin
d8e8939eca remove old code from 1Password 1pif importer 2016-10-20 08:03:20 -04:00
Kyle Spearrin
26c5f4049d link to help site 2016-10-19 22:52:53 -04:00
Kyle Spearrin
b6c9dba0fc Added Chrome csv importer. Adjustments to 1Password importer in preparation for detecting different formats. 2016-10-19 18:22:18 -04:00
blindly
8badc1a354 Folders should be sorted in alphabetical order when adding or editing a site. 2016-10-19 06:07:10 -04:00
Kyle Spearrin
15097eb1f0 version bump 2016-10-18 22:11:54 -04:00
Kyle Spearrin
986306f811 fix imports error 2016-10-18 21:34:08 -04:00
Kyle Spearrin
c5de290dd4 version bump 2016-10-17 23:26:20 -04:00
Kyle Spearrin
21e9e083f5 nothing was imported error 2016-10-17 23:24:16 -04:00
Kyle Spearrin
517ea65bc9 import validation 2016-10-17 23:18:19 -04:00
Kyle Spearrin
e7a9699226 show import errors as console warning to assist in debugging issues 2016-10-17 23:11:39 -04:00
Kyle Spearrin
52b3dfd0e3 set encoding for to UTF-8 for csv parser 2016-10-17 23:00:44 -04:00
Kyle Spearrin
4c0bde9d87 1.2.0 version bump 2016-10-15 12:30:20 -04:00
Kyle Spearrin
f29bbe4316 adjust password generator to use cryptographically secure RNG 2016-10-15 00:41:03 -04:00
Kyle Spearrin
3c03ed8636 trim url for safeincloud xml import 2016-10-14 23:55:02 -04:00
Kyle Spearrin
71ca4eb84a fix safeincloudcsv value in select option 2016-10-14 23:52:31 -04:00
Kyle Spearrin
30d19b4ee1 import for safeincloud xml 2016-10-14 23:50:39 -04:00
Kyle Spearrin
7b88d30aa8 lowered uri trim to 1000 2016-10-14 23:17:20 -04:00
Kyle Spearrin
0ae11cc40c import for 1password 1pif files 2016-10-14 23:16:37 -04:00
Kyle Spearrin
dd5cda867d trim ridiculously large URLs on import 2016-10-14 21:43:00 -04:00
Kyle Spearrin
2d11bef262 padlock csv import 2016-10-13 23:10:14 -04:00
Kyle Spearrin
5d5d0bfb66 gitter badge 2016-10-13 22:02:46 -04:00
Kyle Spearrin
1b169f368b version bump to 1.1.1 2016-10-13 20:06:55 -04:00
Kyle Spearrin
3375bda789 cache tag for local assets on preprocess build 2016-10-13 19:57:56 -04:00
Kyle Spearrin
6569fbe6aa adjust importers to not require Uri and Passwords 2016-10-13 19:05:53 -04:00
Kyle Spearrin
1d2b82a302 lint fixes for import service. made passwird not required for sites 2016-10-13 18:40:42 -04:00
Kyle Spearrin
004ddb1e75 handle lastpass import when users try to upload html file instead of csv 2016-10-13 00:05:18 -04:00
Kyle Spearrin
400826baf7 minor version bump to 1.1.0 2016-10-12 22:51:59 -04:00
Kyle Spearrin
96ae20d81d fix floating labels for firefox 2016-10-12 22:50:46 -04:00
Kyle Spearrin
ef5f4df30f lint errors in import service 2016-10-12 22:49:04 -04:00
Kyle Spearrin
2ce1b12f6e Handle empty password and uri fields when encrypting/decrypting 2016-10-12 22:26:28 -04:00
Kyle Spearrin
85efba92e6 added importer for keypass 2.x xml 2016-10-12 22:14:39 -04:00
Kyle Spearrin
d17211ff86 added SafeInCloud csv import 2016-10-12 19:02:36 -04:00
284 changed files with 22324 additions and 2997 deletions

3
.dockerignore Normal file
View File

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

3
.gitignore vendored
View File

@@ -199,4 +199,5 @@ FakesAssemblies/
*.opt
# Other
project.lock.json
project.lock.json
src/js/*.min.js

10
Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
FROM bitwarden/server
WORKDIR /app
COPY ./dist .
EXPOSE 80
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View File

@@ -1,9 +1,32 @@
[![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)
# bitwarden Web
The bitwarden Web project is an AngularJS application that powers the web vault (https://vault.bitwarden.com/).
<img src="https://i.imgur.com/rxrykeX.png" alt="" width="791" height="739" />
# Build/Run
**Requirements**
- Node.js
- Gulp
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`.
Then run the following commands:
- `npm install`
- `gulp build`
- `gulp serve`
You can now access the web vault at `http://localhost:4001`.
# Contribute
Code contributions are welcome! Please commit any pull requests against the `master` branch.
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature.
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.

45
SECURITY.md Normal file
View File

@@ -0,0 +1,45 @@
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!
# Disclosure Policy
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder.
- If you would like to encrypt your report, please use the PGP key with long ID
`0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
# In-scope
- 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.
# Exclusions
The following bug classes are out-of scope:
- 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
- Missing security best practices that do not directly lead to a vulnerability
- Issues that do not have any impact on the general public
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
Thank you for helping keep bitwarden and our users safe!

View File

@@ -1,36 +1,39 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
# Visual Studio 15
VisualStudioVersion = 15.0.26228.9
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{860863C9-0436-43D4-840D-FE919C9F6FFC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{14FE7221-D377-4AD5-9A9E-4541577CF05A}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
CNAME = CNAME
global.json = global.json
NuGet.Config = NuGet.Config
README.md = README.md
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
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Web", "src\Web\Web.xproj", "{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}.Release|Any CPU.Build.0 = Release|Any CPU
{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
GlobalSection(NestedProjects) = preSolution
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5} = {860863C9-0436-43D4-840D-FE919C9F6FFC}
EndGlobalSection
EndGlobal

13
build.ps1 Normal file
View File

@@ -0,0 +1,13 @@
$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\.

19
build.sh Normal file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -e
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
echo ""
echo "# Building Web"
echo ""
echo "Building app"
echo "npm version $(npm --version)"
echo "gulp version $(gulp --version)"
npm install
gulp dist:selfHosted
echo ""
echo "Building docker image"
docker --version
docker build -t bitwarden/web $DIR/.

5
entrypoint.sh Normal file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
cp /etc/bitwarden/web/settings.js /app/js/settings.js
cp /etc/bitwarden/web/app-id.json /app/app-id.json
dotnet /bitwarden_server/Server.dll /contentRoot=/app /webRoot=. /serveUnknown=false

View File

@@ -1,6 +0,0 @@
{
"projects": [ "src", "test" ],
"sdk": {
"version": "1.0.0-preview2-003121"
}
}

View File

@@ -8,28 +8,35 @@ var gulp = require('gulp'),
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('./project.json'),
project = require('./package.json'),
jshint = require('gulp-jshint'),
_ = require('lodash');
_ = require('lodash'),
webpack = require('webpack-stream'),
browserify = require('browserify'),
derequire = require('gulp-derequire'),
source = require('vinyl-source-stream');
var paths = {};
paths.dist = '../../dist/';
paths.webroot = './wwwroot/'
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 = 'less/';
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())
@@ -39,7 +46,7 @@ gulp.task('lint', function () {
gulp.task('build', function (cb) {
return runSequence(
'clean',
['lib', 'less', 'settings', 'lint'],
['browserify', 'lib', 'webpack', 'less', 'settings', 'lint', 'min:js'],
cb);
});
@@ -58,7 +65,16 @@ gulp.task('clean:lib', function (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], { base: '.' })
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('.'));
@@ -104,8 +120,8 @@ gulp.task('lib', ['clean:lib'], function () {
dest: paths.libDir + 'angular'
},
{
src: paths.npmDir + 'angular-bootstrap-npm/dist/*tpls*.js',
dest: paths.libDir + 'angular-bootstrap'
src: paths.npmDir + 'angular-ui-bootstrap/dist/*tpls*.js',
dest: paths.libDir + 'angular-ui-bootstrap'
},
{
src: paths.npmDir + 'angular-bootstrap-show-errors/src/*.js',
@@ -119,14 +135,14 @@ gulp.task('lib', ['clean:lib'], function () {
src: paths.npmDir + 'angular-jwt/dist/*.js',
dest: paths.libDir + 'angular-jwt'
},
{
src: paths.npmDir + 'angular-md5/angular-md5*.js',
dest: paths.libDir + 'angular-md5'
},
{
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'
@@ -139,10 +155,6 @@ gulp.task('lib', ['clean:lib'], function () {
src: paths.npmDir + 'angular-messages/*messages*.js',
dest: paths.libDir + 'angular-messages'
},
{
src: [paths.npmDir + 'sjcl/core/cbc.js', paths.npmDir + 'sjcl/core/bitArray.js', paths.npmDir + 'sjcl/sjcl.js'],
dest: paths.libDir + 'sjcl'
},
{
src: paths.npmDir + 'ngstorage/*.js',
dest: paths.libDir + 'ngstorage'
@@ -159,12 +171,28 @@ gulp.task('lib', ['clean:lib'], function () {
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'
}
];
@@ -175,6 +203,34 @@ gulp.task('lib', ['clean:lib'], function () {
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'));
@@ -186,10 +242,11 @@ function config() {
createModule: false,
constants: _.merge({}, {
appSettings: {
selfHosted: false,
version: project.version,
environment: project.environment
environment: project.env
}
}, require('./settings.' + project.environment + '.json') || {})
}, require('./settings' + (project.env !== 'Development' ? ('.' + project.env) : '') + '.json') || {})
}));
}
@@ -204,6 +261,35 @@ gulp.task('watch', function () {
gulp.watch('./settings*.json', ['settings']);
});
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);
});
@@ -211,7 +297,7 @@ gulp.task('dist:clean', function (cb) {
gulp.task('dist:move', function () {
var moves = [
{
src: '../../CNAME',
src: './CNAME',
dest: paths.dist
},
{
@@ -237,12 +323,35 @@ gulp.task('dist:move', function () {
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 + 'favicon.ico'
paths.webroot + 'u2f-connector.html',
paths.webroot + 'duo-connector.html',
paths.webroot + 'favicon.ico',
paths.webroot + 'app-id.json'
],
dest: paths.dist
}
@@ -261,7 +370,7 @@ gulp.task('dist:css', function () {
paths.cssDir + '**/*.css',
'!' + paths.cssDir + '**/*.min.css'
])
.pipe(preprocess({ context: settings }))
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
.pipe(cssmin())
.pipe(rename({ suffix: '.min' }))
.pipe(gulp.dest(paths.dist + 'css'));
@@ -277,18 +386,41 @@ gulp.task('dist:js:app', function () {
]);
merge(mainStream, config())
.pipe(preprocess({ context: settings }))
.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 + 'sjcl/sjcl.js',
paths.libDir + 'sjcl/*.js',
paths.libDir + 'angulartics/angulartics.js',
paths.libDir + '**/*.js',
'!' + paths.libDir + '**/*.min.js',
@@ -306,19 +438,49 @@ gulp.task('dist:preprocess', function () {
.src([
paths.dist + '/**/*.html'
], { base: '.' })
.pipe(preprocess({ context: settings }))
.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: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();
}];
}
});
});

55
package.json Normal file
View File

@@ -0,0 +1,55 @@
{
"name": "bitwarden",
"version": "1.16.0",
"env": "Production",
"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",
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
"angular-promise-polyfill": "0.0.4"
}
}

11
settings.Preview.json Normal file
View File

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

11
settings.Production.json Normal file
View File

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

11
settings.json Normal file
View File

@@ -0,0 +1,11 @@
{
"appSettings": {
"apiUri": "http://localhost:4000",
"identityUri": "http://localhost:33656",
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
"whitelistDomains": [
"localhost"
]
}
}

View File

@@ -1,20 +0,0 @@
using System.IO;
using Microsoft.AspNetCore.Hosting;
namespace Bit.Web
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

View File

@@ -1,23 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Bit.Web")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("bitwarden Web")]
[assembly: AssemblyProduct("bitwarden Web")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("0bebf47c-ba0b-48ac-b48c-718f94084ad5")]

View File

@@ -1,27 +0,0 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:4001/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Web": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -1,16 +0,0 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services) { }
public void Configure(IApplicationBuilder app)
{
app.UseFileServer();
}
}
}

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>0bebf47c-ba0b-48ac-b48c-718f94084ad5</ProjectGuid>
<RootNamespace>Bit.Vault</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>4001</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -1,47 +0,0 @@
@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700,300italic,400italic,600italic);
@import "../node_modules/toastr/toastr.less";
/* Start AdminLTE */
//Bootstrap Variables & Mixins
//The core bootstrap code have not been modified. These files
//are included only for reference.
@import (reference) "../node_modules/admin-lte/build/bootstrap-less/mixins.less";
@import (reference) "../node_modules/admin-lte/build/bootstrap-less/variables.less";
//MISC
//----
@import "../node_modules/admin-lte/build/less/core.less";
@import "../node_modules/admin-lte/build/less/variables.less";
@import "../node_modules/admin-lte/build/less/mixins.less";
//COMPONENTS
//-----------
@import "../node_modules/admin-lte/build/less/header.less";
@import "../node_modules/admin-lte/build/less/sidebar.less";
@import "../node_modules/admin-lte/build/less/sidebar-mini.less";
@import "../node_modules/admin-lte/build/less/control-sidebar.less";
@import "../node_modules/admin-lte/build/less/dropdown.less";
@import "../node_modules/admin-lte/build/less/forms.less";
@import "../node_modules/admin-lte/build/less/progress-bars.less";
@import "../node_modules/admin-lte/build/less/small-box.less";
@import "../node_modules/admin-lte/build/less/boxes.less";
@import "../node_modules/admin-lte/build/less/info-box.less";
@import "../node_modules/admin-lte/build/less/timeline.less";
@import "../node_modules/admin-lte/build/less/buttons.less";
@import "../node_modules/admin-lte/build/less/callout.less";
@import "../node_modules/admin-lte/build/less/alerts.less";
@import "../node_modules/admin-lte/build/less/navs.less";
@import "../node_modules/admin-lte/build/less/table.less";
@import "../node_modules/admin-lte/build/less/labels.less";
@import "../node_modules/admin-lte/build/less/modal.less";
//PAGES
//------
@import "../node_modules/admin-lte/build/less/login_and_register.less";
@import "../node_modules/admin-lte/build/less/404_500_errors.less";
//Miscellaneous
//-------------
@import "../node_modules/admin-lte/build/less/miscellaneous.less";
@import "../node_modules/admin-lte/build/less/print.less";
/* End AdminLTE */
@import "../node_modules/admin-lte/build/less/skins/skin-blue.less";

View File

@@ -1,184 +0,0 @@
@import "theme.less";
/* Theme Adjustments */
@boxed-layout-bg-image-path: "../images/boxed-bg.png";
body {
background-color: @gray;
.img-retina(@boxed-layout-bg-image-path, "../images/boxed-bg-2x.png", auto, auto);
}
body,
.main-header .logo,
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
.box-body > .table-responsive {
> .table {
margin-bottom: 0;
}
@media screen and (max-width: @screen-xs-max) {
border: none;
margin-bottom: 0;
}
}
.sidebar-menu li.header {
padding-right: 15px;
}
.sidebar-form .form-group {
margin-bottom: 0;
input[type="text"], .form-control-feedback {
color: #999;
}
}
form div.validation-errors ul {
margin-bottom: 0;
padding-left: 20px;
li {
margin-left: 0;
}
}
.login-page,
.register-page {
background-color: @gray;
background-repeat: repeat;
background-attachment: fixed;
.img-retina(@boxed-layout-bg-image-path, "../images/boxed-bg-2x.png", auto, auto);
}
.login-box-body,
.register-box-body {
.boxShadow(0 0 8px rgba(0, 0, 0, 0.5));
}
.login-box, .register-box {
.checkbox {
margin-top: 0;
}
ul {
margin-bottom: 0;
padding-left: 20px;
}
}
.password-options {
float: right;
i {
margin: 0 5px;
cursor: pointer;
}
}
@media screen and (min-width: @screen-sm-min) {
.settings-photo {
text-align: center;
img {
margin: 0 auto;
}
}
}
/* Buttons */
.btn-table {
padding: 1px 5px;
line-height: 1;
}
.btn-box-tool {
font-size: 14px;
}
form .btn .loading-icon {
margin-right: 8px;
}
/* Modals */
.modal-footer {
text-align: left;
}
/* Toastr */
#toast-container {
position: absolute;
&.toast-top-right {
top: 65px;
right: 15px;
@media (max-width: @screen-xs-max) {
top: initial;
bottom: 0;
right: 0;
width: 100%;
}
}
> .toast {
background-image: none !important;
.border-radius(0);
.boxShadow(0 0 8px rgba(0, 0, 0, 0.5));
@media (max-width: @screen-xs-max) {
width: 100%;
}
&.toast-danger, &.toast-error {
&:extend(.bg-red);
&:before {
content: "\f0e7";
}
}
&.toast-warning {
&:extend(.bg-yellow);
&:before {
content: "\f071";
}
}
&.toast-info {
&:extend(.bg-aqua);
&:before {
content: "\f005";
}
}
&.toast-success {
&:extend(.bg-green);
&:before {
content: "\f00C";
}
}
&:before {
position: fixed;
font-family: FontAwesome;
font-size: 24px;
line-height: 24px;
float: left;
color: #ffffff;
padding-right: 0.5em;
margin: auto 0.5em auto -1.5em;
}
}
}

View File

@@ -1,45 +0,0 @@
{
"name": "bitwarden",
"version": "0.0.0",
"devDependencies": {
"connect": "3.4.1",
"lodash": "4.13.1",
"gulp": "3.9.1",
"gulp-concat": "2.6.0",
"gulp-cssmin": "0.1.7",
"gulp-less": "3.1.0",
"gulp-rename": "1.2.2",
"gulp-uglify": "1.5.3",
"gulp-gh-pages": "0.5.4",
"gulp-preprocess": "2.0.0",
"gulp-ng-annotate": "2.0.0",
"gulp-ng-config": "1.3.1",
"jshint": "2.9.2",
"gulp-jshint": "2.0.1",
"rimraf": "2.5.2",
"run-sequence": "1.2.1",
"merge-stream": "1.0.0",
"jquery": "2.2.4",
"font-awesome": "4.6.3",
"bootstrap": "3.3.6",
"sjcl": "1.0.3",
"angular": "1.5.6",
"angular-resource": "1.5.6",
"angular-bootstrap-npm": "0.14.3",
"angular-ui-router": "0.3.1",
"angular-jwt": "0.0.9",
"angular-cookies": "1.5.6",
"admin-lte": "2.3.5",
"angular-md5": "0.1.10",
"angular-toastr": "1.7.0",
"angular-bootstrap-show-errors": "2.3.0",
"angular-messages": "1.5.6",
"ngstorage": "0.3.10",
"papaparse": "4.1.2",
"toastr": "2.1.2",
"clipboard": "1.5.12",
"ngclipboard": "1.1.1",
"angulartics": "1.1.2",
"angulartics-google-analytics": "0.2.1"
}
}

View File

@@ -1,56 +0,0 @@
{
"version": "1.0.1",
"environment": "Development",
"dependencies": {
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0"
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": {
"version": "1.0.0-preview2-final",
"imports": "portable-net45+win8+dnxcore50"
}
},
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
}
}
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"gcServer": false,
"gcConcurrent": true
},
"publishOptions": {
"include": [
"wwwroot",
"Views",
"settings.json",
"settings.Development.json",
"settings.Production.json",
"settings.Staging.json",
"web.config"
]
},
"scripts": {
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
},
"userSecretsId": "aspnet-Vault-20160519103145"
}

View File

@@ -1,5 +0,0 @@
{
"appSettings": {
"apiUri": "http://localhost:4000"
}
}

View File

@@ -1,5 +0,0 @@
{
"appSettings": {
"apiUri": "https://api.bitwarden.com"
}
}

View File

@@ -1,5 +0,0 @@
{
"appSettings": {
"apiUri": "https://api.bitwarden.com"
}
}

View File

@@ -1,5 +0,0 @@
{
"appSettings": {
"rememberedEmailCookieName": "bit.rememberedEmail"
}
}

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
</system.webServer>
</configuration>

View File

@@ -1,70 +0,0 @@
/// <autosync enabled="true" />
/// <reference path="../gulpfile.js" />
/// <reference path="app/accounts/accountsLoginController.js" />
/// <reference path="app/accounts/accountsLogoutController.js" />
/// <reference path="app/accounts/accountsmodule.js" />
/// <reference path="app/accounts/accountspasswordhintcontroller.js" />
/// <reference path="app/accounts/accountsRegisterController.js" />
/// <reference path="app/apiInterceptor.js" />
/// <reference path="app/app.js" />
/// <reference path="app/config.js" />
/// <reference path="app/directives/apiFieldDirective.js" />
/// <reference path="app/directives/apiFormDirective.js" />
/// <reference path="app/directives/directivesModule.js" />
/// <reference path="app/directives/masterPasswordDirective.js" />
/// <reference path="app/directives/pageTitleDirective.js" />
/// <reference path="app/directives/passwordmeterdirective.js" />
/// <reference path="app/directives/passwordviewerdirective.js" />
/// <reference path="app/global/globalModule.js" />
/// <reference path="app/global/mainController.js" />
/// <reference path="app/global/sideNavController.js" />
/// <reference path="app/global/topNavController.js" />
/// <reference path="app/services/apiService.js" />
/// <reference path="app/services/authService.js" />
/// <reference path="app/services/cipherService.js" />
/// <reference path="app/services/cryptoService.js" />
/// <reference path="app/services/importservice.js" />
/// <reference path="app/services/passwordservice.js" />
/// <reference path="app/services/servicesModule.js" />
/// <reference path="app/services/tokenService.js" />
/// <reference path="app/services/validationservice.js" />
/// <reference path="app/settings.js" />
/// <reference path="app/settings/settingsChangeEmailController.js" />
/// <reference path="app/settings/settingsChangePasswordController.js" />
/// <reference path="app/settings/settingsController.js" />
/// <reference path="app/settings/settingsdeletecontroller.js" />
/// <reference path="app/settings/settingsmodule.js" />
/// <reference path="app/settings/settingsSessionsController.js" />
/// <reference path="app/settings/settingsTwoFactorController.js" />
/// <reference path="app/tools/toolsAuditsController.js" />
/// <reference path="app/tools/toolsController.js" />
/// <reference path="app/tools/toolsExportController.js" />
/// <reference path="app/tools/toolsImportController.js" />
/// <reference path="app/tools/toolsmodule.js" />
/// <reference path="app/vault/vaultAddFolderController.js" />
/// <reference path="app/vault/vaultAddSiteController.js" />
/// <reference path="app/vault/vaultController.js" />
/// <reference path="app/vault/vaultEditFolderController.js" />
/// <reference path="app/vault/vaultEditSiteController.js" />
/// <reference path="app/vault/vaultmodule.js" />
/// <reference path="lib/admin-lte/js/app.js" />
/// <reference path="lib/angular/angular.js" />
/// <reference path="lib/angular-bootstrap/angular-bootstrap-tpls.js" />
/// <reference path="lib/angular-bootstrap-show-errors/showErrors.js" />
/// <reference path="lib/angular-cookies/angular-cookies.js" />
/// <reference path="lib/angular-jwt/angular-jwt.js" />
/// <reference path="lib/angular-md5/angular-md5.js" />
/// <reference path="lib/angular-messages/angular-messages.js" />
/// <reference path="lib/angular-resource/angular-resource.js" />
/// <reference path="lib/angular-toastr/angular-toastr.js" />
/// <reference path="lib/angular-toastr/angular-toastr.tpls.js" />
/// <reference path="lib/angular-ui-router/angular-ui-router.js" />
/// <reference path="lib/bootstrap/js/bootstrap.min.js" />
/// <reference path="lib/clipboard/clipboard.js" />
/// <reference path="lib/jquery/jquery.js" />
/// <reference path="lib/ngclipboard/ngclipboard.js" />
/// <reference path="lib/ngstorage/ngStorage.js" />
/// <reference path="lib/papaparse/papaparse.js" />
/// <reference path="lib/sjcl/bitArray.js" />
/// <reference path="lib/sjcl/cbc.js" />
/// <reference path="lib/sjcl/sjcl.js" />

View File

@@ -1,51 +0,0 @@
angular
.module('bit.accounts')
.controller('accountsLoginController', function ($scope, $rootScope, $cookies, apiService, cryptoService, authService, $state, appSettings, $analytics) {
var rememberedEmail = $cookies.get(appSettings.rememberedEmailCookieName);
if (rememberedEmail) {
$scope.model = {
email: rememberedEmail,
rememberEmail: true
};
}
$scope.login = function (model) {
$scope.loginPromise = authService.logIn(model.email, model.masterPassword);
$scope.loginPromise.then(function () {
if (model.rememberEmail) {
var cookieExpiration = new Date();
cookieExpiration.setFullYear(cookieExpiration.getFullYear() + 10);
$cookies.put(
appSettings.rememberedEmailCookieName,
model.email,
{ expires: cookieExpiration });
}
else {
$cookies.remove(appSettings.rememberedEmailCookieName);
}
var profile = authService.getUserProfile();
if (profile.twoFactor) {
$analytics.eventTrack('Logged In To Two-step');
$state.go('frontend.login.twoFactor');
}
else {
$analytics.eventTrack('Logged In');
$state.go('backend.vault');
}
});
};
$scope.twoFactor = function (model) {
// Only supporting Authenticator provider for now
$scope.twoFactorPromise = authService.logInTwoFactor(model.code, "Authenticator");
$scope.twoFactorPromise.then(function () {
$analytics.eventTrack('Logged In From Two-step');
$state.go('backend.vault');
});
};
});

View File

@@ -1,43 +0,0 @@
angular
.module('bit.accounts')
.controller('accountsRegisterController', function ($scope, $location, apiService, cryptoService, validationService, $analytics) {
var params = $location.search();
$scope.success = false;
$scope.model = {
email: params.email
};
$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 key = cryptoService.makeKey($scope.model.masterPassword, email);
var request = {
name: $scope.model.name,
email: email,
masterPasswordHash: cryptoService.hashPassword($scope.model.masterPassword, key),
masterPasswordHint: $scope.model.masterPasswordHint
};
$scope.registerPromise = apiService.accounts.register(request, function () {
$scope.success = true;
$analytics.eventTrack('Registered');
}).$promise;
};
});

View File

@@ -1,22 +0,0 @@
<p class="login-box-msg">Enter your two-step verification code.</p>
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(model)" api-form="twoFactorPromise">
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
<h4>Errors have occured</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="model.code"
required api-field />
<span class="fa fa-lock form-control-feedback"></span>
</div>
<div class="row">
<div class="col-xs-offset-7 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>

View File

@@ -1,135 +0,0 @@
angular
.module('bit')
.config(function ($stateProvider, $urlRouterProvider, $httpProvider, jwtInterceptorProvider, $uibTooltipProvider, toastrConfig) {
jwtInterceptorProvider.urlParam = 'access_token';
jwtInterceptorProvider.tokenGetter = /*@ngInject*/ function (config, appSettings, tokenService) {
if (config.url.indexOf(appSettings.apiUri) === 0) {
return tokenService.getToken();
}
};
angular.extend(toastrConfig, {
closeButton: true,
progressBar: true,
showMethod: 'slideDown',
target: '.toast-target'
});
$uibTooltipProvider.options({
popupDelay: 600
});
if ($httpProvider.defaults.headers.post) {
$httpProvider.defaults.headers.post = {};
}
$httpProvider.defaults.headers.post['Content-Type'] = 'text/plain; charset=utf-8';
$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.vault', {
url: '^/',
templateUrl: 'app/vault/views/vault.html',
controller: 'vaultController',
data: { pageTitle: 'My Vault' }
})
.state('backend.settings', {
url: '^/settings',
templateUrl: 'app/settings/views/settings.html',
controller: 'settingsController',
data: { pageTitle: 'Settings' }
})
.state('backend.tools', {
url: '^/tools',
templateUrl: 'app/tools/views/tools.html',
controller: 'toolsController',
data: { pageTitle: 'Tools' }
})
// Frontend
.state('frontend', {
templateUrl: 'app/views/frontendLayout.html',
abstract: true,
data: {
authorize: false
}
})
.state('frontend.login', {
templateUrl: 'app/accounts/views/accountsLogin.html',
controller: 'accountsLoginController',
data: {
bodyClass: 'login-page'
}
})
.state('frontend.login.info', {
url: '^/login',
templateUrl: 'app/accounts/views/accountsLoginInfo.html',
data: {
pageTitle: 'Log In'
}
})
.state('frontend.login.twoFactor', {
url: '^/login/two-factor',
templateUrl: 'app/accounts/views/accountsLoginTwoFactor.html',
data: {
pageTitle: 'Log In (Two Factor)',
authorizeTwoFactor: true
}
})
.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.register', {
url: '^/register',
templateUrl: 'app/accounts/views/accountsRegister.html',
controller: 'accountsRegisterController',
data: {
pageTitle: 'Register',
bodyClass: 'register-page'
}
});
})
.run(function ($rootScope, authService, jwtHelper, tokenService, $state) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
if (!toState.data || !toState.data.authorize) {
if (authService.isAuthenticated() && !jwtHelper.isTokenExpired(tokenService.getToken())) {
event.preventDefault();
$state.go('backend.vault');
}
return;
}
if (!authService.isAuthenticated() || jwtHelper.isTokenExpired(tokenService.getToken())) {
event.preventDefault();
authService.logOut();
$state.go('frontend.login.info');
}
});
});

View File

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

View File

@@ -1,82 +0,0 @@
angular
.module('bit.global')
.controller('mainController', function ($scope, $state, authService, appSettings, toastr) {
var vm = this;
vm.bodyClass = '';
vm.userProfile = null;
vm.searchVaultText = null;
vm.version = appSettings.version;
$scope.currentYear = new Date().getFullYear();
$scope.$on('$viewContentLoaded', function () {
if ($.AdminLTE) {
if ($.AdminLTE.layout) {
$.AdminLTE.layout.fix();
$.AdminLTE.layout.fixSidebar();
}
if ($.AdminLTE.pushMenu) {
$.AdminLTE.pushMenu.expandOnHover();
}
}
});
$scope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
vm.searchVaultText = null;
vm.userProfile = authService.getUserProfile();
if (toState.data.bodyClass) {
vm.bodyClass = toState.data.bodyClass;
return;
}
else {
vm.bodyClass = '';
}
});
$scope.searchVault = function () {
$state.go('backend.vault');
};
$scope.addSite = function () {
$scope.$broadcast('vaultAddSite');
};
$scope.addFolder = function () {
$scope.$broadcast('vaultAddFolder');
};
$scope.changeEmail = function () {
$scope.$broadcast('settingsChangeEmail');
};
$scope.changePassword = function () {
$scope.$broadcast('settingsChangePassword');
};
$scope.sessions = function () {
$scope.$broadcast('settingsSessions');
};
$scope.delete = function () {
$scope.$broadcast('settingsDelete');
};
$scope.twoFactor = function () {
$scope.$broadcast('settingsTwoFactor');
};
$scope.import = function () {
$scope.$broadcast('toolsImport');
};
$scope.export = function () {
$scope.$broadcast('toolsExport');
};
$scope.audits = function () {
$scope.$broadcast('toolsAudits');
};
});

View File

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

View File

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

View File

@@ -1,53 +0,0 @@
angular
.module('bit.services')
.factory('apiService', function ($resource, tokenService, appSettings) {
var _service = {},
_apiUri = appSettings.apiUri;
_service.sites = $resource(_apiUri + '/sites/:id', {}, {
get: { method: 'GET', params: { id: '@id' } },
list: { method: 'GET', params: {} },
post: { method: 'POST', params: {} },
put: { method: 'POST', params: { id: '@id' } },
del: { url: _apiUri + '/sites/:id/delete', method: 'POST', params: { id: '@id' } }
});
_service.folders = $resource(_apiUri + '/folders/:id', {}, {
get: { method: 'GET', params: { id: '@id' } },
list: { method: 'GET', params: {} },
post: { method: 'POST', params: {} },
put: { method: 'POST', params: { id: '@id' } },
del: { url: _apiUri + '/folders/:id/delete', method: 'POST', params: { id: '@id' } }
});
_service.ciphers = $resource(_apiUri + '/ciphers/:id', {}, {
get: { method: 'GET', params: { id: '@id' } },
list: { method: 'GET', params: {} },
'import': { url: _apiUri + '/ciphers/import', method: 'POST', params: {} },
favorite: { url: _apiUri + '/ciphers/:id/favorite', method: 'POST', params: { id: '@id' } },
del: { url: _apiUri + '/ciphers/:id/delete', method: 'POST', params: { id: '@id' } }
});
_service.accounts = $resource(_apiUri + '/accounts', {}, {
register: { url: _apiUri + '/accounts/register', method: 'POST', params: {} },
emailToken: { url: _apiUri + '/accounts/email-token', method: 'POST', params: {} },
email: { url: _apiUri + '/accounts/email', method: 'POST', params: {} },
putPassword: { url: _apiUri + '/accounts/password', method: 'POST', params: {} },
getProfile: { url: _apiUri + '/accounts/profile', method: 'GET', params: {} },
putProfile: { url: _apiUri + '/accounts/profile', method: 'POST', params: {} },
getTwoFactor: { url: _apiUri + '/accounts/two-factor', method: 'GET', params: {} },
putTwoFactor: { url: _apiUri + '/accounts/two-factor', method: 'POST', params: {} },
postPasswordHint: { url: _apiUri + '/accounts/password-hint', method: 'POST', params: {} },
putSecurityStamp: { url: _apiUri + '/accounts/security-stamp', method: 'POST', params: {} },
'import': { url: _apiUri + '/accounts/import', method: 'POST', params: {} },
postDelete: { url: _apiUri + '/accounts/delete', method: 'POST', params: {} }
});
_service.auth = $resource(_apiUri + '/auth', {}, {
token: { url: _apiUri + '/auth/token', method: 'POST', params: {} },
tokenTwoFactor: { url: _apiUri + '/auth/token/two-factor', method: 'POST', params: {} }
});
return _service;
});

View File

@@ -1,112 +0,0 @@
angular
.module('bit.services')
.factory('authService', function (cryptoService, apiService, tokenService, $q, jwtHelper) {
var _service = {},
_userProfile = null;
_service.logIn = function (email, masterPassword) {
email = email.toLowerCase();
var key = cryptoService.makeKey(masterPassword, email);
var request = {
email: email,
masterPasswordHash: cryptoService.hashPassword(masterPassword, key)
};
var deferred = $q.defer();
apiService.auth.token(request, function (response) {
if (!response || !response.Token) {
return;
}
tokenService.setToken(response.Token);
cryptoService.setKey(key);
_service.setUserProfile(response.Profile);
deferred.resolve(response);
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
};
_service.logInTwoFactor = function (code, provider) {
var request = {
code: code,
provider: provider
};
var deferred = $q.defer();
apiService.auth.tokenTwoFactor(request, function (response) {
if (!response || !response.Token) {
return;
}
tokenService.setToken(response.Token);
_service.setUserProfile(response.Profile);
deferred.resolve(response);
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
};
_service.logOut = function () {
tokenService.clearToken();
cryptoService.clearKey();
_userProfile = null;
};
_service.getUserProfile = function () {
if (!_userProfile) {
_service.setUserProfile();
}
return _userProfile;
};
_service.setUserProfile = function (profile) {
var token = tokenService.getToken();
if (!token) {
return;
}
var decodedToken = jwtHelper.decodeToken(token);
var twoFactor = decodedToken.authmethod === "TwoFactor";
_userProfile = {
id: decodedToken.nameid,
email: decodedToken.email,
twoFactor: twoFactor
};
if (!twoFactor && profile) {
loadProfile(profile);
}
else if (!twoFactor && !profile) {
apiService.accounts.getProfile({}, loadProfile);
}
};
function loadProfile(profile) {
_userProfile.extended = {
name: profile.Name,
twoFactorEnabled: profile.TwoFactorEnabled,
culture: profile.Culture
};
}
_service.isAuthenticated = function () {
return _service.getUserProfile() !== null && !_service.getUserProfile().twoFactor;
};
_service.isTwoFactorAuthenticated = function () {
return _service.getUserProfile() !== null && _service.getUserProfile().twoFactor;
};
return _service;
});

View File

@@ -1,112 +0,0 @@
angular
.module('bit.services')
.factory('cipherService', function (cryptoService, apiService) {
var _service = {};
_service.decryptSites = function (encryptedSites) {
if (!encryptedSites) throw "encryptedSites is undefined or null";
var unencryptedSites = [];
for (var i = 0; i < encryptedSites.length; i++) {
unencryptedSites.push(_service.decryptSite(encryptedSites[i]));
}
return unencryptedSites;
};
_service.decryptSite = function (encryptedSite) {
if (!encryptedSite) throw "encryptedSite is undefined or null";
var site = {
id: encryptedSite.Id,
'type': 1,
folderId: encryptedSite.FolderId,
favorite: encryptedSite.Favorite,
name: cryptoService.decrypt(encryptedSite.Name),
uri: cryptoService.decrypt(encryptedSite.Uri),
username: encryptedSite.Username && encryptedSite.Username !== '' ? cryptoService.decrypt(encryptedSite.Username) : null,
password: cryptoService.decrypt(encryptedSite.Password),
notes: encryptedSite.Notes && encryptedSite.Notes !== '' ? cryptoService.decrypt(encryptedSite.Notes) : null
};
if (encryptedSite.Folder) {
site.folder = {
name: cryptoService.decrypt(encryptedSite.Folder.Name)
};
}
return site;
};
_service.decryptFolders = function (encryptedFolders) {
if (!encryptedFolders) throw "encryptedFolders is undefined or null";
var unencryptedFolders = [];
for (var i = 0; i < encryptedFolders.length; i++) {
unencryptedFolders.push(_service.decryptFolder(encryptedFolders[i]));
}
return unencryptedFolders;
};
_service.decryptFolder = function (encryptedFolder) {
if (!encryptedFolder) throw "encryptedFolder is undefined or null";
return {
id: encryptedFolder.Id,
'type': 0,
name: cryptoService.decrypt(encryptedFolder.Name)
};
};
_service.encryptSites = function (unencryptedSites, key) {
if (!unencryptedSites) throw "unencryptedSites is undefined or null";
var encryptedSites = [];
for (var i = 0; i < unencryptedSites.length; i++) {
encryptedSites.push(_service.encryptSite(unencryptedSites[i], key));
}
return encryptedSites;
};
_service.encryptSite = function (unencryptedSite, key) {
if (!unencryptedSite) throw "unencryptedSite is undefined or null";
return {
id: unencryptedSite.id,
'type': 1,
folderId: unencryptedSite.folderId === '' ? null : unencryptedSite.folderId,
favorite: unencryptedSite.favorite !== null ? unencryptedSite.favorite : false,
uri: cryptoService.encrypt(unencryptedSite.uri, key),
name: cryptoService.encrypt(unencryptedSite.name, key),
username: !unencryptedSite.username || unencryptedSite.username === '' ? null : cryptoService.encrypt(unencryptedSite.username, key),
password: cryptoService.encrypt(unencryptedSite.password, key),
notes: !unencryptedSite.notes || unencryptedSite.notes === '' ? null : cryptoService.encrypt(unencryptedSite.notes, key)
};
};
_service.encryptFolders = function (unencryptedFolders, key) {
if (!unencryptedFolders) throw "unencryptedFolders is undefined or null";
var encryptedFolders = [];
for (var i = 0; i < unencryptedFolders.length; i++) {
encryptedFolders.push(_service.encryptFolder(unencryptedFolders[i], key));
}
return encryptedFolders;
};
_service.encryptFolder = function (unencryptedFolder, key) {
if (!unencryptedFolder) throw "unencryptedFolder is undefined or null";
return {
id: unencryptedFolder.id,
'type': 0,
name: cryptoService.encrypt(unencryptedFolder.name, key)
};
};
return _service;
});

View File

@@ -1,114 +0,0 @@
angular
.module('bit.services')
.factory('cryptoService', function ($sessionStorage) {
var _service = {},
_key,
_b64Key,
_aes;
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
_service.setKey = function (key) {
_key = key;
$sessionStorage.key = sjcl.codec.base64.fromBits(key);
};
_service.getKey = function (b64) {
if (b64 && b64 === true && _b64Key) {
return _b64Key;
}
else if (!b64 && _key) {
return _key;
}
if ($sessionStorage.key) {
_key = sjcl.codec.base64.toBits($sessionStorage.key);
}
if (b64 && b64 === true) {
_b64Key = sjcl.codec.base64.fromBits(_key);
return _b64Key;
}
return _key;
};
_service.clearKey = function () {
_key = _b64Key = _aes = null;
delete $sessionStorage.key;
};
_service.makeKey = function (password, salt, b64) {
var key = sjcl.misc.pbkdf2(password, salt, 5000, 256, null);
if (b64 && b64 === true) {
return sjcl.codec.base64.fromBits(key);
}
return key;
};
_service.hashPassword = function (password, key) {
if (!key) {
key = _service.getKey();
}
if (!password || !key) {
throw 'Invalid parameters.';
}
var hashBits = sjcl.misc.pbkdf2(key, password, 1, 256, null);
return sjcl.codec.base64.fromBits(hashBits);
};
_service.getAes = function () {
if (!_aes && _service.getKey()) {
_aes = new sjcl.cipher.aes(_service.getKey());
}
return _aes;
};
_service.encrypt = function (plaintextValue, key) {
if (!_service.getKey() && !key) {
throw 'Encryption key unavailable.';
}
if (!key) {
key = _service.getKey();
}
var response = {};
var params = {
mode: "cbc",
iv: sjcl.random.randomWords(4, 10)
};
var ctJson = sjcl.encrypt(key, plaintextValue, params, response);
var ct = ctJson.match(/"ct":"([^"]*)"/)[1];
var iv = sjcl.codec.base64.fromBits(response.iv);
return iv + "|" + ct;
};
_service.decrypt = function (encValue) {
if (!_service.getAes()) {
throw 'AES encryption unavailable.';
}
var encPieces = encValue.split('|');
if (encPieces.length !== 2) {
return '';
}
var ivBits = sjcl.codec.base64.toBits(encPieces[0]);
var ctBits = sjcl.codec.base64.toBits(encPieces[1]);
var decBits = sjcl.mode.cbc.decrypt(_service.getAes(), ctBits, ivBits, null);
return sjcl.codec.utf8String.fromBits(decBits);
};
return _service;
});

View File

@@ -1,136 +0,0 @@
angular
.module('bit.services')
.factory('importService', function () {
var _service = {};
_service.import = function (source, file, success, error) {
switch (source) {
case 'local':
importLocal(file, success, error);
break;
case 'lastpass':
importLastPass(file, success, error);
break;
default:
error();
break;
}
};
function importLocal(file, success, error) {
Papa.parse(file, {
header: true,
complete: function (results) {
var folders = [],
sites = [],
folderRelationships = [];
angular.forEach(results.data, function (value, key) {
if (!value.uri || value.uri === '') {
return;
}
var folderIndex = folders.length,
siteIndex = sites.length,
hasFolder = value.folder && value.folder !== '',
addFolder = hasFolder;
if (hasFolder) {
for (var i = 0; i < folders.length; i++) {
if (folders[i].name == value.folder) {
addFolder = false;
folderIndex = i;
break;
}
}
}
sites.push({
favorite: value.favorite !== null ? value.favorite : false,
uri: value.uri,
username: value.username && value.username !== '' ? value.username : null,
password: value.password,
notes: value.notes && value.notes !== '' ? value.notes : null,
name: value.name
});
if (addFolder) {
folders.push({
name: value.folder
});
}
if (hasFolder) {
var relationship = {
key: siteIndex,
value: folderIndex
};
folderRelationships.push(relationship);
}
});
success(folders, sites, folderRelationships);
}
});
}
function importLastPass(file, success, error) {
Papa.parse(file, {
header: true,
complete: function (results) {
var folders = [],
sites = [],
siteRelationships = [];
angular.forEach(results.data, function (value, key) {
if (!value.url || value.url === '') {
return;
}
var folderIndex = folders.length,
siteIndex = sites.length,
hasFolder = value.grouping && value.grouping !== '' && value.grouping != '(none)',
addFolder = hasFolder;
if (hasFolder) {
for (var i = 0; i < folders.length; i++) {
if (folders[i].name == value.grouping) {
addFolder = false;
folderIndex = i;
break;
}
}
}
sites.push({
favorite: value.fav == '1',
uri: value.url,
username: value.username && value.username !== '' ? value.username : null,
password: value.password,
notes: value.extra && value.extra !== '' ? value.extra : null,
name: value.name
});
if (addFolder) {
folders.push({
name: value.grouping
});
}
if (hasFolder) {
var relationship = {
key: siteIndex,
value: folderIndex
};
siteRelationships.push(relationship);
}
});
success(folders, sites, siteRelationships);
}
});
}
return _service;
});

View File

@@ -1,27 +0,0 @@
angular
.module('bit.services')
.factory('tokenService', function ($sessionStorage) {
var _service = {},
_token;
_service.setToken = function (token) {
$sessionStorage.authBearer = token;
_token = token;
};
_service.getToken = function () {
if (!_token) {
_token = $sessionStorage.authBearer;
}
return _token;
};
_service.clearToken = function () {
_token = null;
delete $sessionStorage.authBearer;
};
return _service;
});

View File

@@ -1,2 +0,0 @@
angular.module("bit")
.constant("appSettings", {"rememberedEmailCookieName":"bit.rememberedEmail","version":"1.0.1","environment":"Development","apiUri":"http://localhost:4000"});

View File

@@ -1,69 +0,0 @@
angular
.module('bit.settings')
.controller('settingsChangeEmailController', function ($scope, $state, apiService, $uibModalInstance, cryptoService, cipherService, authService, $q, toastr, $analytics) {
$analytics.eventTrack('settingsChangeEmailController', { category: 'Modal' });
var _masterPasswordHash,
_newMasterPasswordHash,
_newKey;
$scope.token = function (model) {
_masterPasswordHash = cryptoService.hashPassword(model.masterPassword);
var newEmail = model.newEmail.toLowerCase();
var request = {
newEmail: newEmail,
masterPasswordHash: _masterPasswordHash
};
$scope.tokenPromise = apiService.accounts.emailToken(request, function () {
_newKey = cryptoService.makeKey(model.masterPassword, newEmail);
_newMasterPasswordHash = cryptoService.hashPassword(model.masterPassword, _newKey);
$scope.tokenSent = true;
}).$promise;
};
$scope.confirm = function (model) {
$scope.processing = true;
var reencryptedSites = [];
var sitesPromise = apiService.sites.list({ dirty: false }, function (encryptedSites) {
var unencryptedSites = cipherService.decryptSites(encryptedSites.Data);
reencryptedSites = cipherService.encryptSites(unencryptedSites, _newKey);
}).$promise;
var reencryptedFolders = [];
var foldersPromise = apiService.folders.list({ dirty: false }, function (encryptedFolders) {
var unencryptedFolders = cipherService.decryptFolders(encryptedFolders.Data);
reencryptedFolders = cipherService.encryptFolders(unencryptedFolders, _newKey);
}).$promise;
$q.all([sitesPromise, foldersPromise]).then(function () {
var request = {
token: model.token,
newEmail: model.newEmail.toLowerCase(),
masterPasswordHash: _masterPasswordHash,
newMasterPasswordHash: _newMasterPasswordHash,
ciphers: reencryptedSites.concat(reencryptedFolders)
};
$scope.confirmPromise = apiService.accounts.email(request, function () {
$uibModalInstance.dismiss('cancel');
$analytics.eventTrack('Changed Email');
authService.logOut();
$state.go('frontend.login.info').then(function () {
toastr.success('Please log back in.', 'Email Changed');
});
}, function () {
// TODO: recovery mode
$uibModalInstance.dismiss('cancel');
toastr.error('Something went wrong.', 'Oh No!');
}).$promise;
});
};
$scope.close = function () {
$uibModalInstance.dismiss('cancel');
};
});

View File

@@ -1,67 +0,0 @@
angular
.module('bit.settings')
.controller('settingsChangePasswordController', function ($scope, $state, apiService, $uibModalInstance,
cryptoService, authService, cipherService, validationService, $q, toastr, $analytics) {
$analytics.eventTrack('settingsChangePasswordController', { category: 'Modal' });
$scope.save = function (model, form) {
var error = false;
if ($scope.model.newMasterPassword.length < 8) {
validationService.addError(form, 'NewMasterPasswordHash',
'Master password must be at least 8 characters long.', true);
error = true;
}
if ($scope.model.newMasterPassword !== $scope.model.confirmNewMasterPassword) {
validationService.addError(form, 'ConfirmNewMasterPasswordHash',
'New master password confirmation does not match.', true);
error = true;
}
if (error) {
return;
}
$scope.processing = true;
var profile = authService.getUserProfile();
var newKey = cryptoService.makeKey(model.newMasterPassword, profile.email.toLowerCase());
var reencryptedSites = [];
var sitesPromise = apiService.sites.list({ dirty: false }, function (encryptedSites) {
var unencryptedSites = cipherService.decryptSites(encryptedSites.Data);
reencryptedSites = cipherService.encryptSites(unencryptedSites, newKey);
}).$promise;
var reencryptedFolders = [];
var foldersPromise = apiService.folders.list({ dirty: false }, function (encryptedFolders) {
var unencryptedFolders = cipherService.decryptFolders(encryptedFolders.Data);
reencryptedFolders = cipherService.encryptFolders(unencryptedFolders, newKey);
}).$promise;
$q.all([sitesPromise, foldersPromise]).then(function () {
var request = {
masterPasswordHash: cryptoService.hashPassword(model.masterPassword),
newMasterPasswordHash: cryptoService.hashPassword(model.newMasterPassword, newKey),
ciphers: reencryptedSites.concat(reencryptedFolders)
};
$scope.savePromise = apiService.accounts.putPassword(request, function () {
$uibModalInstance.dismiss('cancel');
authService.logOut();
$analytics.eventTrack('Changed Password');
$state.go('frontend.login.info').then(function () {
toastr.success('Please log back in.', 'Master Password Changed');
});
}, function () {
// TODO: recovery mode
$uibModalInstance.dismiss('cancel');
toastr.error('Something went wrong.', 'Oh No!');
}).$promise;
});
};
$scope.close = function () {
$uibModalInstance.dismiss('cancel');
};
});

View File

@@ -1,85 +0,0 @@
angular
.module('bit.settings')
.controller('settingsController', function ($scope, $uibModal, apiService, toastr, authService) {
$scope.model = {};
apiService.accounts.getProfile({}, function (user) {
$scope.model = {
name: user.Name,
email: user.Email,
masterPasswordHint: user.MasterPasswordHint,
culture: user.Culture,
twoFactorEnabled: user.TwoFactorEnabled
};
});
$scope.save = function (model) {
$scope.savePromise = apiService.accounts.putProfile({}, model, function (profile) {
authService.setUserProfile(profile);
toastr.success('Account has been updated.', 'Success!');
}).$promise;
};
$scope.changePassword = function () {
$uibModal.open({
animation: true,
templateUrl: 'app/settings/views/settingsChangePassword.html',
controller: 'settingsChangePasswordController'
});
};
$scope.$on('settingsChangePassword', function (event, args) {
$scope.changePassword();
});
$scope.changeEmail = function () {
$uibModal.open({
animation: true,
templateUrl: 'app/settings/views/settingsChangeEmail.html',
controller: 'settingsChangeEmailController',
size: 'sm'
});
};
$scope.$on('settingsChangeEmail', function (event, args) {
$scope.changeEmail();
});
$scope.twoFactor = function () {
$uibModal.open({
animation: true,
templateUrl: 'app/settings/views/settingsTwoFactor.html',
controller: 'settingsTwoFactorController'
});
};
$scope.$on('settingsTwoFactor', function (event, args) {
$scope.twoFactor();
});
$scope.sessions = function () {
$uibModal.open({
animation: true,
templateUrl: 'app/settings/views/settingsSessions.html',
controller: 'settingsSessionsController'
});
};
$scope.$on('settingsSessions', function (event, args) {
$scope.sessions();
});
$scope.delete = function () {
$uibModal.open({
animation: true,
templateUrl: 'app/settings/views/settingsDelete.html',
controller: 'settingsDeleteController',
size: 'sm'
});
};
$scope.$on('settingsDelete', function (event, args) {
$scope.delete();
});
});

View File

@@ -1,24 +0,0 @@
angular
.module('bit.settings')
.controller('settingsDeleteController', function ($scope, $state, apiService, $uibModalInstance, cryptoService, authService, toastr, $analytics) {
$analytics.eventTrack('settingsDeleteController', { category: 'Modal' });
$scope.submit = function (model) {
var request = {
masterPasswordHash: cryptoService.hashPassword(model.masterPassword)
};
$scope.submitPromise = apiService.accounts.postDelete(request, function () {
$uibModalInstance.dismiss('cancel');
authService.logOut();
$analytics.eventTrack('Deleted Account');
$state.go('frontend.login.info').then(function () {
toastr.success('Your account has been closed and all associated data has been deleted.', 'Account Deleted');
});
}).$promise;
};
$scope.close = function () {
$uibModalInstance.dismiss('cancel');
};
});

View File

@@ -1,24 +0,0 @@
angular
.module('bit.settings')
.controller('settingsSessionsController', function ($scope, $state, apiService, $uibModalInstance, cryptoService, authService, toastr, $analytics) {
$analytics.eventTrack('settingsSessionsController', { category: 'Modal' });
$scope.submit = function (model) {
var request = {
masterPasswordHash: cryptoService.hashPassword(model.masterPassword)
};
$scope.submitPromise = apiService.accounts.putSecurityStamp(request, function () {
$uibModalInstance.dismiss('cancel');
authService.logOut();
$analytics.eventTrack('Deauthorized Sessions');
$state.go('frontend.login.info').then(function () {
toastr.success('Please log back in.', 'All Sessions Deauthorized');
});
}).$promise;
};
$scope.close = function () {
$uibModalInstance.dismiss('cancel');
};
});

View File

@@ -1,65 +0,0 @@
angular
.module('bit.settings')
.controller('settingsTwoFactorController', function ($scope, apiService, $uibModalInstance, cryptoService, authService, $q, toastr, $analytics) {
$analytics.eventTrack('settingsTwoFactorController', { category: 'Modal' });
var _issuer = 'bitwarden',
_profile = authService.getUserProfile(),
_masterPasswordHash;
$scope.account = _profile.email;
$scope.enabled = function () {
return _profile.extended && _profile.extended.twoFactorEnabled;
};
$scope.auth = function (model) {
_masterPasswordHash = cryptoService.hashPassword(model.masterPassword);
$scope.authPromise = apiService.accounts.getTwoFactor({
masterPasswordHash: _masterPasswordHash,
provider: 0 /* Only authenticator provider for now. */
}, function (response) {
var key = response.AuthenticatorKey;
$scope.twoFactorModel = {
enabled: response.TwoFactorEnabled,
key: key.replace(/(.{4})/g, '$1 ').trim(),
qr: 'https://chart.googleapis.com/chart?chs=120x120&chld=L|0&cht=qr&chl=otpauth://totp/' +
_issuer + ':' + encodeURIComponent(_profile.email) +
'%3Fsecret=' + encodeURIComponent(key) +
'%26issuer=' + _issuer
};
}).$promise;
};
$scope.update = function (model) {
var currentlyEnabled = $scope.twoFactorModel.enabled;
if (currentlyEnabled && !confirm('Are you sure you want to disable two-step login?')) {
return;
}
var request = {
enabled: !currentlyEnabled,
token: model ? model.token : null,
masterPasswordHash: _masterPasswordHash
};
$scope.updatePromise = apiService.accounts.putTwoFactor({}, request, function (response) {
if (response.TwoFactorEnabled) {
$analytics.eventTrack('Enabled Two-step Login');
toastr.success('Two-step login has been enabled.');
if (_profile.extended) _profile.extended.twoFactorEnabled = true;
}
else {
$analytics.eventTrack('Disabled Two-step Login');
toastr.success('Two-step login has been disabled.');
if (_profile.extended) _profile.extended.twoFactorEnabled = false;
}
$scope.close();
}).$promise;
};
$scope.close = function () {
$uibModalInstance.dismiss('cancel');
};
});

View File

@@ -1,60 +0,0 @@
<section class="content-header">
<h1>
Settings
<small>manage your account</small>
</h1>
</section>
<section class="content">
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">General</h3>
</div>
<form role="form" name="profileForm" ng-submit="profileForm.$valid && save(model)" api-form="savePromise">
<div class="box-body">
<div class="row">
<div class="col-sm-9">
<div class="callout callout-danger validation-errors" ng-show="profileForm.$errors">
<h4>Errors have occured</h4>
<ul>
<li ng-repeat="e in profileForm.$errors">{{e}}</li>
</ul>
</div>
<div class="form-group" show-errors>
<label for="name">Name</label>
<input type="text" id="name" name="Name" ng-model="model.name" class="form-control"
required api-field />
</div>
<div class="form-group">
<label for="email">Email - <a href="javascript:void(0)" ng-click="changeEmail()">change</a></label>
<input type="text" id="email" ng-model="model.email" class="form-control" readonly />
</div>
<div class="form-group" show-errors>
<label for="hint">Master Password Hint</label>
<input type="text" id="hint" name="MasterPasswordHint" ng-model="model.masterPasswordHint"
class="form-control" api-field />
</div>
<div class="form-group" show-errors>
<label for="culture">Language/Culture</label>
<select id="culture" name="Culture" ng-model="model.culture" class="form-control" api-field>
<option value="en-US">English (US)</option>
</select>
</div>
</div>
<div class="col-sm-3 settings-photo">
<a href="http://www.gravatar.com/" target="_blank">
<img src="//www.gravatar.com/avatar/{{ main.userProfile.email | gravatar }}.jpg?s=150&d=mm"
class="img-rounded img-responsive" alt="User Image">
</a>
<a href="http://www.gravatar.com/" target="_blank" class="btn btn-link"
analytics-on="click" analytics-event="Clicked Update Photo">Update Photo</a>
</div>
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="profileForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="profileForm.$loading"></i>Save
</button>
</div>
</form>
</div>
</section>

View File

@@ -1,82 +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" id="twoFactorModelLabel"><i class="fa fa-key"></i> Two-step Login</h4>
</div>
<form name="authTwoStepForm" ng-submit="authTwoStepForm.$valid && auth(authModel)" api-form="authPromise" ng-if="!twoFactorModel">
<div class="modal-body">
<p>Current Status: <span class="label bg-green" ng-show="enabled()">ENABLED</span><span class="label bg-gray" ng-show="!enabled()">DISABLED</span></p>
<p>Two-step login helps keep your account more secure by requiring a code provided by an app on your mobile device while logging in (in addition to your master password).</p>
<p ng-show="enabled()">Two-step login is already enabled on your account. To access your two-step settings enter your master password below.</p>
<p ng-show="!enabled()">To get started with two-step login enter your master password below.</p>
<div class="callout callout-danger validation-errors" ng-show="authTwoStepForm.$errors">
<h4>Errors have occured</h4>
<ul>
<li ng-repeat="e in authTwoStepForm.$errors">{{e}}</li>
</ul>
</div>
<div class="form-group" show-errors>
<label for="masterPassword">Master Password</label>
<input type="password" id="masterPassword" name="MasterPasswordHash" ng-model="authModel.masterPassword" class="form-control" required api-field />
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="authTwoStepForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="authTwoStepForm.$loading"></i>Continue
</button>
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
</div>
</form>
<form name="updateTwoStepForm" ng-submit="updateTwoStepForm.$valid && update(updateModel)" api-form="updatePromise" ng-if="twoFactorModel">
<div class="modal-body">
<div ng-show="enabled()">
<p>Two-step login is enabled on your account. Below is the code required by your verification app.</p>
<p>Need a two-step verification app? Download one of the following:</p>
</div>
<div ng-show="!enabled()">
<p>Setting up two-step verification is easy, just follow these steps:</p>
<h4>1. Download a two-step verification app</h4>
</div>
<ul class="fa-ul">
<li><i class="fa-li fa fa-apple"></i> iOS devices: <a href="https://itunes.apple.com/en/app/authy/id494168017" target="_blank">Authy for iOS</a></li>
<li><i class="fa-li fa fa-android"></i> Android devices: <a href="https://play.google.com/store/apps/details?id=com.authy.authy" target="_blank">Authy for Android</a></li>
<li><i class="fa-li fa fa-windows"></i> Windows devices: <a href="https://www.microsoft.com/en-us/store/apps/authenticator/9wzdncrfj3rj" target="_blank">Microsoft Authenticator </a></li>
</ul>
<hr ng-show="enabled()" />
<h4 ng-show="!enabled()" style="margin-top: 30px;">2. Scan this QR code with your verification app</h4>
<div class="row">
<div class="col-md-4 text-center">
<p><img ng-src="{{twoFactorModel.qr}}" alt="QR" class="img-thumbnail" /></p>
</div>
<div class="col-sm-8">
<p><strong>Can't scan the code?</strong> You can add the code to your application manually using the following details:</p>
<ul class="list-unstyled">
<li><strong>Key:</strong> <samp>{{twoFactorModel.key}}</samp></li>
<li><strong>Account:</strong> {{account}}</li>
<li><strong>Time based:</strong> Yes</li>
</ul>
</div>
</div>
<div ng-show="!enabled()">
<div class="callout callout-danger validation-errors" ng-show="updateTwoStepForm.$errors">
<h4>Errors have occured</h4>
<ul>
<li ng-repeat="e in updateTwoStepForm.$errors">{{e}}</li>
</ul>
</div>
<h4 style="margin-top: 30px;">3. Enter the resulting verification code from the app</h4>
<div class="form-group" show-errors ng-show="!twoFactorModel.enabled">
<label for="token" class="sr-only">Verification Code</label>
<input type="number" id="token" name="Token" placeholder="Verification Code" ng-model="updateModel.token" class="form-control" ng-required="!twoFactorModel.enabled" api-field />
</div>
<p>NOTE: After enabling two-step login, you will be required to enter the current code generated by your verification app each time you log in.</p>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="updateTwoStepForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="updateTwoStepForm.$loading"></i>
<span ng-show="twoFactorModel.enabled">Disable Two-step</span>
<span ng-show="!twoFactorModel.enabled">Enable Two-step</span>
</button>
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
</div>
</form>

View File

@@ -1,9 +0,0 @@
angular
.module('bit.tools')
.controller('toolsAuditsController', function ($scope, apiService, $uibModalInstance, toastr, $analytics) {
$analytics.eventTrack('toolsAuditsController', { category: 'Modal' });
$scope.close = function () {
$uibModalInstance.dismiss('cancel');
};
});

View File

@@ -1,42 +0,0 @@
angular
.module('bit.tools')
.controller('toolsController', function ($scope, $uibModal, apiService, toastr, authService) {
$scope.import = function () {
$uibModal.open({
animation: true,
templateUrl: 'app/tools/views/toolsImport.html',
controller: 'toolsImportController',
size: 'sm'
});
};
$scope.$on('toolsImport', function (event, args) {
$scope.import();
});
$scope.export = function () {
$uibModal.open({
animation: true,
templateUrl: 'app/tools/views/toolsExport.html',
controller: 'toolsExportController',
size: 'sm'
});
};
$scope.$on('toolsExport', function (event, args) {
$scope.export();
});
$scope.audits = function () {
$uibModal.open({
animation: true,
templateUrl: 'app/tools/views/toolsAudits.html',
controller: 'toolsAuditsController'
});
};
$scope.$on('toolsAudits', function (event, args) {
$scope.audits();
});
});

View File

@@ -1,36 +0,0 @@
angular
.module('bit.tools')
.controller('toolsImportController', function ($scope, $state, apiService, $uibModalInstance, cryptoService, cipherService, toastr, importService, $analytics) {
$analytics.eventTrack('toolsImportController', { category: 'Modal' });
$scope.model = { source: 'local' };
$scope.import = function (model) {
$scope.processing = true;
var file = document.getElementById('file').files[0];
importService.import(model.source, file, importSuccess, importError);
};
function importSuccess(folders, sites, folderRelationships) {
apiService.ciphers.import({
folders: cipherService.encryptFolders(folders, cryptoService.getKey()),
sites: cipherService.encryptSites(sites, cryptoService.getKey()),
folderRelationships: folderRelationships
}, function () {
$uibModalInstance.dismiss('cancel');
$state.go('backend.vault').then(function () {
$analytics.eventTrack('Imported Data', { label: model.source });
toastr.success('Data has been successfully imported into your vault.', 'Import Success');
});
}, importError);
}
function importError() {
$uibModalInstance.dismiss('cancel');
toastr.error('Something went wrong. Try again.', 'Oh No!');
}
$scope.close = function () {
$uibModalInstance.dismiss('cancel');
};
});

View File

@@ -1,9 +0,0 @@
<section class="content-header">
<h1>
Tools
<small>helpful utilities</small>
</h1>
</section>
<section class="content">
Several tools are available in the menu to the left. More tools coming soon...
</section>

View File

@@ -1,10 +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" id="auditsModelLabel"><i class="fa fa-search"></i> Audits</h4>
</div>
<div class="modal-body">
Coming soon...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
</div>

View File

@@ -1,29 +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" id="importModelLabel"><i class="fa fa-cloud-upload"></i> Import</h4>
</div>
<form name="importForm" ng-submit="importForm.$valid && import(model)" ng-show="!processing">
<div class="modal-body">
<div class="form-group">
<label for="source">Source</label>
<select id="source" name="source" class="form-control" ng-model="model.source">
<option value="local">bitwarden</option>
<option value="lastpass">LastPass</option>
</select>
</div>
<div class="form-group">
<label for="file">File</label>
<input type="file" id="file" name="file" />
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-flat">
Import
</button>
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
</div>
</form>
<div ng-show="processing" class="modal-body text-center">
<p><i class="fa fa-cog fa-spin fa-3x"></i></p>
<p>Please wait. We are now importing all of your data. Do not close this window. You will be redirected to your vault when the import has completed.</p>
</div>

View File

@@ -1,50 +0,0 @@
angular
.module('bit.vault')
.controller('vaultAddSiteController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService, passwordService, folders, selectedFolder, $analytics) {
$analytics.eventTrack('vaultAddSiteController', { category: 'Modal' });
$scope.folders = folders;
$scope.site = {
folderId: selectedFolder ? selectedFolder.id : null
};
$scope.savePromise = null;
$scope.save = function (model) {
var site = cipherService.encryptSite(model);
$scope.savePromise = apiService.sites.post(site, function (siteResponse) {
$analytics.eventTrack('Created Site');
var decSite = cipherService.decryptSite(siteResponse);
$uibModalInstance.close(decSite);
}).$promise;
};
$scope.generatePassword = function () {
if (!$scope.site.password || confirm('Are you sure you want to overwrite the current password?')) {
$analytics.eventTrack('Generated Password From Add');
$scope.site.password = passwordService.generatePassword({ length: 10, special: true });
}
};
$scope.clipboardSuccess = function (e) {
e.clearSelection();
selectPassword(e);
};
$scope.clipboardError = function (e, password) {
if (password) {
selectPassword(e);
}
alert('Your web browser does not support easy clipboard copying. Copy it manually instead.');
};
function selectPassword(e) {
var target = $(e.trigger).parent().prev();
if (target.attr('type') === 'text') {
target.select();
}
}
$scope.close = function () {
$uibModalInstance.dismiss('close');
};
});

View File

@@ -1,177 +0,0 @@
angular
.module('bit.vault')
.controller('vaultController', function ($scope, $uibModal, apiService, $filter, cryptoService, authService, toastr, cipherService) {
$scope.sites = [];
$scope.folders = [];
$scope.loadingSites = true;
apiService.sites.list({}, function (sites) {
$scope.loadingSites = false;
var decSites = [];
for (var i = 0; i < sites.Data.length; i++) {
var decSite = {
id: sites.Data[i].Id,
folderId: sites.Data[i].FolderId,
favorite: sites.Data[i].Favorite
};
try { decSite.name = cryptoService.decrypt(sites.Data[i].Name); }
catch (err) { decSite.name = '[error: cannot decrypt]'; }
if (sites.Data[i].Username) {
try { decSite.username = cryptoService.decrypt(sites.Data[i].Username); }
catch (err) { decSite.username = '[error: cannot decrypt]'; }
}
decSites.push(decSite);
}
$scope.sites = decSites;
}, function () {
$scope.loadingSites = false;
});
$scope.loadingFolders = true;
apiService.folders.list({}, function (folders) {
$scope.loadingFolders = false;
var decFolders = [{
id: null,
name: '(none)'
}];
for (var i = 0; i < folders.Data.length; i++) {
var decFolder = {
id: folders.Data[i].Id
};
try { decFolder.name = cryptoService.decrypt(folders.Data[i].Name); }
catch (err) { decFolder.name = '[error: cannot decrypt]'; }
decFolders.push(decFolder);
}
$scope.folders = decFolders;
}, function () {
$scope.loadingFolders = false;
});
$scope.folderSort = function (item) {
if (!item.id) {
return '';
}
return item.name.toLowerCase();
};
$scope.editSite = function (site) {
var editModel = $uibModal.open({
animation: true,
templateUrl: 'app/vault/views/vaultEditSite.html',
controller: 'vaultEditSiteController',
resolve: {
siteId: function () { return site.id; },
folders: function () { return $scope.folders; }
}
});
editModel.result.then(function (editedSite) {
var site = $filter('filter')($scope.sites, { id: editedSite.id }, true);
if (site && site.length > 0) {
site[0].folderId = editedSite.folderId;
site[0].name = editedSite.name;
site[0].username = editedSite.username;
site[0].favorite = editedSite.favorite;
}
});
};
$scope.$on('vaultAddSite', function (event, args) {
$scope.addSite();
});
$scope.addSite = function (folder) {
var addModel = $uibModal.open({
animation: true,
templateUrl: 'app/vault/views/vaultAddSite.html',
controller: 'vaultAddSiteController',
resolve: {
folders: function () { return $scope.folders; },
selectedFolder: function () { return folder; }
}
});
addModel.result.then(function (addedSite) {
$scope.sites.push(addedSite);
});
};
$scope.deleteSite = function (site) {
if (!confirm('Are you sure you want to delete this site (' + site.name + ')?')) {
return;
}
apiService.sites.del({ id: site.id }, function () {
var index = $scope.sites.indexOf(site);
$scope.sites.splice(index, 1);
});
};
$scope.editFolder = function (folder) {
var editModel = $uibModal.open({
animation: true,
templateUrl: 'app/vault/views/vaultEditFolder.html',
controller: 'vaultEditFolderController',
size: 'sm',
resolve: {
folderId: function () { return folder.id; }
}
});
editModel.result.then(function (editedFolder) {
var folder = $filter('filter')($scope.folders, { id: editedFolder.id }, true);
if (folder && folder.length > 0) {
folder[0].name = editedFolder.name;
}
});
};
$scope.$on('vaultAddFolder', function (event, args) {
$scope.addFolder();
});
$scope.addFolder = function () {
var addModel = $uibModal.open({
animation: true,
templateUrl: 'app/vault/views/vaultAddFolder.html',
controller: 'vaultAddFolderController',
size: 'sm'
});
addModel.result.then(function (addedFolder) {
$scope.folders.push(addedFolder);
});
};
$scope.deleteFolder = function (folder) {
if (!confirm('Are you sure you want to delete this folder (' + folder.name + ')?')) {
return;
}
apiService.folders.del({ id: folder.id }, function () {
var index = $scope.folders.indexOf(folder);
$scope.folders.splice(index, 1);
});
};
$scope.canDeleteFolder = function (folder) {
if (!folder || !folder.id) {
return false;
}
var sites = $filter('filter')($scope.sites, { folderId: folder.id });
return sites.length === 0;
};
});

View File

@@ -1,51 +0,0 @@
angular
.module('bit.vault')
.controller('vaultEditSiteController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService, passwordService, siteId, folders, $analytics) {
$analytics.eventTrack('vaultEditSiteController', { category: 'Modal' });
$scope.folders = folders;
$scope.site = {};
apiService.sites.get({ id: siteId }, function (site) {
$scope.site = cipherService.decryptSite(site);
});
$scope.save = function (model) {
var site = cipherService.encryptSite(model);
$scope.savePromise = apiService.sites.put({ id: siteId }, site, function (siteResponse) {
$analytics.eventTrack('Edited Site');
var decSite = cipherService.decryptSite(siteResponse);
$uibModalInstance.close(decSite);
}).$promise;
};
$scope.generatePassword = function () {
if (!$scope.site.password || confirm('Are you sure you want to overwrite the current password?')) {
$analytics.eventTrack('Generated Password From Edit');
$scope.site.password = passwordService.generatePassword({ length: 10, special: true });
}
};
$scope.clipboardSuccess = function (e) {
e.clearSelection();
selectPassword(e);
};
$scope.clipboardError = function (e, password) {
if (password) {
selectPassword(e);
}
alert('Your web browser does not support easy clipboard copying. Copy it manually instead.');
};
function selectPassword(e) {
var target = $(e.trigger).parent().prev();
if (target.attr('type') === 'text') {
target.select();
}
}
$scope.close = function () {
$uibModalInstance.dismiss('cancel');
};
});

View File

@@ -1,57 +0,0 @@
<section class="content-header">
<h1>
My Vault
<small>safe and secure</small>
</h1>
</section>
<section class="content">
<div ng-show="loadingFolders && !folders.length">
<p>Loading...</p>
</div>
<div class="box" ng-repeat="folder in folders | orderBy: folderSort" ng-show="folders.length">
<div class="box-header with-border">
<h3 class="box-title"><i class="fa fa-folder-open"></i> {{folder.name}}</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" ng-click="deleteFolder(folder)" ng-show="canDeleteFolder(folder)" uib-tooltip="Delete">
<i class="fa fa-trash"></i>
</button>
<button type="button" class="btn btn-box-tool" ng-click="editFolder(folder)" ng-show="folder.id" uib-tooltip="Edit">
<i class="fa fa-pencil"></i>
</button>
<button type="button" class="btn btn-box-tool" data-widget="collapse" uib-tooltip="Collapse">
<i class="fa fa-minus"></i>
</button>
</div>
</div>
<div class="box-body" ng-class="{'no-padding': folderSites.length}">
<div ng-show="loadingSites && !folderSites.length">
<p>Loading sites...</p>
</div>
<div ng-show="!loadingSites && !folderSites.length">
<p>No sites in this folder.</p>
<button type="button" ng-click="addSite(folder)" class="btn btn-default btn-flat">Add a Site</button>
</div>
<div class="table-responsive" ng-show="folderSites.length">
<table class="table table-striped table-hover">
<thead>
<tr>
<th style="width: 75px; min-width: 75px;"></th>
<th>Site</th>
<th style="width: 300px;">Username</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="site in folderSites = (sites | filter: { folderId: folder.id } | filter: (main.searchVaultText || '') | orderBy: ['name', 'username'])">
<td>
<button type="button" ng-click="deleteSite(site)" class="btn btn-link btn-table" uib-tooltip="Delete"><i class="fa fa-lg fa-trash"></i></button>
<button type="button" ng-click="editSite(site)" class="btn btn-link btn-table" uib-tooltip="View/Edit"><i class="fa fa-lg fa-pencil"></i></button>
</td>
<td>{{site.name}} <i class="fa fa-star text-muted" uib-tooltip="Favorite" ng-show="site.favorite"></i></td>
<td>{{site.username}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>

View File

@@ -1,101 +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" id="editSiteModelLabel"><i class="fa fa-globe"></i> Site Information <small>{{site.name}}</small></h4>
</div>
<form name="editSiteForm" ng-submit="editSiteForm.$valid && save(site)" api-form="savePromise">
<div class="modal-body">
<div class="callout callout-danger validation-errors" ng-show="editSiteForm.$errors">
<h4>Errors have occured</h4>
<ul>
<li ng-repeat="e in editSiteForm.$errors">{{e}}</li>
</ul>
</div>
<div class="form-group" show-errors>
<label for="uri">URI</label>
<div class="input-group">
<input type="text" id="uri" name="Uri" ng-model="site.uri" class="form-control" placeholder="http://..." api-field />
<span class="input-group-btn">
<button tabindex="-1" class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI" tooltip-placement="left" ngclipboard
ngclipboard-error="clipboardError(e)"
data-clipboard-target="#uri">
<i class="fa fa-clipboard"></i>
</button>
<a href="{{site.uri}}" target="_blank" class="btn btn-default btn-flat" uib-tooltip="Go To Site" tooltip-placement="left">
<i class="fa fa-share"></i>
</a>
</span>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group" show-errors>
<label for="name">Name</label>
<input type="text" id="name" name="Name" ng-model="site.name" class="form-control" required api-field />
</div>
</div>
<div class="col-md-6">
<div class="form-group" show-errors>
<label for="folder">Folder</label>
<select id="folder" name="FolderId" ng-model="site.folderId" class="form-control" api-field>
<option ng-repeat="folder in folders" value="{{folder.id}}">{{folder.name}}</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group" show-errors>
<label for="username">Username</label>
<div class="input-group">
<input type="text" id="username" name="Username" ng-model="site.username" class="form-control" api-field />
<span class="input-group-btn" uib-tooltip="Copy Username" tooltip-placement="left">
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
ngclipboard-error="clipboardError(e)"
data-clipboard-target="#username">
<i class="fa fa-clipboard"></i>
</button>
</span>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group" show-errors>
<div class="pull-right password-options">
<i class="fa fa-lg fa-refresh" uib-tooltip="Generate Password" tooltip-placement="left" ng-click="generatePassword()"></i>
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Password" tooltip-placement="left" password-viewer="#password"></i>
</div>
<label for="password">Password</label>
<div class="input-group">
<input type="text" id="password-text" value="{{site.password}}" style="margin-left: -9999px;" />
<input type="password" id="password" name="Password" ng-model="site.password" class="form-control" required api-field />
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
ngclipboard-success="clipboardSuccess(e)"
ngclipboard-error="clipboardError(e, true)"
data-clipboard-target="#password-text">
<i class="fa fa-clipboard"></i>
</button>
</span>
</div>
</div>
<div style="margin: -10px 0 15px 0;" password-meter="site.password" password-meter-username="site.username" outer-class="xs"></div>
</div>
</div>
<div class="form-group" show-errors>
<label for="notes">Notes</label>
<textarea id="notes" name="Notes" class="form-control" ng-model="site.notes" api-field></textarea>
</div>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="site.favorite" name="Favorite" />
Favorite
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="editSiteForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="editSiteForm.$loading"></i>Save
</button>
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
</div>
</form>

View File

@@ -1,173 +0,0 @@
<div class="wrapper toast-target">
<header class="main-header" ng-controller="topNavController as topNav">
<a ui-sref="backend.vault" class="logo">
<span class="logo-mini"><i class="fa fa-shield"></i></span>
<span class="logo-lg"><i class="fa fa-shield"></i> <b>bit</b>warden</span>
</a>
<nav class="navbar navbar-static-top" role="navigation">
<a class="sidebar-toggle" data-toggle="offcanvas" role="button">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<li><a ui-sref="frontend.logout">Log Out</a></li>
</ul>
</div>
</nav>
</header>
<aside class="main-sidebar" ng-controller="sideNavController as sideNav">
<section class="sidebar">
<div class="user-panel">
<div class="pull-left image">
<img src="//www.gravatar.com/avatar/{{ main.userProfile.email | gravatar }}.jpg?s=45&d=mm"
class="img-circle" alt="User Image">
</div>
<div class="pull-left info">
<p>{{main.userProfile.extended && main.userProfile.extended.name ? main.userProfile.extended.name : main.userProfile.email}}</p>
<a ui-sref="frontend.logout">Log Out</a>
</div>
</div>
<form class="sidebar-form">
<label for="search" class="sr-only">Search</label>
<div class="form-group has-feedback">
<input type="text" id="search" class="form-control" placeholder="Search vault..."
ng-focus="searchVault()" ng-model="main.searchVaultText" />
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
</div>
</form>
<ul class="sidebar-menu">
<li class="header">WEB VAULT</li>
<li class="treeview" ng-class="{active: $state.includes('backend.vault')}">
<a ui-sref="backend.vault"><i class="fa fa-lock"></i> <span>My Vault</span></a>
<ul class="treeview-menu menu-open">
<li>
<a href="javascript:void(0)" ng-click="addSite()">
<i class="fa fa-plus"></i> New Site
</a>
</li>
<li>
<a href="javascript:void(0)" ng-click="addFolder()">
<i class="fa fa-folder"></i> New Folder
</a>
</li>
</ul>
</li>
<li class="treeview" ng-class="{active: $state.includes('backend.settings')}">
<a ui-sref="backend.settings"><i class="fa fa-cogs"></i> <span>Settings</span></a>
<ul class="treeview-menu">
<li>
<a href="javascript:void(0)" ng-click="changePassword()">
<i class="fa fa-circle-o"></i> Change Password
</a>
</li>
<li>
<a href="javascript:void(0)" ng-click="changeEmail()">
<i class="fa fa-circle-o"></i> Change Email
</a>
</li>
<li>
<a href="javascript:void(0)" ng-click="sessions()">
<i class="fa fa-circle-o"></i> Deauthorize Sessions
</a>
</li>
<li>
<a href="javascript:void(0)" ng-click="twoFactor()">
<i class="fa fa-circle-o"></i> Two-step Login
</a>
</li>
<li>
<a href="javascript:void(0)" ng-click="delete()">
<i class="fa fa-circle-o"></i> Delete Account
</a>
</li>
</ul>
</li>
<li class="treeview" ng-class="{active: $state.includes('backend.tools')}">
<a ui-sref="backend.tools"><i class="fa fa-wrench"></i> <span>Tools</span></a>
<ul class="treeview-menu">
<li>
<a href="javascript:void(0)" ng-click="import()">
<i class="fa fa-circle-o"></i> Import
</a>
</li>
<li>
<a href="javascript:void(0)" ng-click="export()">
<i class="fa fa-circle-o"></i> Export
</a>
</li>
<li>
<a href="javascript:void(0)" ng-click="audits()">
<i class="fa fa-circle-o"></i> Audits
</a>
</li>
</ul>
</li>
<li>
<a href="https://bitwarden.com/contact/" target="_blank"
analytics-on="click" analytics-event="Clicked Get Help">
<i class="fa fa-info-circle"></i> <span>Get Help</span>
</a>
</li>
<li class="header">
MOBILE APPS<small class="label pull-right bg-green">FREE</small>
</li>
<li>
<a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8"
target="_blank" analytics-on="click" analytics-event="Clicked iOS">
<i class="fa fa-apple"></i> <span>iOS</span>
</a>
</li>
<li>
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden"
target="_blank" analytics-on="click" analytics-event="Clicked Android">
<i class="fa fa-android"></i> <span>Android</span>
</a>
</li>
<li class="header">
BROWSER EXTENSIONS <small class="label pull-right bg-green">FREE</small>
</li>
<li>
<a href="https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb"
target="_blank" analytics-on="click" analytics-event="Clicked Chrome">
<i class="fa fa-chrome"></i> <span>Chrome</span>
</a>
</li>
<li>
<a href="javascript:void(0)"
target="_blank" analytics-on="click" analytics-event="Clicked Firefox">
<i class="fa fa-firefox"></i> <span>Firefox</span>
<small class="label pull-right bg-gray">coming very soon</small>
</a>
</li>
<li>
<a href="javascript:void(0)"
target="_blank" analytics-on="click" analytics-event="Clicked Opera">
<i class="fa fa-opera"></i> <span>Opera</span>
<small class="label pull-right bg-gray">coming very soon</small>
</a>
</li>
<li>
<a href="javascript:void(0)"
target="_blank" analytics-on="click" analytics-event="Clicked Edge">
<i class="fa fa-edge"></i> <span>Edge</span>
<small class="label pull-right bg-gray">coming soon</small>
</a>
</li>
</ul>
</section>
</aside>
<div class="content-wrapper" ui-view>
</div>
<footer class="main-footer">
<div class="pull-right hidden-xs">
<b>Version</b> {{main.version}}
</div>
<strong>Copyright &copy; <span ng-bind="currentYear"></span></strong>, bitwarden.com
</footer>
</div>

View File

@@ -1,136 +0,0 @@
<!DOCTYPE html>
<html ng-app="bit">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<base href="/" />
<title page-title>bitwarden.com Password Manager</title>
<!-- @if true !>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
<meta name="x-stylesheet-test-bs" content="" class="invisible" />
<script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName('SCRIPT'),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}('visibility','hidden',['lib\/bootstrap\/css\/bootstrap.min.css']);</script>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" />
<meta name="x-stylesheet-test-fa" content="" class="fa" />
<script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName('SCRIPT'),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}('font-family','FontAwesome',['lib\/font-awesome\/css\/font-awesome.min.css']);</script>
<link rel="stylesheet" href="css/vault.min.css" />
<!-- @endif -->
<!-- @exclude -->
<link rel="stylesheet" href="lib/bootstrap/css/bootstrap.css" />
<link rel="stylesheet" href="lib/font-awesome/css/font-awesome.css" />
<link rel="stylesheet" href="css/vault.css" />
<!-- @endexclude -->
</head>
<body ng-controller="mainController as main" class="layout-boxed skin-blue sidebar-mini {{main.bodyClass}}">
<div ui-view></div>
<!-- @if true !>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>(window.jQuery||document.write('<script src="lib\/jquery\/jquery.min.js"><\/script>'));</script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script>((window.jQuery&&window.jQuery.fn&&window.jQuery.fn.modal)||document.write('<script src="lib\/bootstrap\/js\/bootstrap.min.js"><\/script>'));</script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script>(window.angular||document.write('<script src="lib\/angular\/angular.min.js"><\/script>'));</script>
<script src="js/lib.min.js"></script>
<script src="js/app.min.js"></script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-81915606-3', 'auto');
</script>
<!-- @endif -->
<!-- @exclude -->
<script src="lib/jquery/jquery.js"></script>
<script src="lib/bootstrap/js/bootstrap.js"></script>
<script src="lib/admin-lte/js/app.js"></script>
<script src="lib/sjcl/sjcl.js"></script>
<script src="lib/sjcl/cbc.js"></script>
<script src="lib/sjcl/bitArray.js"></script>
<script src="lib/papaparse/papaparse.js"></script>
<script src="lib/clipboard/clipboard.js"></script>
<script src="lib/angular/angular.js"></script>
<script src="lib/angular-cookies/angular-cookies.js"></script>
<script src="lib/angular-messages/angular-messages.js"></script>
<script src="lib/angular-jwt/angular-jwt.js"></script>
<script src="lib/angular-resource/angular-resource.js"></script>
<script src="lib/angular-ui-router/angular-ui-router.js"></script>
<script src="lib/angular-bootstrap/angular-bootstrap-tpls.js"></script>
<script src="lib/angular-md5/angular-md5.js"></script>
<script src="lib/angular-toastr/angular-toastr.tpls.js"></script>
<script src="lib/angular-bootstrap-show-errors/showErrors.js"></script>
<script src="lib/ngstorage/ngStorage.js"></script>
<script src="lib/ngclipboard/ngclipboard.js"></script>
<script src="lib/angulartics/angulartics.js"></script>
<script src="lib/angulartics/angulartics-ga.js"></script>
<script src="app/app.js"></script>
<script src="app/settings.js"></script>
<script src="app/config.js"></script>
<script src="app/apiInterceptor.js"></script>
<script src="app/directives/directivesModule.js"></script>
<script src="app/directives/pageTitleDirective.js"></script>
<script src="app/directives/apiFormDirective.js"></script>
<script src="app/directives/apiFieldDirective.js"></script>
<script src="app/directives/masterPasswordDirective.js"></script>
<script src="app/directives/passwordMeterDirective.js"></script>
<script src="app/directives/passwordViewerDirective.js"></script>
<script src="app/services/servicesModule.js"></script>
<script src="app/services/tokenService.js"></script>
<script src="app/services/apiService.js"></script>
<script src="app/services/authService.js"></script>
<script src="app/services/cryptoService.js"></script>
<script src="app/services/cipherService.js"></script>
<script src="app/services/validationService.js"></script>
<script src="app/services/passwordService.js"></script>
<script src="app/services/importService.js"></script>
<script src="app/global/globalModule.js"></script>
<script src="app/global/mainController.js"></script>
<script src="app/global/topNavController.js"></script>
<script src="app/global/sideNavController.js"></script>
<script src="app/accounts/accountsModule.js"></script>
<script src="app/accounts/accountsLoginController.js"></script>
<script src="app/accounts/accountsLogoutController.js"></script>
<script src="app/accounts/accountsRegisterController.js"></script>
<script src="app/accounts/accountsPasswordHintController.js"></script>
<script src="app/vault/vaultModule.js"></script>
<script src="app/vault/vaultController.js"></script>
<script src="app/vault/vaultEditSiteController.js"></script>
<script src="app/vault/vaultAddSiteController.js"></script>
<script src="app/vault/vaultEditFolderController.js"></script>
<script src="app/vault/vaultAddFolderController.js"></script>
<script src="app/settings/settingsModule.js"></script>
<script src="app/settings/settingsController.js"></script>
<script src="app/settings/settingsChangePasswordController.js"></script>
<script src="app/settings/settingsChangeEmailController.js"></script>
<script src="app/settings/settingsTwoFactorController.js"></script>
<script src="app/settings/settingsSessionsController.js"></script>
<script src="app/settings/settingsDeleteController.js"></script>
<script src="app/tools/toolsModule.js"></script>
<script src="app/tools/toolsController.js"></script>
<script src="app/tools/toolsImportController.js"></script>
<script src="app/tools/toolsExportController.js"></script>
<script src="app/tools/toolsAuditsController.js"></script>
<!-- @endexclude -->
</body>
</html>

15
src/app-id.json Normal file
View File

@@ -0,0 +1,15 @@
{
"trustedFacets": [
{
"version": {
"major": 1,
"minor": 0
},
"ids": [
"https://vault.bitwarden.com",
"ios:bundle-id:com.8bit.bitwarden",
"android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI"
]
}
]
}

View File

@@ -0,0 +1,278 @@
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

@@ -0,0 +1,45 @@
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,11 +1,12 @@
angular
.module('bit.accounts')
.controller('accountsPasswordHintController', function ($scope, $rootScope, apiService) {
.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

@@ -0,0 +1,21 @@
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

@@ -0,0 +1,13 @@
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

@@ -0,0 +1,92 @@
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

@@ -0,0 +1,40 @@
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

@@ -0,0 +1,28 @@
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

@@ -0,0 +1,36 @@
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

@@ -1,7 +1,7 @@
<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 occured</h4>
<h4>Errors have occurred</h4>
<ul>
<li ng-repeat="e in loginForm.$errors">{{e}}</li>
</ul>
@@ -35,7 +35,15 @@
</div>
<hr />
<ul>
<li><a ui-sref="frontend.register">Create a new account</a></li>
<li><a ui-sref="frontend.passwordHint">Get master password hint</a></li>
<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

@@ -0,0 +1,164 @@
<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">
<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">
<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">
<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

@@ -0,0 +1,32 @@
<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

@@ -13,7 +13,7 @@
<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 occured</h4>
<h4>Errors have occurred</h4>
<ul>
<li ng-repeat="e in passwordHintForm.$errors">{{e}}</li>
</ul>

View File

@@ -0,0 +1,56 @@
<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

@@ -0,0 +1,39 @@
<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

@@ -9,12 +9,16 @@
<h4>Account Created!</h4>
<p>You may now log in to your new account.</p>
</div>
<a ui-sref="frontend.login.info">Ready to log in?</a>
<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 occured</h4>
<h4>Errors have occurred</h4>
<ul>
<li ng-repeat="e in registerForm.$errors">{{e}}</li>
</ul>
@@ -22,7 +26,7 @@
<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 />
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>
@@ -60,7 +64,7 @@
</div>
<div class="row">
<div class="col-xs-7">
<a ui-sref="frontend.login.info">Already have an account?</a>
<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">
@@ -68,6 +72,11 @@
</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

@@ -0,0 +1,25 @@
<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

@@ -0,0 +1,8 @@
<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

@@ -0,0 +1,21 @@
<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

@@ -3,18 +3,25 @@
'ui.router',
'ngMessages',
'angular-jwt',
'angular-md5',
'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.tools',
'bit.organization',
'bit.reports'
]);

368
src/app/config.js Normal file
View File

@@ -0,0 +1,368 @@
angular
.module('bit')
.config(function ($stateProvider, $urlRouterProvider, $httpProvider, jwtInterceptorProvider, jwtOptionsProvider,
$uibTooltipProvider, toastrConfig, $locationProvider, $qProvider, appSettings
// @if !selfHosted
, stripeProvider
// @endif
) {
angular.extend(appSettings, window.bitwardenAppSettings);
$qProvider.errorOnUnhandledRejections(false);
$locationProvider.hashPrefix('');
jwtOptionsProvider.config({
urlParam: 'access_token',
whiteListedDomains: appSettings.whitelistDomains
});
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;
}
refreshPromise = authService.refreshAccessToken().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.vaultLogins = $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');
}
});
}
});
});

138
src/app/constants.js Normal file
View File

@@ -0,0 +1,138 @@
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
},
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
},
personal: {
basePrice: 1,
annualBasePrice: 12,
baseSeats: 5,
seatPrice: 1,
annualSeatPrice: 12,
maxAdditionalSeats: 5,
annualPlanType: 'personalAnnually',
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,7 +1,7 @@
angular
.module('bit.directives')
.directive('apiForm', function ($rootScope, validationService) {
.directive('apiForm', function ($rootScope, validationService, $timeout) {
return {
require: 'form',
restrict: 'A',
@@ -25,11 +25,21 @@ angular
form.$loading = true;
promise.then(function success(response) {
form.$loading = false;
$timeout(function () {
form.$loading = false;
});
}, function failure(reason) {
form.$loading = false;
validationService.addErrors(form, reason);
scope.$broadcast('show-errors-check-validity');
$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

@@ -0,0 +1,151 @@
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

@@ -0,0 +1,38 @@
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

@@ -6,9 +6,9 @@ angular
link: function (scope, element) {
var listener = function (event, toState, toParams, fromState, fromParams) {
// Default title
var title = 'bitwarden Password Manager';
var title = 'bitwarden Web Vault';
if (toState.data && toState.data.pageTitle) {
title = toState.data.pageTitle + ' - bitwarden Password Manager';
title = toState.data.pageTitle + ' - ' + title;
}
$timeout(function () {

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