mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Compare commits
1257 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a815b0bdf | ||
|
|
b7525e1e7e | ||
|
|
c3f64fe9c4 | ||
|
|
34f6bc2403 | ||
|
|
9ecec972ca | ||
|
|
80febf97d3 | ||
|
|
71073874eb | ||
|
|
f3dfeac125 | ||
|
|
f12e73519e | ||
|
|
19f7dda4cc | ||
|
|
221397b159 | ||
|
|
91766ecea3 | ||
|
|
191be134f9 | ||
|
|
56d279ae1e | ||
|
|
3e61464dac | ||
|
|
85ca10dbb3 | ||
|
|
eaf08c45d9 | ||
|
|
bcb44e8cf7 | ||
|
|
d215e0716e | ||
|
|
f635162832 | ||
|
|
c892480086 | ||
|
|
ea49d17c47 | ||
|
|
39c32b0e62 | ||
|
|
5cdfa35a76 | ||
|
|
147b3ff993 | ||
|
|
662c229de1 | ||
|
|
c90cb2ae6e | ||
|
|
864d070656 | ||
|
|
e8ac2b561a | ||
|
|
c71a432ce4 | ||
|
|
e3ca470a6a | ||
|
|
e7c6fbf423 | ||
|
|
6cef5e614d | ||
|
|
33cf77559f | ||
|
|
b5085d8004 | ||
|
|
80af20ef54 | ||
|
|
4a324ca764 | ||
|
|
85db64ed70 | ||
|
|
d72ba456ae | ||
|
|
b4a0913994 | ||
|
|
6fb8a9ef2f | ||
|
|
4e58708f12 | ||
|
|
32876367c2 | ||
|
|
226aa0b3ba | ||
|
|
c37359cdfd | ||
|
|
d2d89c252a | ||
|
|
b76f1d3c8c | ||
|
|
9ff34a12a2 | ||
|
|
c5f6ce3fe5 | ||
|
|
965d556ac9 | ||
|
|
e52d3f21d1 | ||
|
|
00576053d0 | ||
|
|
1f040efb7f | ||
|
|
3b5e9775c5 | ||
|
|
ea0653460e | ||
|
|
6ed80eb6c9 | ||
|
|
12bdd87705 | ||
|
|
9bdf7ff174 | ||
|
|
2ab2f757a0 | ||
|
|
f830de8d13 | ||
|
|
7eb329db6b | ||
|
|
fecb980c9b | ||
|
|
600218cf7b | ||
|
|
8eb48e4311 | ||
|
|
ac33d2f37c | ||
|
|
bfc462cbec | ||
|
|
8b8bd88adf | ||
|
|
bc768b773b | ||
|
|
950e5a57b1 | ||
|
|
52a4fc8e93 | ||
|
|
dd5c840b61 | ||
|
|
337c01daf8 | ||
|
|
5a9da2a97a | ||
|
|
38c1a0f383 | ||
|
|
2b1718945f | ||
|
|
a6319a3865 | ||
|
|
35241307d3 | ||
|
|
ed8aaa5505 | ||
|
|
a25f6dee73 | ||
|
|
9aa8699617 | ||
|
|
94f671ca6b | ||
|
|
7c9016bf4a | ||
|
|
98d18fb097 | ||
|
|
dc69887628 | ||
|
|
7c882ed40c | ||
|
|
c8909beedd | ||
|
|
15fc4e5f2d | ||
|
|
229b9cf40a | ||
|
|
17ae441c22 | ||
|
|
9a70c0e8a7 | ||
|
|
2a41fee1ff | ||
|
|
f385c3773c | ||
|
|
4b4e847816 | ||
|
|
a70c4c250c | ||
|
|
950e75e7e3 | ||
|
|
2cbf2006c6 | ||
|
|
14c4095a4e | ||
|
|
c755e8a90d | ||
|
|
7c1d824636 | ||
|
|
f71be112c2 | ||
|
|
8a4dba6317 | ||
|
|
87c72bd595 | ||
|
|
f9e756402f | ||
|
|
f576bbd3e1 | ||
|
|
f57d54249c | ||
|
|
20dca960ef | ||
|
|
fe9df3977a | ||
|
|
c5bac554a7 | ||
|
|
0e791fd0ec | ||
|
|
6fb9aafa03 | ||
|
|
12f7f764dc | ||
|
|
d9a09a815a | ||
|
|
489c7934bf | ||
|
|
5cc1438e8b | ||
|
|
6ee2f05fb7 | ||
|
|
695dc98010 | ||
|
|
cbc975399c | ||
|
|
25c643a1a3 | ||
|
|
2a16be2fb2 | ||
|
|
105a7870bf | ||
|
|
c05442dea8 | ||
|
|
370197aea3 | ||
|
|
cf09918a94 | ||
|
|
65713477de | ||
|
|
9d4f29f0a9 | ||
|
|
85429bc505 | ||
|
|
5b66143fbe | ||
|
|
ca78953889 | ||
|
|
865d475083 | ||
|
|
6b9c9f9f78 | ||
|
|
97dfb6fdec | ||
|
|
473f5bfb62 | ||
|
|
183f5fea5c | ||
|
|
69e059ba01 | ||
|
|
3544c96a64 | ||
|
|
56c2515f01 | ||
|
|
90d3639796 | ||
|
|
a3e994fc95 | ||
|
|
98b3cfaaa0 | ||
|
|
a8f28af2b3 | ||
|
|
c2603c3f53 | ||
|
|
949433b65f | ||
|
|
dda1a9ecc4 | ||
|
|
abdb40179c | ||
|
|
c1efe268d0 | ||
|
|
58818dabc5 | ||
|
|
24ee5c2d5d | ||
|
|
93cec9a2d6 | ||
|
|
fe1c5b4b38 | ||
|
|
e3e94ede65 | ||
|
|
b9ea3de860 | ||
|
|
b99df5905f | ||
|
|
a5246df3ed | ||
|
|
f4127a575b | ||
|
|
65860b166f | ||
|
|
59f5304d87 | ||
|
|
3d4848da90 | ||
|
|
f43fd34a8c | ||
|
|
bb68303b03 | ||
|
|
236bcdfb68 | ||
|
|
6c7df2788e | ||
|
|
b3a344635a | ||
|
|
4ca29a4a75 | ||
|
|
de35a62984 | ||
|
|
ef886c8742 | ||
|
|
1b754b435b | ||
|
|
50b7ce764e | ||
|
|
090fa76d15 | ||
|
|
898137c8a5 | ||
|
|
901a9ae9d7 | ||
|
|
40a5c6f55e | ||
|
|
6d0651a1cb | ||
|
|
ee4d2400c9 | ||
|
|
5131ebb9c9 | ||
|
|
0a86709440 | ||
|
|
9d5b9f0bde | ||
|
|
19fa769bd3 | ||
|
|
de8e2d1be7 | ||
|
|
f546d682bd | ||
|
|
197d1c673c | ||
|
|
16e6a88ccf | ||
|
|
de43eb4a57 | ||
|
|
ff64ad8df0 | ||
|
|
e55408424e | ||
|
|
3c9354ba2f | ||
|
|
2382d5028b | ||
|
|
7346958b27 | ||
|
|
78e12775e4 | ||
|
|
a76053be58 | ||
|
|
028731458b | ||
|
|
1cee1c6e8f | ||
|
|
1f6dd079cd | ||
|
|
faddfe8506 | ||
|
|
223cd61220 | ||
|
|
8278a8f3e4 | ||
|
|
8bd9eafa37 | ||
|
|
9a23d5fa97 | ||
|
|
4557366154 | ||
|
|
8f95ba03ab | ||
|
|
89b7672630 | ||
|
|
49f948844f | ||
|
|
d1395e37fd | ||
|
|
06de7b5176 | ||
|
|
56b9cb5c9e | ||
|
|
e4a684ff10 | ||
|
|
e4f12ed47f | ||
|
|
6fcda290c7 | ||
|
|
243a00e326 | ||
|
|
d274a83c24 | ||
|
|
bdb95e58e6 | ||
|
|
0650cafb28 | ||
|
|
786f6953e7 | ||
|
|
a1d52af0ba | ||
|
|
1da85c96cf | ||
|
|
84dc1d1b74 | ||
|
|
63aa55baf1 | ||
|
|
3b28e68e31 | ||
|
|
19d835c793 | ||
|
|
df9282e759 | ||
|
|
c679726564 | ||
|
|
7e6f9eb67a | ||
|
|
3cfe8bf751 | ||
|
|
a1495a8f0c | ||
|
|
fe6a40f7d0 | ||
|
|
e5733b83a0 | ||
|
|
1dc4f851cb | ||
|
|
ba2debf577 | ||
|
|
470a767eaf | ||
|
|
8e3d5b99c5 | ||
|
|
763e43905a | ||
|
|
ab4005ae00 | ||
|
|
0c61e48977 | ||
|
|
8526ad17c4 | ||
|
|
ee91f4610e | ||
|
|
1afc05310e | ||
|
|
ed381bcf0a | ||
|
|
59aaa07e3b | ||
|
|
4d759a6995 | ||
|
|
ce00587041 | ||
|
|
50aad69189 | ||
|
|
2dc04fa041 | ||
|
|
6dd21fe9e9 | ||
|
|
98d3b42728 | ||
|
|
6d225beb46 | ||
|
|
1f7ca7386a | ||
|
|
455df290bf | ||
|
|
2d2192f2f6 | ||
|
|
175d5ec57f | ||
|
|
255886df53 | ||
|
|
f0bc2d9c9b | ||
|
|
dc778ea578 | ||
|
|
bd0d321dba | ||
|
|
57e13c25b5 | ||
|
|
b428660f92 | ||
|
|
a8618f1c79 | ||
|
|
716619e7f1 | ||
|
|
b8da4cf6fc | ||
|
|
9432abbd1a | ||
|
|
edef454043 | ||
|
|
febc3093a9 | ||
|
|
ddd832d016 | ||
|
|
137bb3a4c2 | ||
|
|
5ade229cb9 | ||
|
|
a6bd9ecfa3 | ||
|
|
1cb396dc51 | ||
|
|
a27357213a | ||
|
|
2d4676ed7e | ||
|
|
3e9ac965e8 | ||
|
|
a45c77bda9 | ||
|
|
e659fd1262 | ||
|
|
7459f1d1ad | ||
|
|
637120d0c5 | ||
|
|
cd131b2d21 | ||
|
|
90dd0d0b9b | ||
|
|
3358846d41 | ||
|
|
473fc1a766 | ||
|
|
ecfecd295a | ||
|
|
b774091b83 | ||
|
|
f49640cbe6 | ||
|
|
8b4fadde2b | ||
|
|
1b94b22360 | ||
|
|
b090de0da1 | ||
|
|
0294c2cb6d | ||
|
|
d9bcce781a | ||
|
|
3451322a38 | ||
|
|
ba604fcd3c | ||
|
|
c7df888d39 | ||
|
|
7b80505f69 | ||
|
|
de5914194e | ||
|
|
e9da73b930 | ||
|
|
15cc46bba5 | ||
|
|
417743ccdd | ||
|
|
0f04286783 | ||
|
|
3e0f2126b3 | ||
|
|
656d17cc07 | ||
|
|
35bb106654 | ||
|
|
634e5e27d3 | ||
|
|
0f9186628b | ||
|
|
6267ca52fc | ||
|
|
06b32dc6ec | ||
|
|
93582da044 | ||
|
|
d830499c76 | ||
|
|
30d1a119f3 | ||
|
|
5efab1d495 | ||
|
|
1228010488 | ||
|
|
b8921713eb | ||
|
|
0b779dfd3d | ||
|
|
311baaa3d1 | ||
|
|
6f75e0bba0 | ||
|
|
6193bf431d | ||
|
|
7baf72d3db | ||
|
|
ba3b2fbed1 | ||
|
|
9d4e0849d6 | ||
|
|
1a51a7bf5b | ||
|
|
7d42c4eaa0 | ||
|
|
1d60e881ee | ||
|
|
fda8155894 | ||
|
|
f089b5e3d1 | ||
|
|
f578ebe4ef | ||
|
|
b97378dd40 | ||
|
|
32f62b7ceb | ||
|
|
db43f817f7 | ||
|
|
3c45e7dac9 | ||
|
|
f1584ad7d7 | ||
|
|
8f503f4f99 | ||
|
|
499ecf9c39 | ||
|
|
9cf15e37ce | ||
|
|
6d731e2939 | ||
|
|
1e7c2c2362 | ||
|
|
ae51847f02 | ||
|
|
738eaa6ca7 | ||
|
|
70dbca67e7 | ||
|
|
16c3d4c253 | ||
|
|
1f62b9fdcb | ||
|
|
bd070ff066 | ||
|
|
3bb667f524 | ||
|
|
463c1f8b77 | ||
|
|
c5d575b9b6 | ||
|
|
d509637623 | ||
|
|
37026e556f | ||
|
|
f7b9416460 | ||
|
|
1ac22c6d48 | ||
|
|
66cb7f9bf0 | ||
|
|
87f7b782dc | ||
|
|
15d6e4937f | ||
|
|
dec6b17aa4 | ||
|
|
4a2b3d6293 | ||
|
|
04ef16a94b | ||
|
|
fd88a066da | ||
|
|
7267045475 | ||
|
|
318b750603 | ||
|
|
38e87b1af0 | ||
|
|
ce96a32af3 | ||
|
|
0ec9045849 | ||
|
|
b9895ba79a | ||
|
|
ff65297275 | ||
|
|
c647c0f509 | ||
|
|
5760efdaa1 | ||
|
|
bf5d8cab1c | ||
|
|
890bf49294 | ||
|
|
a097793b0d | ||
|
|
4b4bedaef3 | ||
|
|
246d605e5c | ||
|
|
f43319ba7a | ||
|
|
09ef907673 | ||
|
|
e345bbcae0 | ||
|
|
381da132f8 | ||
|
|
998a63612f | ||
|
|
63ccc49ce2 | ||
|
|
ce3b4cd817 | ||
|
|
28f4ed9144 | ||
|
|
4bd47f728a | ||
|
|
03dfda7a17 | ||
|
|
1717960a8c | ||
|
|
bb14aa821b | ||
|
|
fd8128dfe9 | ||
|
|
9b67f6e398 | ||
|
|
e59277742d | ||
|
|
53d23ec831 | ||
|
|
a2a6d89908 | ||
|
|
be29e6d847 | ||
|
|
c26a6a5252 | ||
|
|
ce01fe6141 | ||
|
|
39ff952667 | ||
|
|
cafc65ffa5 | ||
|
|
cccd2abb55 | ||
|
|
cb1a62ee27 | ||
|
|
51efa59728 | ||
|
|
aed5db0a8c | ||
|
|
0599dd1525 | ||
|
|
989f4c3aa5 | ||
|
|
22093d5111 | ||
|
|
7022bf005f | ||
|
|
68b8ad7e28 | ||
|
|
306f8a43e1 | ||
|
|
e46f3073b4 | ||
|
|
ed65bcf185 | ||
|
|
9633800977 | ||
|
|
1fb4f2946a | ||
|
|
d73b01674f | ||
|
|
271510ffb5 | ||
|
|
16b29c6116 | ||
|
|
367104f0a7 | ||
|
|
7979953f33 | ||
|
|
5ffd13e2c8 | ||
|
|
edb1700218 | ||
|
|
9729a2595d | ||
|
|
7c3468fbcb | ||
|
|
94c5bdb5aa | ||
|
|
e714d61a66 | ||
|
|
14ecc534e0 | ||
|
|
e18f76d2b0 | ||
|
|
3edf761549 | ||
|
|
27f18c1630 | ||
|
|
314ab61349 | ||
|
|
3666ee5a87 | ||
|
|
1f3ecbab33 | ||
|
|
4aacc06af0 | ||
|
|
ddab383b55 | ||
|
|
edef3f90f1 | ||
|
|
85fd36f5c3 | ||
|
|
d16f599db9 | ||
|
|
ba6451856a | ||
|
|
f1651078e4 | ||
|
|
a6aef345d5 | ||
|
|
4df4f57de3 | ||
|
|
5bd9a9a81d | ||
|
|
a0ac8ec9c2 | ||
|
|
2bac2f1a39 | ||
|
|
ab353d8498 | ||
|
|
f054365a46 | ||
|
|
cfc3fae67c | ||
|
|
5d8e32222a | ||
|
|
21a126f31f | ||
|
|
d21540878e | ||
|
|
894ab16b35 | ||
|
|
945d661a1e | ||
|
|
a176a4819f | ||
|
|
6892badf36 | ||
|
|
5db9e9531f | ||
|
|
7ebd18b00d | ||
|
|
9f1c7a0a32 | ||
|
|
561120c51c | ||
|
|
b93b9feee4 | ||
|
|
ea6317e3a2 | ||
|
|
5ecb26b032 | ||
|
|
c1a0818376 | ||
|
|
fbc756c6e3 | ||
|
|
8f258a2d05 | ||
|
|
d4d01cc186 | ||
|
|
5098153fde | ||
|
|
63a56b359b | ||
|
|
38a95c3745 | ||
|
|
f05c6ccddd | ||
|
|
de9780a756 | ||
|
|
b34e511ddc | ||
|
|
3de3c7a189 | ||
|
|
d256a872fc | ||
|
|
3db86e2a6b | ||
|
|
6436bb65e2 | ||
|
|
059df9c45d | ||
|
|
a18a591f0a | ||
|
|
08dc36fbb0 | ||
|
|
a0ace6e70f | ||
|
|
a52aadd37d | ||
|
|
0fb66e247a | ||
|
|
ecddc1691f | ||
|
|
b0f683bacf | ||
|
|
a89cf28812 | ||
|
|
cc9410602c | ||
|
|
e814b8ef09 | ||
|
|
ce34ef902f | ||
|
|
db5a5e1b37 | ||
|
|
9e3f3e324c | ||
|
|
a4ff241574 | ||
|
|
28197970bd | ||
|
|
7aa4450556 | ||
|
|
dabc37a1ef | ||
|
|
4315000905 | ||
|
|
8d4bd1171b | ||
|
|
82c5a53f5d | ||
|
|
29d0803a5c | ||
|
|
8f14ced5af | ||
|
|
9cafb76781 | ||
|
|
22c1b0627e | ||
|
|
995835a96c | ||
|
|
bf796fe80c | ||
|
|
425029783b | ||
|
|
8f399f3c64 | ||
|
|
192eb7e7c9 | ||
|
|
48a36649fa | ||
|
|
6b83b53401 | ||
|
|
c953c2b93f | ||
|
|
7cdce165fb | ||
|
|
b393064f26 | ||
|
|
31bf22063e | ||
|
|
cd9a43f359 | ||
|
|
c326e03eb2 | ||
|
|
f0a986aa04 | ||
|
|
4225da355d | ||
|
|
14bac6a744 | ||
|
|
05cc9b45e6 | ||
|
|
dba596bf35 | ||
|
|
db39d58ea8 | ||
|
|
c0f38216ef | ||
|
|
3643222b3c | ||
|
|
551217ea38 | ||
|
|
24bf1363ab | ||
|
|
08b2184e12 | ||
|
|
b73161882c | ||
|
|
e2186ecd62 | ||
|
|
b407402f3f | ||
|
|
8bb4132458 | ||
|
|
443822fd52 | ||
|
|
68427fd2de | ||
|
|
c3d3369601 | ||
|
|
3c5022d628 | ||
|
|
832ddddc58 | ||
|
|
0fc1415a06 | ||
|
|
1ab408c591 | ||
|
|
3160d3f275 | ||
|
|
d083f1ddc3 | ||
|
|
5fbc09b135 | ||
|
|
6282fabf98 | ||
|
|
2b528bad97 | ||
|
|
c3be8195fd | ||
|
|
39471d0421 | ||
|
|
7a50c0536c | ||
|
|
4ccd9501a8 | ||
|
|
75c05a4a85 | ||
|
|
ca7e12370f | ||
|
|
8bc9dafff2 | ||
|
|
dcb0416fd6 | ||
|
|
bbb69bba26 | ||
|
|
c1838b48ff | ||
|
|
d53f40002c | ||
|
|
866954b180 | ||
|
|
befa9cbf08 | ||
|
|
859f44db43 | ||
|
|
cca9c3c561 | ||
|
|
27e68e4c75 | ||
|
|
5c92350ed2 | ||
|
|
b94c62d1e5 | ||
|
|
de888d8a37 | ||
|
|
f8d6816101 | ||
|
|
119c6d5817 | ||
|
|
aaa21daa29 | ||
|
|
10f41bf288 | ||
|
|
91582691d8 | ||
|
|
463efc2254 | ||
|
|
0333354271 | ||
|
|
b85f56c681 | ||
|
|
be491be2cd | ||
|
|
4be4a8115d | ||
|
|
c0eb499f4d | ||
|
|
1b43f3facd | ||
|
|
26d41d3cb9 | ||
|
|
179765f6e4 | ||
|
|
df2e332134 | ||
|
|
2952f9d158 | ||
|
|
3c9face597 | ||
|
|
25f2e9c1b7 | ||
|
|
a6f8e1b9a3 | ||
|
|
d832031cec | ||
|
|
7a1a3ab64d | ||
|
|
19491a684e | ||
|
|
757224287e | ||
|
|
c9b5426f6f | ||
|
|
bf885c184f | ||
|
|
0d2bf4f7a1 | ||
|
|
01ffc68fc2 | ||
|
|
16892239fb | ||
|
|
d5765d8814 | ||
|
|
8d6a96074d | ||
|
|
f54884eb79 | ||
|
|
828149b2d6 | ||
|
|
501c4fc263 | ||
|
|
1d0b45e17d | ||
|
|
a0f7ed68fb | ||
|
|
7bd0c17188 | ||
|
|
1ea9d28523 | ||
|
|
8a3fb92bbe | ||
|
|
de3a9b9903 | ||
|
|
9834f3d2aa | ||
|
|
ac079b9d88 | ||
|
|
9e96906f32 | ||
|
|
90c079e743 | ||
|
|
4ecf307285 | ||
|
|
6cf4c453d9 | ||
|
|
d2899d14c7 | ||
|
|
f3b438d514 | ||
|
|
2997f694f8 | ||
|
|
b78ab4db27 | ||
|
|
37dddea515 | ||
|
|
e307d1e87d | ||
|
|
62e1dbb642 | ||
|
|
b8a425f530 | ||
|
|
cafb6fa694 | ||
|
|
0482ddea2c | ||
|
|
b411176c8d | ||
|
|
2f13449cb6 | ||
|
|
b0c1b7b683 | ||
|
|
7e8978c7fc | ||
|
|
d58b422bd0 | ||
|
|
3563601382 | ||
|
|
d42e6ca3fd | ||
|
|
7f0d8c99e3 | ||
|
|
48a67dc2b3 | ||
|
|
8d0b42492d | ||
|
|
e4076e95dd | ||
|
|
30a2b878f6 | ||
|
|
e17f94a67d | ||
|
|
4dd60c3844 | ||
|
|
9d76990f24 | ||
|
|
ed3d15f075 | ||
|
|
2c36a2aa96 | ||
|
|
16930aa422 | ||
|
|
263f5ba147 | ||
|
|
6a60c00e22 | ||
|
|
f3eaf644b0 | ||
|
|
a57110b935 | ||
|
|
cae8beaa8f | ||
|
|
df94d81d07 | ||
|
|
f03c22cc07 | ||
|
|
5b31fe37f2 | ||
|
|
c60a596995 | ||
|
|
b52ecd8085 | ||
|
|
4323341d19 | ||
|
|
e13992ba27 | ||
|
|
52a4317d09 | ||
|
|
d53187935b | ||
|
|
0d6c96e38b | ||
|
|
b0832578a4 | ||
|
|
805393b4db | ||
|
|
c3653577c6 | ||
|
|
1eb5a99ba3 | ||
|
|
a035d73545 | ||
|
|
79fc3056a6 | ||
|
|
e44cf6e7ee | ||
|
|
641c76ae62 | ||
|
|
1efcd69148 | ||
|
|
49ee41f7d3 | ||
|
|
598c7ea068 | ||
|
|
001a116c8b | ||
|
|
106e71fe54 | ||
|
|
cd93d6cc32 | ||
|
|
d63c89bae7 | ||
|
|
fb3a7733a3 | ||
|
|
852363cb77 | ||
|
|
7f6ee21a8e | ||
|
|
2963516d5c | ||
|
|
1f26ff5c80 | ||
|
|
de3f310082 | ||
|
|
4af2edafd3 | ||
|
|
4de08f2e71 | ||
|
|
d978e1dfa3 | ||
|
|
f828288b84 | ||
|
|
7a36f13034 | ||
|
|
422b48fa36 | ||
|
|
fe9e29a057 | ||
|
|
88c302ca2e | ||
|
|
52f3032483 | ||
|
|
b13edfeeae | ||
|
|
4046339569 | ||
|
|
52f4a9d961 | ||
|
|
ca0fb6d66a | ||
|
|
7c93c82d24 | ||
|
|
3b71760f9e | ||
|
|
c4d2045884 | ||
|
|
d28c59544f | ||
|
|
acff0b19d6 | ||
|
|
94bfcb2865 | ||
|
|
1bb6244337 | ||
|
|
a132ec4fd7 | ||
|
|
8291fa0ce1 | ||
|
|
37364ecd7e | ||
|
|
48d9e626f5 | ||
|
|
f0fbf664d4 | ||
|
|
7b8b4dc164 | ||
|
|
21635dd728 | ||
|
|
c7802940b1 | ||
|
|
f7b60febe9 | ||
|
|
6c93a63c06 | ||
|
|
c44a638644 | ||
|
|
0d3fead0f3 | ||
|
|
5ba4b37610 | ||
|
|
44a2d071ae | ||
|
|
3b22764368 | ||
|
|
11336da6df | ||
|
|
a0e5591f8e | ||
|
|
e952073c3c | ||
|
|
9bdd0d116a | ||
|
|
05c8a39e6d | ||
|
|
8fa6ff48cf | ||
|
|
7a31783ea4 | ||
|
|
96585b183d | ||
|
|
f81e7b02dc | ||
|
|
f7fbdf2081 | ||
|
|
06a877c755 | ||
|
|
30abd52189 | ||
|
|
6af0e62976 | ||
|
|
84a36a18d6 | ||
|
|
595cf6c375 | ||
|
|
4262e2cc1d | ||
|
|
c134986bbf | ||
|
|
d9981e1d71 | ||
|
|
2b6d7ec361 | ||
|
|
aaa91e50b7 | ||
|
|
ff9030e7af | ||
|
|
cc39e6402e | ||
|
|
c89b641b88 | ||
|
|
465304b004 | ||
|
|
63033ca12d | ||
|
|
f019dc6575 | ||
|
|
d15e3a64e7 | ||
|
|
7099b0579a | ||
|
|
2c2d08c7cc | ||
|
|
671e9ccb1c | ||
|
|
f93c5cb9a1 | ||
|
|
8c7f1c4359 | ||
|
|
d7c1c6efa1 | ||
|
|
30a2301697 | ||
|
|
c639186c60 | ||
|
|
5618cfb031 | ||
|
|
7e97c04d1e | ||
|
|
4d25077108 | ||
|
|
635caa9ad0 | ||
|
|
2772bffd09 | ||
|
|
995fc96a5d | ||
|
|
4660ad824d | ||
|
|
801049cbd0 | ||
|
|
09a7b4ea90 | ||
|
|
226c201925 | ||
|
|
4749a3da89 | ||
|
|
ae567ab462 | ||
|
|
bf382889d3 | ||
|
|
2272bcac71 | ||
|
|
a209c9450a | ||
|
|
2539a9c23f | ||
|
|
e95ede73ba | ||
|
|
ad970b1cb7 | ||
|
|
161e7d1763 | ||
|
|
3a823d32b5 | ||
|
|
4c46317f24 | ||
|
|
0271c223a6 | ||
|
|
9a4669067d | ||
|
|
53f3124345 | ||
|
|
b49a40b077 | ||
|
|
fb10da8ce3 | ||
|
|
b286c1a29b | ||
|
|
e5e7712716 | ||
|
|
2beb22e8cf | ||
|
|
747b5608e8 | ||
|
|
dad3cd9414 | ||
|
|
0c1fb3e118 | ||
|
|
afe223f410 | ||
|
|
e1ec50bcad | ||
|
|
04da844b22 | ||
|
|
f944910975 | ||
|
|
96b8467859 | ||
|
|
84554174ac | ||
|
|
65e03e707c | ||
|
|
fd9fcbea38 | ||
|
|
a1dfd7493a | ||
|
|
d4759d4056 | ||
|
|
d879518233 | ||
|
|
ef6cb3779b | ||
|
|
fc22114855 | ||
|
|
6b1eb5a479 | ||
|
|
bbd8a1265b | ||
|
|
444f63db42 | ||
|
|
f46a6aefea | ||
|
|
10792f714e | ||
|
|
d6d535ed9e | ||
|
|
55a50fac83 | ||
|
|
a7beed334f | ||
|
|
83274ad7a4 | ||
|
|
24056163dd | ||
|
|
79383ed693 | ||
|
|
d2da3f6e00 | ||
|
|
c40193c861 | ||
|
|
715835c12f | ||
|
|
0242de9145 | ||
|
|
b075f25d7c | ||
|
|
0b34b7a980 | ||
|
|
f291b24a7a | ||
|
|
9707fa34e4 | ||
|
|
cd19e0c9e4 | ||
|
|
38883b9550 | ||
|
|
f761733d0b | ||
|
|
842b157955 | ||
|
|
87f0e2be0e | ||
|
|
c3bea80ec7 | ||
|
|
a1529bc4e9 | ||
|
|
ccb7ede4fa | ||
|
|
1dbf831bda | ||
|
|
ea4d772dda | ||
|
|
25536e10ef | ||
|
|
51e30b2f7a | ||
|
|
47cb20f01e | ||
|
|
204ee72926 | ||
|
|
b9cbc1546c | ||
|
|
bc8892a237 | ||
|
|
b62950fa2b | ||
|
|
ab12c990bc | ||
|
|
abed4df973 | ||
|
|
76da9b1f18 | ||
|
|
11cbe3b7bb | ||
|
|
08b432775e | ||
|
|
49dbf4945f | ||
|
|
ff729608e1 | ||
|
|
b380d723b7 | ||
|
|
ed13644a02 | ||
|
|
8a90f562ef | ||
|
|
dfd791ecf9 | ||
|
|
8df16f28e7 | ||
|
|
1fb220c25e | ||
|
|
b24f892f60 | ||
|
|
5d81ed6a96 | ||
|
|
7ff79a0fdd | ||
|
|
7b4cf53ec4 | ||
|
|
9c7b47c277 | ||
|
|
547c7b8b70 | ||
|
|
1d70434ed1 | ||
|
|
06d53d350d | ||
|
|
742d7240f7 | ||
|
|
9b3ca76934 | ||
|
|
9f1c445214 | ||
|
|
075ba931ea | ||
|
|
29cbe48eb5 | ||
|
|
be1cc945a2 | ||
|
|
3e61d938bc | ||
|
|
0ee928cdce | ||
|
|
5d87fae906 | ||
|
|
afcc5ceb5b | ||
|
|
74d8e595f2 | ||
|
|
bc988181f9 | ||
|
|
1030654ce2 | ||
|
|
1c25143a75 | ||
|
|
39281811f5 | ||
|
|
2f07d22a9e | ||
|
|
1d1b9706ce | ||
|
|
7a19d444f1 | ||
|
|
73eb743f54 | ||
|
|
181ee74ba3 | ||
|
|
b8e9567501 | ||
|
|
dda64b301e | ||
|
|
af56551fd2 | ||
|
|
c55d0449cb | ||
|
|
0135476b68 | ||
|
|
e366b7c7a7 | ||
|
|
ca9a0b072e | ||
|
|
2f3035a08f | ||
|
|
cf5b0635e4 | ||
|
|
4db5c96781 | ||
|
|
e49948b512 | ||
|
|
1298d42b09 | ||
|
|
00e74dd2c8 | ||
|
|
10fe79c558 | ||
|
|
cddabebe86 | ||
|
|
9a7dac706c | ||
|
|
2e2998bb8b | ||
|
|
ce1352cb9f | ||
|
|
00007c20a7 | ||
|
|
cdaf3cb428 | ||
|
|
d640bb5a04 | ||
|
|
b1ebcb76f0 | ||
|
|
488dbb6715 | ||
|
|
f170157817 | ||
|
|
c094a26cbf | ||
|
|
366506555a | ||
|
|
9eb4043595 | ||
|
|
3359e78047 | ||
|
|
7ebafaf0fc | ||
|
|
fadd070663 | ||
|
|
27d291b0e9 | ||
|
|
f07f58733c | ||
|
|
b5521425ae | ||
|
|
b191ecd29e | ||
|
|
5989918300 | ||
|
|
f5720cf20e | ||
|
|
2106e48e0e | ||
|
|
1dd9e459c6 | ||
|
|
138b57b33d | ||
|
|
3845c55155 | ||
|
|
9aa2014e85 | ||
|
|
9239588757 | ||
|
|
5904b269e7 | ||
|
|
9bf3e31d6f | ||
|
|
618cb07ead | ||
|
|
1e3a39defc | ||
|
|
0aab548b87 | ||
|
|
489b93d5df | ||
|
|
cfb2a4d404 | ||
|
|
3b8ad132bc | ||
|
|
8510711e5d | ||
|
|
9918e903b2 | ||
|
|
6a292d6905 | ||
|
|
62926d6e28 | ||
|
|
51edf80e48 | ||
|
|
804f1f5610 | ||
|
|
3f0b14e48a | ||
|
|
3e0ce5544c | ||
|
|
933cbb72aa | ||
|
|
6bda5d5983 | ||
|
|
bfae8e7def | ||
|
|
96a91b97e9 | ||
|
|
12096a8fb3 | ||
|
|
e03d4d52c4 | ||
|
|
ea24d72f01 | ||
|
|
a4473ad739 | ||
|
|
08c28950f4 | ||
|
|
5cc8439f5b | ||
|
|
eb7fd4a015 | ||
|
|
dce609d141 | ||
|
|
f31360ecbf | ||
|
|
93e88d8b23 | ||
|
|
816cc0b17b | ||
|
|
1f73269480 | ||
|
|
f7d1b8821c | ||
|
|
cd5ad9f85b | ||
|
|
9c706f07f0 | ||
|
|
ea82925e14 | ||
|
|
1c5f208ef1 | ||
|
|
aeae0ba535 | ||
|
|
f59b227c44 | ||
|
|
4518e7056c | ||
|
|
565c6bafae | ||
|
|
584e8131cd | ||
|
|
20e958b1ee | ||
|
|
21ca3abc7e | ||
|
|
612ad32722 | ||
|
|
8ec07266b9 | ||
|
|
a9a7b0b317 | ||
|
|
e634e3e28f | ||
|
|
86de4b721f | ||
|
|
1d95a78e75 | ||
|
|
f5e44163be | ||
|
|
1ffc005479 | ||
|
|
31f67d412b | ||
|
|
cc62237ab5 | ||
|
|
f11d4a92df | ||
|
|
0be6249c2b | ||
|
|
a083fc9084 | ||
|
|
54172c441f | ||
|
|
b5f8b1014e | ||
|
|
df42c6176d | ||
|
|
7d0a34fceb | ||
|
|
b3e94b13f7 | ||
|
|
4eee908f2f | ||
|
|
1ebae5c284 | ||
|
|
361f03eb5f | ||
|
|
d8f54fc15a | ||
|
|
90b0f3201e | ||
|
|
b0d2374960 | ||
|
|
5c471e43dd | ||
|
|
c69169cbf9 | ||
|
|
f2c670dfd0 | ||
|
|
cfdd6dc0d9 | ||
|
|
d61b6c2faa | ||
|
|
e010995b19 | ||
|
|
053a1c1394 | ||
|
|
581184e2ae | ||
|
|
84e617b201 | ||
|
|
4ba21638b1 | ||
|
|
f92c5a214f | ||
|
|
180101400f | ||
|
|
ede10677f9 | ||
|
|
7627601ff8 | ||
|
|
cb120d2e75 | ||
|
|
ec86ccd956 | ||
|
|
63a657cac5 | ||
|
|
c3eb6bb972 | ||
|
|
eab5c0db12 | ||
|
|
0b9083915a | ||
|
|
051703234c | ||
|
|
6d555bcf84 | ||
|
|
d99fcd8e59 | ||
|
|
04eee919e8 | ||
|
|
0926c82878 | ||
|
|
79744d89ce | ||
|
|
214274f495 | ||
|
|
2425eb0ff8 | ||
|
|
c8931cde6e | ||
|
|
af698c7628 | ||
|
|
52745993cb | ||
|
|
7a8d23ba84 | ||
|
|
34559f0dbd | ||
|
|
b34a205ace | ||
|
|
799fbeba72 | ||
|
|
0e36abe1ad | ||
|
|
69ce07ef01 | ||
|
|
9f32e76a99 | ||
|
|
e89e48014c | ||
|
|
9863a95a71 | ||
|
|
dc0bf54401 | ||
|
|
3728f012d7 | ||
|
|
f904558315 | ||
|
|
a79556dfce | ||
|
|
901332dbee | ||
|
|
1ab75115f0 | ||
|
|
bc431b896b | ||
|
|
aa7a3c442c | ||
|
|
6825967cb9 | ||
|
|
cdc06a2b49 | ||
|
|
309c73a972 | ||
|
|
c4a3e5c4fd | ||
|
|
8d6cbe8e1e | ||
|
|
ff4e76b723 | ||
|
|
acdbc6b9a3 | ||
|
|
6714390890 | ||
|
|
249d00b285 | ||
|
|
e4ffdf6815 | ||
|
|
2228263b9f | ||
|
|
ee1c884ef1 | ||
|
|
0d29c75e7f | ||
|
|
7042f4bca8 | ||
|
|
ea42ed5381 | ||
|
|
ba6ca4a6bb | ||
|
|
ce68c1599f | ||
|
|
ce64601e38 | ||
|
|
b9f6351720 | ||
|
|
da8b31533a | ||
|
|
0591f106d3 | ||
|
|
40f9961541 | ||
|
|
5c8117539c | ||
|
|
af7400642b | ||
|
|
f8c5f31f97 | ||
|
|
5f2c2a8064 | ||
|
|
08aa53748e | ||
|
|
673485b5c4 | ||
|
|
18bea7edb2 | ||
|
|
cdf029bc84 | ||
|
|
31ce92fa9d | ||
|
|
f6b1666cd7 | ||
|
|
5f130bdda7 | ||
|
|
d619167c02 | ||
|
|
400932c6de | ||
|
|
8984ec3127 | ||
|
|
02076fadf4 | ||
|
|
1d93d5c687 | ||
|
|
5f028ea65f | ||
|
|
cf22ea2b78 | ||
|
|
58df3e692b | ||
|
|
80ca89b3f6 | ||
|
|
4209d91c43 | ||
|
|
79b878209d | ||
|
|
24cbe13ca7 | ||
|
|
f8fcbbea85 | ||
|
|
40d38ec0db | ||
|
|
f63f4e0aa3 | ||
|
|
d4b4c7bd71 | ||
|
|
bdef522da7 | ||
|
|
bb1ba1dbc4 | ||
|
|
2b880d322a | ||
|
|
60f62b2b50 | ||
|
|
b11d7be990 | ||
|
|
05d153e1d2 | ||
|
|
eaba45369b | ||
|
|
71adf31f7b | ||
|
|
d39d49fb8f | ||
|
|
7c91066618 | ||
|
|
57116c4f54 | ||
|
|
80e4d2329a | ||
|
|
7591843220 | ||
|
|
653afe9f8b | ||
|
|
8f007a70db | ||
|
|
0feea6091b | ||
|
|
b27b4bef44 | ||
|
|
2798a05e8e | ||
|
|
fe039f7b35 | ||
|
|
ea5dc4b7fc | ||
|
|
acc214d7c1 | ||
|
|
83c232ecb5 | ||
|
|
157875f7d5 | ||
|
|
ef00e57f72 | ||
|
|
8098ab50e8 | ||
|
|
ebb1044c43 | ||
|
|
751935e90b | ||
|
|
a81572914a | ||
|
|
e00f033ffd | ||
|
|
bf9414199c | ||
|
|
3011e9a804 | ||
|
|
a678f03284 | ||
|
|
11002c2881 | ||
|
|
2692bbaa63 | ||
|
|
1db6d7f32b | ||
|
|
61cce7e8e7 | ||
|
|
616a442fcb | ||
|
|
916519a43a | ||
|
|
af2f7a7a5a | ||
|
|
9ab9fcd577 | ||
|
|
853d1f4cfa | ||
|
|
cbcfdafef6 | ||
|
|
b156a27d1f | ||
|
|
f6ce6426f1 | ||
|
|
e12582c2c2 | ||
|
|
4d2cae0b0f | ||
|
|
35e0f27f52 | ||
|
|
77ddc83a04 | ||
|
|
3c83741b13 | ||
|
|
636c709671 | ||
|
|
f3f1b413b7 | ||
|
|
8eaad64dd6 | ||
|
|
f80ba6b87c | ||
|
|
5e5e3b5359 | ||
|
|
19203e976b | ||
|
|
2154607d11 | ||
|
|
072de1ea44 | ||
|
|
1818dad0d1 | ||
|
|
d51eab779c | ||
|
|
9f1ab6f961 | ||
|
|
0b875fc6f7 | ||
|
|
fd62938db0 | ||
|
|
4499ec6a22 | ||
|
|
dde20f4451 | ||
|
|
715b91ab96 | ||
|
|
7d26361680 | ||
|
|
b85a45d8f9 | ||
|
|
22ab5d334e | ||
|
|
acf124c81e | ||
|
|
51d81dea9f | ||
|
|
4a6066bb88 | ||
|
|
6ece16ccc9 | ||
|
|
0acab61f2e | ||
|
|
1cbd322105 | ||
|
|
ed9d26fd1b | ||
|
|
14e290c489 | ||
|
|
429b2b8a21 | ||
|
|
e7707c4826 | ||
|
|
290cbe6b55 | ||
|
|
d5708f24e6 | ||
|
|
3d273f041e | ||
|
|
22299c03cd | ||
|
|
0ea4b4400f | ||
|
|
b3c8337f83 | ||
|
|
a9e85f8765 | ||
|
|
b36799bf0c | ||
|
|
4d71a05d2a | ||
|
|
4fdf2a98bf | ||
|
|
880be03211 | ||
|
|
27495d5055 | ||
|
|
492e2e693c | ||
|
|
05a92ebd26 | ||
|
|
0d2e296eda | ||
|
|
ad25267ed7 | ||
|
|
1ed86899bb | ||
|
|
63c136a1ff | ||
|
|
3905b2b945 | ||
|
|
afaaf7d73a | ||
|
|
642b35582f | ||
|
|
117188769c | ||
|
|
bd7aad37e6 | ||
|
|
08b4e08820 | ||
|
|
aa4f360f59 | ||
|
|
2420375d56 | ||
|
|
bc5c738c25 | ||
|
|
ccc527f329 | ||
|
|
cf144aa2c1 | ||
|
|
086d924f06 | ||
|
|
24862f31b3 | ||
|
|
877eb4d423 | ||
|
|
a37a5fa1b5 | ||
|
|
2478a8f3cc | ||
|
|
3ed69d887f | ||
|
|
f0d440d204 | ||
|
|
3e18f812db | ||
|
|
8cf02fd59a | ||
|
|
06bfab3afa | ||
|
|
71e4697562 | ||
|
|
cf1bffe2f1 | ||
|
|
55a5fd49dc | ||
|
|
3f6637eb8f | ||
|
|
7373e281ac | ||
|
|
52b89455d7 | ||
|
|
bca7592c77 | ||
|
|
f6ab0bfe82 | ||
|
|
012a5c491d | ||
|
|
7666d6136d | ||
|
|
7bdda34f14 | ||
|
|
df21f89fcb | ||
|
|
f3b4cdca8a | ||
|
|
52460bf47b | ||
|
|
e674e7287e | ||
|
|
a20e8b6228 | ||
|
|
8d50e96dab | ||
|
|
1fe673951b | ||
|
|
3df5a9454e | ||
|
|
79fecd6b03 | ||
|
|
f3445c24b9 | ||
|
|
a3150d8505 | ||
|
|
828b5d8703 | ||
|
|
39559e203a | ||
|
|
605bdd0ea0 | ||
|
|
74945e03ce | ||
|
|
c772502af5 | ||
|
|
401d9db0f2 | ||
|
|
2306da94fe | ||
|
|
9f7ed11082 | ||
|
|
fff0efb095 | ||
|
|
9aa61f4bca | ||
|
|
bd70dc5966 | ||
|
|
ac6a3caa8f | ||
|
|
0914776152 | ||
|
|
45d0f43e90 | ||
|
|
7264367fa3 | ||
|
|
022fa34478 | ||
|
|
f7fd28fded | ||
|
|
e01a22de48 | ||
|
|
711c8e63c1 | ||
|
|
f186ec160a | ||
|
|
53fcfd13ee | ||
|
|
c684d66ec0 | ||
|
|
6496f750b0 | ||
|
|
6aaa47cccd | ||
|
|
1f6677d610 | ||
|
|
54b659aff0 | ||
|
|
b9db21309e | ||
|
|
8649c3b2b1 | ||
|
|
6bf6cc365b | ||
|
|
7c2d5448e8 | ||
|
|
a9f2ef7c10 | ||
|
|
f86bce970e | ||
|
|
6cbd618fb8 | ||
|
|
11787193ed | ||
|
|
45aae6810c | ||
|
|
7f6d571ef1 | ||
|
|
908dc4727c | ||
|
|
264759cfa0 | ||
|
|
b5d265526a | ||
|
|
22290eafb8 | ||
|
|
3101e57c36 | ||
|
|
b72a52232d | ||
|
|
a5b8e703fc | ||
|
|
fb26425f17 | ||
|
|
3114e20aef | ||
|
|
a52d2f4b7a | ||
|
|
34e484c377 | ||
|
|
08e8e9ff64 | ||
|
|
ebf55390eb | ||
|
|
c328144a58 | ||
|
|
4b583bea9b | ||
|
|
3f0ca412c6 | ||
|
|
0050b570b4 | ||
|
|
9405be03b0 | ||
|
|
b5b706fe06 | ||
|
|
8313d9fa90 | ||
|
|
93b96b3be7 | ||
|
|
50e6818f2b | ||
|
|
104fb57bd8 | ||
|
|
7ba6b2f00a | ||
|
|
d8e8939eca | ||
|
|
26c5f4049d | ||
|
|
b6c9dba0fc | ||
|
|
8badc1a354 | ||
|
|
15097eb1f0 | ||
|
|
986306f811 |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
*
|
||||
!build/*
|
||||
!entrypoint.sh
|
||||
15
.editorconfig
Normal file
15
.editorconfig
Normal file
@@ -0,0 +1,15 @@
|
||||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# Set default charset
|
||||
[*.{js,ts,scss,html}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
211
.gitignore
vendored
211
.gitignore
vendored
@@ -1,202 +1,13 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
build/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
# Visual Studo 2015 cache/options directory
|
||||
.vs/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding addin-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
|
||||
# Others
|
||||
*.[Cc]ache
|
||||
ClientBin/
|
||||
[Ss]tyle[Cc]op.*
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
bower_components/
|
||||
lib/
|
||||
.vs
|
||||
.idea
|
||||
.DS_Store
|
||||
node_modules
|
||||
npm-debug.log
|
||||
vwd.webinfo
|
||||
css/
|
||||
dist/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Other
|
||||
project.lock.json
|
||||
*.pem
|
||||
*.crx
|
||||
*.zip
|
||||
build/
|
||||
!dev-server.shared.pem
|
||||
|
||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "jslib"]
|
||||
path = jslib
|
||||
url = https://github.com/bitwarden/jslib.git
|
||||
branch = master
|
||||
13
CONTRIBUTING.md
Normal file
13
CONTRIBUTING.md
Normal file
@@ -0,0 +1,13 @@
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
||||
|
||||
# Localization (l10n)
|
||||
|
||||
[](https://crowdin.com/project/bitwarden-web)
|
||||
|
||||
We use a translation tool called [Crowdin](https://crowdin.com) to help manage our localization efforts across many different languages.
|
||||
|
||||
If you are interested in helping translate the Bitwarden web vault into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-web
|
||||
|
||||
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/kspearrin).
|
||||
|
||||
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/
|
||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM bitwarden/server
|
||||
|
||||
LABEL com.bitwarden.product="bitwarden"
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
gosu \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV ASPNETCORE_URLS http://+:5000
|
||||
WORKDIR /app
|
||||
EXPOSE 5000
|
||||
COPY ./build .
|
||||
COPY entrypoint.sh /
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
5
ISSUE_TEMPLATE.md
Normal file
5
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,5 @@
|
||||
<!--
|
||||
Please do not submit feature requests. The [Community Forums][1] has a
|
||||
section for submitting, voting for, and discussing product feature requests.
|
||||
[1]: https://community.bitwarden.com
|
||||
-->
|
||||
@@ -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>
|
||||
59
README.md
59
README.md
@@ -1,11 +1,60 @@
|
||||
[](https://gitter.im/bitwarden/Lobby)
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/web-vault-macbook.png" alt="" width="600" height="358" />
|
||||
</p>
|
||||
<p align="center">
|
||||
The Bitwarden web project is an Angular application that powers the web vault (https://vault.bitwarden.com/).
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://ci.appveyor.com/project/bitwarden/web/branch/master" target="_blank">
|
||||
<img src="https://ci.appveyor.com/api/projects/status/github/bitwarden/web?branch=master&svg=true" alt="appveyor build" />
|
||||
</a>
|
||||
<a href="https://crowdin.com/project/bitwarden-web" target="_blank">
|
||||
<img src="https://d322cqt584bo4o.cloudfront.net/bitwarden-web/localized.svg" alt="Crowdin" />
|
||||
</a>
|
||||
<a href="https://hub.docker.com/u/bitwarden/" target="_blank">
|
||||
<img src="https://img.shields.io/docker/pulls/bitwarden/web.svg" alt="DockerHub" />
|
||||
</a>
|
||||
<a href="https://gitter.im/bitwarden/Lobby" target="_blank">
|
||||
<img src="https://badges.gitter.im/bitwarden/Lobby.svg" alt="gitter chat" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# bitwarden Web
|
||||
## Build/Run
|
||||
|
||||
The bitwarden Web project is an AngularJS application that powers the web vault (https://vault.bitwarden.com/).
|
||||
### Requirements
|
||||
|
||||
# Contribute
|
||||
- [Node.js](https://nodejs.org) v8.11 or greater
|
||||
|
||||
### Run the app
|
||||
|
||||
```
|
||||
npm install
|
||||
npm run build:watch
|
||||
```
|
||||
|
||||
You can now access the web vault in your browser at `https://localhost:8080`. You can adjust your API endpoint settings in `src/app/services/services.module.ts` by altering the `apiService.setUrls` call. For example:
|
||||
|
||||
```typescript
|
||||
await apiService.setUrls({
|
||||
base: isDev ? null : window.location.origin,
|
||||
api: isDev ? 'http://mylocalapi' : null,
|
||||
identity: isDev ? 'http://mylocalidentity' : null,
|
||||
});
|
||||
```
|
||||
|
||||
If you want to point the development web vault to the production APIs, you can set:
|
||||
|
||||
```typescript
|
||||
await apiService.setUrls({
|
||||
base: null,
|
||||
api: 'https://api.bitwarden.com',
|
||||
identity: 'https://identity.bitwarden.com',
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## 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
45
SECURITY.md
Normal 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!
|
||||
@@ -1,36 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
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
|
||||
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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5} = {860863C9-0436-43D4-840D-FE919C9F6FFC}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
33
build.sh
Normal file
33
build.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
echo ""
|
||||
|
||||
if [ $# -gt 1 -a "$1" == "push" ]
|
||||
then
|
||||
TAG=$2
|
||||
echo "# Pushing Web ($TAG)"
|
||||
echo ""
|
||||
docker push bitwarden/web:$TAG
|
||||
elif [ $# -gt 1 -a "$1" == "tag" ]
|
||||
then
|
||||
TAG=$2
|
||||
echo "Tagging Web as '$TAG'"
|
||||
docker tag bitwarden/web bitwarden/web:$TAG
|
||||
else
|
||||
echo "# Building Web"
|
||||
|
||||
echo ""
|
||||
echo "Building app"
|
||||
echo "npm version $(npm --version)"
|
||||
npm install
|
||||
npm run sub:update
|
||||
npm run dist:selfhost
|
||||
|
||||
echo ""
|
||||
echo "Building docker image"
|
||||
docker --version
|
||||
docker build -t bitwarden/web $DIR/.
|
||||
fi
|
||||
10
crowdin.yml
Normal file
10
crowdin.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
files:
|
||||
- source: /src/locales/en/messages.json
|
||||
translation: /src/locales/%two_letters_code%/%original_file_name%
|
||||
update_option: update_as_unapproved
|
||||
languages_mapping:
|
||||
two_letters_code:
|
||||
pt-PT: pt_PT
|
||||
pt-BR: pt_BR
|
||||
zh-CN: zh_CN
|
||||
zh-TW: zh_TW
|
||||
45
dev-server.shared.pem
Normal file
45
dev-server.shared.pem
Normal file
@@ -0,0 +1,45 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDaEy1cPw07irjg
|
||||
4wUgaxshW7oQrgVoNZYRROmdU20K22L+HyG2ahW6usiWw8+6fPgVve7Y1z+/GYsK
|
||||
DhaDdY1Ket3JvZHxoAJ/+6lYY+05PhtmYnyEZzZlnuYx/tu3vyGpsXqMpzL3ZrX2
|
||||
Mh2dWE7ZXKxsyig4wSDJhBPMrW8HKXLrLR/JPFhu/nz5MpRF5LfzWU13FEfmS43s
|
||||
PEkBCn5ZxhVX4eNclQhTl7oOo5LU+KCWn+C/GPQir7pdmMoYF6D5j3qtbsq9irPe
|
||||
qR7HsM9z6DnX+L0mF31P2S6OTgLT3kuWJn9vqLUwvQWGFvSSzpw7JgGBK3eX6zE9
|
||||
2koGWP9NAgMBAAECggEAIpwCie5TykxU1RQSfzegYaXuHLGRmB1RCMKYFOjlmGCD
|
||||
EHOeZRXnBvCX3x2KfT1SHhk7q9xVeJ20LE9aEVj5qIVhZ6AXZnKPkwI8uRN61afe
|
||||
r1wYCOdcgbo7LFoXQs0pqYXKPkJW217IqB8CBjO6p9KGZumago9cBb9ZaRVpVohZ
|
||||
c6YHeatrna2mPb/EUPHdT0RHHQ5Dz2ToPjCkDtxsNHLZLekR35WIMtCBlp0xY5hb
|
||||
5h54ZxnaMihvHTLa8L/pgxGEUsP+XFpdXkM1oREzh8tDRFcUL8mUVZq8bGyzALn5
|
||||
MxDhdXqxrnyD2cQ/cSqXLs1/2mh5eccU3g5IaNtrAQKBgQD0Q4K2UYXa8jWQu7jI
|
||||
b37zwr2EypLFjeluqF4fxs+oz3UYEXeBDK0Td19/tze6/XieKibKDtFrOZQwDDKC
|
||||
AVxD7Dm58T9Jf4LDHNYOfYL3X/E4H+JrVBh94s0B00jVJ6TnEQDMuLi+wMGtvTdW
|
||||
huxoNefIWKf73ozvxIF+nsyeDQKBgQDkjYoXkBtfNgQR42RVA0/UdLLDWWctMU4F
|
||||
sJYc1bLL3txbf+fK7QzbU/ggLMW0hv8/IdyirGJhW3G0K0yhpAOlPhe36qv4QyhD
|
||||
o2nFlRrzfzvGJAgH9b1s+VcL/cSIuv4aCkbv97DAoQGPzAWEKv5gY1iw1DsGgrz5
|
||||
svZUvd7WQQKBgQDPrp7yuTngQNP+bT3dXb9JLqjIwRwt0E1LgugUiIuDcnCSuDct
|
||||
iEOYK4UNKBDAckcd46T7Y8H3MwumFpjTJKj4L1+dk1tF+J6Lmnb99wVlozOLjsCK
|
||||
lQQF9NJt3OEuKvjwZeqSJfUeavHB8QGeFjXnHP4nwAmEA2M9cYzQxeAf+QKBgCbS
|
||||
U+6Er+GQT0iqk1RNZ7XyzJqaCQiII3Sb9iOXuPMgO9Xe+ARkF5b5wF/Wuw5bD+gt
|
||||
XEjVdzCKU9oCsNWUAnqC/Yxj9CoLXj9+9mx1U0qhBgo1/Jc9ipuEDuEejc+b06Wg
|
||||
sUP5krBlqNpAEX/Nvb+poFsI8a29b1QKrgTe64cBAoGALg92rZBG60N2n8fTokou
|
||||
f1fui8Ftb+vOVGv9CM6icmNuwXeMF40A33Hvx14XLFk6B5p5dtVyOR660rRv4HRV
|
||||
cBUm5wwCZjwR5Aj83XGR0PRbTNFNngHbawQiutSo6dw8cNNKCZMywVh2KX29dsLh
|
||||
0Yj++kb8+G1kzFonR8WWoC8=
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICwzCCAaugAwIBAgIJAN5sbMfEx05qMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
|
||||
BAMTCWxvY2FsaG9zdDAeFw0xODA2MDUwMzMxNDhaFw0yODA2MDIwMzMxNDhaMBQx
|
||||
EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||
ggEBANoTLVw/DTuKuODjBSBrGyFbuhCuBWg1lhFE6Z1TbQrbYv4fIbZqFbq6yJbD
|
||||
z7p8+BW97tjXP78ZiwoOFoN1jUp63cm9kfGgAn/7qVhj7Tk+G2ZifIRnNmWe5jH+
|
||||
27e/IamxeoynMvdmtfYyHZ1YTtlcrGzKKDjBIMmEE8ytbwcpcustH8k8WG7+fPky
|
||||
lEXkt/NZTXcUR+ZLjew8SQEKflnGFVfh41yVCFOXug6jktT4oJaf4L8Y9CKvul2Y
|
||||
yhgXoPmPeq1uyr2Ks96pHsewz3PoOdf4vSYXfU/ZLo5OAtPeS5Ymf2+otTC9BYYW
|
||||
9JLOnDsmAYErd5frMT3aSgZY/00CAwEAAaMYMBYwFAYDVR0RBA0wC4IJbG9jYWxo
|
||||
b3N0MA0GCSqGSIb3DQEBCwUAA4IBAQCBTn7szrcs+fSs1Q/a2O3ng35zcme6NRhp
|
||||
T65RP0ooj3tPT9QlTJyKjo9Yb2RW2RGVbQO86mkYe9N9wcZkzurZ6KDqsfBn3FkI
|
||||
eZA1G/za907Dt/25mOdrsav7NmFBwxo9iuZ/cozgneK1mAXOu4nDI5yYvAlvNA6E
|
||||
iXgls4WX1LtHL5b9YV7Jz27d5tTmGxEimakMBo+zr10vCtMCsTlDs/ChamnI7ljN
|
||||
7B4WIVUMI3xOZzqClLnSzFJNReAlapjtGtp1qH6Y+6aZ9OErIwZOjE9CYYvm6MbE
|
||||
CblXQ9Uifpwrc09TA5S2Y/9+VxUBF9xlxh8hkcGLTzlNFDzVWdmR
|
||||
-----END CERTIFICATE-----
|
||||
38
entrypoint.sh
Normal file
38
entrypoint.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Setup
|
||||
|
||||
GROUPNAME="bitwarden"
|
||||
USERNAME="bitwarden"
|
||||
|
||||
LUID=${LOCAL_UID:-0}
|
||||
LGID=${LOCAL_GID:-0}
|
||||
|
||||
# Step down from host root to well-known nobody/nogroup user
|
||||
|
||||
if [ $LUID -eq 0 ]
|
||||
then
|
||||
LUID=65534
|
||||
fi
|
||||
if [ $LGID -eq 0 ]
|
||||
then
|
||||
LGID=65534
|
||||
fi
|
||||
|
||||
# Create user and group
|
||||
|
||||
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
|
||||
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
|
||||
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
|
||||
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
|
||||
mkhomedir_helper $USERNAME
|
||||
|
||||
# The rest...
|
||||
|
||||
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
|
||||
cp /etc/bitwarden/web/app-id.json /app/app-id.json
|
||||
chown -R $USERNAME:$GROUPNAME /app
|
||||
chown -R $USERNAME:$GROUPNAME /bitwarden_server
|
||||
|
||||
exec gosu $USERNAME:$GROUPNAME dotnet /bitwarden_server/Server.dll \
|
||||
/contentRoot=/app /webRoot=. /serveUnknown=false /webVault=true
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"projects": [ "src", "test" ],
|
||||
"sdk": {
|
||||
"version": "1.0.0-preview2-003121"
|
||||
}
|
||||
}
|
||||
35
gulpfile.js
Normal file
35
gulpfile.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const gulp = require('gulp');
|
||||
const googleWebFonts = require('gulp-google-webfonts');
|
||||
const del = require('del');
|
||||
const package = require('./package.json');
|
||||
const fs = require('fs');
|
||||
|
||||
const paths = {
|
||||
src: './src/',
|
||||
build: './build/',
|
||||
cssDir: './src/css/',
|
||||
};
|
||||
|
||||
function clean() {
|
||||
return del([paths.cssDir]);
|
||||
}
|
||||
|
||||
function webfonts() {
|
||||
return gulp.src('./webfonts.list')
|
||||
.pipe(googleWebFonts({
|
||||
fontsDir: 'webfonts',
|
||||
cssFilename: 'webfonts.css',
|
||||
format: 'woff',
|
||||
}))
|
||||
.pipe(gulp.dest(paths.cssDir));
|
||||
};
|
||||
|
||||
function version() {
|
||||
fs.writeFileSync(paths.build + 'version.json', '{"version":"' + package.version + '"}');
|
||||
}
|
||||
|
||||
gulp.task('clean', clean);
|
||||
gulp.task('webfonts', ['clean'], webfonts);
|
||||
gulp.task('prebuild', ['webfonts']);
|
||||
gulp.task('version', version);
|
||||
gulp.task('postdist', ['version']);
|
||||
1
jslib
Submodule
1
jslib
Submodule
Submodule jslib added at 6f43b73237
12214
package-lock.json
generated
Normal file
12214
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
92
package.json
Normal file
92
package.json
Normal file
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"name": "bitwarden-web",
|
||||
"version": "2.2.0",
|
||||
"scripts": {
|
||||
"sub:init": "git submodule update --init --recursive",
|
||||
"sub:update": "git submodule update --remote",
|
||||
"sub:pull": "git submodule foreach git pull",
|
||||
"postinstall": "npm run sub:init",
|
||||
"build": "gulp prebuild && webpack --config webpack.config.js",
|
||||
"build:watch": "gulp prebuild && webpack-serve --config webpack.config.js",
|
||||
"build:prod": "gulp prebuild && cross-env NODE_ENV=production webpack --config webpack.config.js",
|
||||
"build:prod:watch": "gulp prebuild && cross-env NODE_ENV=production webpack-serve --config webpack.config.js",
|
||||
"build:selfhost": "gulp prebuild && cross-env SELF_HOST=true webpack-serve --config webpack.config.js",
|
||||
"build:selfhost:watch": "gulp prebuild && cross-env SELF_HOST=true webpack-serve --config webpack.config.js",
|
||||
"build:selfhost:prod": "gulp prebuild && cross-env SELF_HOST=true NODE_ENV=production webpack --config webpack.config.js",
|
||||
"build:selfhost:prod:watch": "gulp prebuild && cross-env SELF_HOST=true NODE_ENV=production webpack-serve --config webpack.config.js",
|
||||
"clean:l10n": "git push origin --delete l10n_master",
|
||||
"dist": "npm run build:prod && gulp postdist",
|
||||
"dist:selfhost": "npm run build:selfhost:prod && gulp postdist",
|
||||
"deploy": "npm run dist && gh-pages -d build",
|
||||
"deploy:preview": "npm run dist && gh-pages -d build -r git@github.com:kspearrin/web-preview.git",
|
||||
"lint": "tslint src/**/*.ts || true",
|
||||
"lint:fix": "tslint src/**/*.ts --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/compiler-cli": "5.2.0",
|
||||
"@ngtools/webpack": "1.10.2",
|
||||
"@types/jquery": "^3.3.2",
|
||||
"@types/lunr": "^2.1.6",
|
||||
"@types/node": "8.0.19",
|
||||
"@types/node-forge": "0.6.10",
|
||||
"@types/papaparse": "4.1.33",
|
||||
"@types/webcrypto": "^0.0.28",
|
||||
"angular2-template-loader": "^0.6.2",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"cross-env": "^5.1.4",
|
||||
"css-loader": "^0.28.11",
|
||||
"del": "^3.0.0",
|
||||
"extract-text-webpack-plugin": "next",
|
||||
"file-loader": "^1.1.11",
|
||||
"gh-pages": "^1.2.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-google-webfonts": "^2.0.0",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"node-sass": "^4.9.2",
|
||||
"sass-loader": "^7.0.2",
|
||||
"style-loader": "^0.21.0",
|
||||
"ts-loader": "^4.3.1",
|
||||
"tslint": "^5.10.0",
|
||||
"tslint-loader": "^3.6.0",
|
||||
"typescript": "^2.7.2",
|
||||
"webpack": "^4.10.2",
|
||||
"webpack-cli": "^3.0.2",
|
||||
"webpack-serve": "^1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "5.2.0",
|
||||
"@angular/common": "5.2.0",
|
||||
"@angular/compiler": "5.2.0",
|
||||
"@angular/core": "5.2.0",
|
||||
"@angular/forms": "5.2.0",
|
||||
"@angular/http": "5.2.0",
|
||||
"@angular/platform-browser": "5.2.0",
|
||||
"@angular/platform-browser-dynamic": "5.2.0",
|
||||
"@angular/router": "5.2.0",
|
||||
"@angular/upgrade": "5.2.0",
|
||||
"@aspnet/signalr": "1.0.3",
|
||||
"@aspnet/signalr-protocol-msgpack": "1.0.3",
|
||||
"angular2-toaster": "4.0.2",
|
||||
"angulartics2": "5.0.1",
|
||||
"bootstrap": "4.1.3",
|
||||
"core-js": "2.4.1",
|
||||
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
|
||||
"font-awesome": "4.7.0",
|
||||
"jquery": "3.3.1",
|
||||
"lunr": "2.3.1",
|
||||
"mousetrap": "1.6.1",
|
||||
"ngx-infinite-scroll": "0.8.4",
|
||||
"node-forge": "0.7.1",
|
||||
"papaparse": "4.3.5",
|
||||
"popper.js": "1.14.4",
|
||||
"qrious": "4.0.2",
|
||||
"rxjs": "5.5.6",
|
||||
"sweetalert": "2.1.0",
|
||||
"web-animations-js": "2.3.1",
|
||||
"webcrypto-shim": "0.1.4",
|
||||
"whatwg-fetch": "^2.0.4",
|
||||
"zone.js": "0.8.19"
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")]
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -1,326 +0,0 @@
|
||||
/// <binding BeforeBuild='build, dist' Clean='clean' ProjectOpened='build. dist' />
|
||||
|
||||
var gulp = require('gulp'),
|
||||
rimraf = require('rimraf'),
|
||||
concat = require('gulp-concat'),
|
||||
rename = require('gulp-rename'),
|
||||
cssmin = require('gulp-cssmin'),
|
||||
uglify = require('gulp-uglify'),
|
||||
ghPages = require('gulp-gh-pages'),
|
||||
less = require('gulp-less'),
|
||||
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'),
|
||||
jshint = require('gulp-jshint'),
|
||||
_ = require('lodash');
|
||||
|
||||
var paths = {};
|
||||
paths.dist = '../../dist/';
|
||||
paths.webroot = './wwwroot/'
|
||||
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.cssDir = paths.webroot + 'css/';
|
||||
paths.jsDir = paths.webroot + 'js/';
|
||||
|
||||
var randomString = Math.random().toString(36).substring(7);
|
||||
|
||||
gulp.task('lint', function () {
|
||||
return gulp.src(paths.webroot + 'app/**/*.js')
|
||||
.pipe(jshint())
|
||||
.pipe(jshint.reporter('default'));
|
||||
});
|
||||
|
||||
gulp.task('build', function (cb) {
|
||||
return runSequence(
|
||||
'clean',
|
||||
['lib', 'less', 'settings', 'lint'],
|
||||
cb);
|
||||
});
|
||||
|
||||
gulp.task('clean:js', function (cb) {
|
||||
return rimraf(paths.concatJsDest, cb);
|
||||
});
|
||||
|
||||
gulp.task('clean:css', function (cb) {
|
||||
return rimraf(paths.cssDir, cb);
|
||||
});
|
||||
|
||||
gulp.task('clean:lib', function (cb) {
|
||||
return rimraf(paths.libDir, cb);
|
||||
});
|
||||
|
||||
gulp.task('clean', ['clean:js', 'clean:css', 'clean:lib', 'dist:clean']);
|
||||
|
||||
gulp.task('min:js', ['clean:js'], function () {
|
||||
return gulp.src([paths.js, '!' + paths.minJs], { base: '.' })
|
||||
.pipe(concat(paths.concatJsDest))
|
||||
.pipe(uglify())
|
||||
.pipe(gulp.dest('.'));
|
||||
});
|
||||
|
||||
gulp.task('min:css', [], function () {
|
||||
return gulp.src([paths.cssDir + '**/*.css', '!' + paths.cssDir + '**/*.min.css'], { base: '.' })
|
||||
.pipe(cssmin())
|
||||
.pipe(rename({ suffix: '.min' }))
|
||||
.pipe(gulp.dest('.'));
|
||||
});
|
||||
|
||||
gulp.task('min', ['min:js', 'min:css']);
|
||||
|
||||
gulp.task('lib', ['clean:lib'], function () {
|
||||
var libs = [
|
||||
{
|
||||
src: [
|
||||
paths.npmDir + 'bootstrap/dist/**/*',
|
||||
'!' + paths.npmDir + 'bootstrap/dist/**/npm.js',
|
||||
'!' + paths.npmDir + 'bootstrap/dist/**/css/*theme*'
|
||||
],
|
||||
dest: paths.libDir + 'bootstrap'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'font-awesome/css/*',
|
||||
dest: paths.libDir + 'font-awesome/css'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'font-awesome/fonts/*',
|
||||
dest: paths.libDir + 'font-awesome/fonts'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'jquery/dist/*.js',
|
||||
dest: paths.libDir + 'jquery'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'admin-lte/dist/js/app*.js',
|
||||
dest: paths.libDir + 'admin-lte/js'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'angular/angular*.js',
|
||||
dest: paths.libDir + 'angular'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'angular-bootstrap-npm/dist/*tpls*.js',
|
||||
dest: paths.libDir + 'angular-bootstrap'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'angular-bootstrap-show-errors/src/*.js',
|
||||
dest: paths.libDir + 'angular-bootstrap-show-errors'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'angular-cookies/*cookies*.js',
|
||||
dest: paths.libDir + 'angular-cookies'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'angular-jwt/dist/*.js',
|
||||
dest: paths.libDir + 'angular-jwt'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'angular-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-toastr/dist/**/*.css', paths.npmDir + 'angular-toastr/dist/**/*.js'],
|
||||
dest: paths.libDir + 'angular-toastr'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'angular-ui-router/release/*.js',
|
||||
dest: paths.libDir + 'angular-ui-router'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'angular-messages/*messages*.js',
|
||||
dest: paths.libDir + 'angular-messages'
|
||||
},
|
||||
{
|
||||
src: [paths.npmDir + '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'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'papaparse/papaparse*.js',
|
||||
dest: paths.libDir + 'papaparse'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'ngclipboard/dist/ngclipboard*.js',
|
||||
dest: paths.libDir + 'ngclipboard'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'clipboard/dist/clipboard*.js',
|
||||
dest: paths.libDir + 'clipboard'
|
||||
},
|
||||
{
|
||||
src: [
|
||||
paths.npmDir + 'angulartics-google-analytics/lib/angulartics*.js',
|
||||
paths.npmDir + 'angulartics/src/angulartics.js'
|
||||
],
|
||||
dest: paths.libDir + 'angulartics'
|
||||
}
|
||||
];
|
||||
|
||||
var tasks = libs.map(function (lib) {
|
||||
return gulp.src(lib.src).pipe(gulp.dest(lib.dest));
|
||||
});
|
||||
|
||||
return merge(tasks);
|
||||
});
|
||||
|
||||
gulp.task('settings', function () {
|
||||
return config()
|
||||
.pipe(gulp.dest(paths.webroot + 'app'));
|
||||
});
|
||||
|
||||
function config() {
|
||||
return gulp.src('./settings.json')
|
||||
.pipe(ngConfig('bit', {
|
||||
createModule: false,
|
||||
constants: _.merge({}, {
|
||||
appSettings: {
|
||||
version: project.version,
|
||||
environment: project.environment
|
||||
}
|
||||
}, require('./settings.' + project.environment + '.json') || {})
|
||||
}));
|
||||
}
|
||||
|
||||
gulp.task('less', function () {
|
||||
return gulp.src(paths.lessDir + 'vault.less')
|
||||
.pipe(less())
|
||||
.pipe(gulp.dest(paths.cssDir));
|
||||
});
|
||||
|
||||
gulp.task('watch', function () {
|
||||
gulp.watch(paths.lessDir + '*.less', ['less']);
|
||||
gulp.watch('./settings*.json', ['settings']);
|
||||
});
|
||||
|
||||
gulp.task('dist:clean', function (cb) {
|
||||
return rimraf(paths.dist, cb);
|
||||
});
|
||||
|
||||
gulp.task('dist:move', function () {
|
||||
var moves = [
|
||||
{
|
||||
src: '../../CNAME',
|
||||
dest: paths.dist
|
||||
},
|
||||
{
|
||||
src: [
|
||||
paths.npmDir + 'bootstrap/dist/**/bootstrap.min.js',
|
||||
paths.npmDir + 'bootstrap/dist/**/bootstrap.min.css',
|
||||
paths.npmDir + 'bootstrap/dist/**/fonts/**/*',
|
||||
],
|
||||
dest: paths.dist + 'lib/bootstrap'
|
||||
},
|
||||
{
|
||||
src: [
|
||||
paths.npmDir + 'font-awesome/**/font-awesome.min.css',
|
||||
paths.npmDir + 'font-awesome/**/fonts/**/*'
|
||||
],
|
||||
dest: paths.dist + 'lib/font-awesome'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'jquery/dist/jquery.min.js',
|
||||
dest: paths.dist + 'lib/jquery'
|
||||
},
|
||||
{
|
||||
src: paths.npmDir + 'angular/angular.min.js',
|
||||
dest: paths.dist + 'lib/angular'
|
||||
},
|
||||
{
|
||||
src: [
|
||||
paths.webroot + '**/app/**/*.html',
|
||||
paths.webroot + '**/images/**/*',
|
||||
paths.webroot + 'index.html',
|
||||
paths.webroot + 'favicon.ico'
|
||||
],
|
||||
dest: paths.dist
|
||||
}
|
||||
];
|
||||
|
||||
var tasks = moves.map(function (move) {
|
||||
return gulp.src(move.src).pipe(gulp.dest(move.dest));
|
||||
});
|
||||
|
||||
return merge(tasks);
|
||||
});
|
||||
|
||||
gulp.task('dist:css', function () {
|
||||
return gulp
|
||||
.src([
|
||||
paths.cssDir + '**/*.css',
|
||||
'!' + paths.cssDir + '**/*.min.css'
|
||||
])
|
||||
.pipe(preprocess({ context: { cacheTag: randomString } }))
|
||||
.pipe(cssmin())
|
||||
.pipe(rename({ suffix: '.min' }))
|
||||
.pipe(gulp.dest(paths.dist + 'css'));
|
||||
});
|
||||
|
||||
gulp.task('dist:js:app', function () {
|
||||
var mainStream = gulp
|
||||
.src([
|
||||
paths.webroot + 'app/app.js',
|
||||
'!' + paths.webroot + 'app/settings.js',
|
||||
paths.webroot + 'app/**/*Module.js',
|
||||
paths.webroot + 'app/**/*.js'
|
||||
]);
|
||||
|
||||
merge(mainStream, config())
|
||||
.pipe(preprocess({ context: { cacheTag: randomString } }))
|
||||
.pipe(concat(paths.dist + '/js/app.min.js'))
|
||||
.pipe(ngAnnotate())
|
||||
.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',
|
||||
'!' + paths.libDir + 'angular/**/*',
|
||||
'!' + paths.libDir + 'bootstrap/**/*',
|
||||
'!' + paths.libDir + 'jquery/**/*'
|
||||
])
|
||||
.pipe(concat(paths.dist + '/js/lib.min.js'))
|
||||
.pipe(uglify())
|
||||
.pipe(gulp.dest('.'));
|
||||
});
|
||||
|
||||
gulp.task('dist:preprocess', function () {
|
||||
return gulp
|
||||
.src([
|
||||
paths.dist + '/**/*.html'
|
||||
], { base: '.' })
|
||||
.pipe(preprocess({ context: { cacheTag: randomString }}))
|
||||
.pipe(gulp.dest('.'));
|
||||
});
|
||||
|
||||
gulp.task('dist', ['build'], function (cb) {
|
||||
return runSequence(
|
||||
'dist:clean',
|
||||
['dist:move', 'dist:css', 'dist:js:app', 'dist:js:lib'],
|
||||
'dist:preprocess',
|
||||
cb);
|
||||
});
|
||||
|
||||
gulp.task('deploy', ['dist'], function () {
|
||||
return gulp.src(paths.dist + '**/*')
|
||||
.pipe(ghPages({ cacheDir: paths.dist + '.publish' }));
|
||||
});
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"version": "1.2.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"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"appSettings": {
|
||||
"apiUri": "http://localhost:4000"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"appSettings": {
|
||||
"apiUri": "https://api.bitwarden.com"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"appSettings": {
|
||||
"apiUri": "https://api.bitwarden.com"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"appSettings": {
|
||||
"rememberedEmailCookieName": "bit.rememberedEmail"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -1,73 +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/angulartics/angulartics.js" />
|
||||
/// <reference path="lib/angulartics/angulartics-ga.js" />
|
||||
/// <reference path="lib/angular-toastr/angular-toastr.js" />
|
||||
/// <reference path="lib/angular-toastr/angular-toastr.min.js" />
|
||||
/// <reference path="lib/angular-toastr/angular-toastr.tpls.min.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" />
|
||||
@@ -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');
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
angular
|
||||
.module('bit.accounts')
|
||||
|
||||
.controller('accountsLogoutController', function ($scope, authService, $state, $analytics) {
|
||||
authService.logOut();
|
||||
$analytics.eventTrack('Logged Out');
|
||||
$state.go('frontend.login.info');
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
angular
|
||||
.module('bit.accounts', ['ui.bootstrap', 'ngCookies']);
|
||||
@@ -1,12 +0,0 @@
|
||||
angular
|
||||
.module('bit.accounts')
|
||||
|
||||
.controller('accountsPasswordHintController', function ($scope, $rootScope, apiService) {
|
||||
$scope.success = false;
|
||||
|
||||
$scope.submit = function (model) {
|
||||
$scope.submitPromise = apiService.accounts.postPasswordHint({ email: model.email }, function () {
|
||||
$scope.success = true;
|
||||
}).$promise;
|
||||
};
|
||||
});
|
||||
@@ -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;
|
||||
};
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
<div class="login-box">
|
||||
<div class="login-logo">
|
||||
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||
</div>
|
||||
<div class="login-box-body" ui-view>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,41 +0,0 @@
|
||||
<p class="login-box-msg">Log in to access your vault.</p>
|
||||
<form name="loginForm" ng-submit="loginForm.$valid && login(model)" api-form="loginPromise">
|
||||
<div class="callout callout-danger validation-errors" ng-show="loginForm.$errors">
|
||||
<h4>Errors have occured</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in loginForm.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="form-group has-feedback" show-errors>
|
||||
<label for="email" class="sr-only">Email</label>
|
||||
<input type="email" id="email" name="Email" class="form-control" placeholder="Email" ng-model="model.email"
|
||||
required api-field />
|
||||
<span class="fa fa-envelope form-control-feedback"></span>
|
||||
</div>
|
||||
<div class="form-group has-feedback" show-errors>
|
||||
<label for="masterPassword" class="sr-only">Master Password</label>
|
||||
<input type="password" id="masterPassword" name="MasterPasswordHash" class="form-control" placeholder="Master Password"
|
||||
ng-model="model.masterPassword"
|
||||
required api-field />
|
||||
<span class="fa fa-lock form-control-feedback"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-7">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="rememberEmail" ng-model="model.rememberEmail" /> Remember Email
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-5">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="loginForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="loginForm.$loading"></i>Log In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<ul>
|
||||
<li><a ui-sref="frontend.register">Create a new account</a></li>
|
||||
<li><a ui-sref="frontend.passwordHint">Get master password hint</a></li>
|
||||
</ul>
|
||||
</form>
|
||||
@@ -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>
|
||||
@@ -1,39 +0,0 @@
|
||||
<div class="login-box">
|
||||
<div class="login-logo">
|
||||
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||
</div>
|
||||
<div class="login-box-body">
|
||||
<p class="login-box-msg">Get your master password hint.</p>
|
||||
<div class="text-center" ng-show="success">
|
||||
<div class="callout callout-success">
|
||||
If your account exists ({{model.email}}) we've sent you an email with your master password hint.
|
||||
</div>
|
||||
<a ui-sref="frontend.login.info">Ready to log in?</a>
|
||||
</div>
|
||||
<form name="passwordHintForm" ng-submit="passwordHintForm.$valid && submit(model)" ng-show="!success"
|
||||
api-form="submitPromise">
|
||||
<div class="callout callout-danger validation-errors" ng-show="passwordHintForm.$errors">
|
||||
<h4>Errors have occured</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in passwordHintForm.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="form-group has-feedback" show-errors>
|
||||
<label for="email" class="sr-only">Your account email address</label>
|
||||
<input type="email" id="email" name="Email" class="form-control" placeholder="Your account email address"
|
||||
ng-model="model.email" required api-field />
|
||||
<span class="fa fa-envelope form-control-feedback"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-7">
|
||||
<a ui-sref="frontend.login.info">Ready to log in?</a>
|
||||
</div>
|
||||
<div class="col-xs-5">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="passwordHintForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="passwordHintForm.$loading"></i>Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,73 +0,0 @@
|
||||
<div class="register-box">
|
||||
<div class="register-logo">
|
||||
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||
</div>
|
||||
<div class="register-box-body">
|
||||
<p class="login-box-msg">Create a new account.</p>
|
||||
<div class="text-center" ng-show="success">
|
||||
<div class="callout callout-success">
|
||||
<h4>Account Created!</h4>
|
||||
<p>You may now log in to your new account.</p>
|
||||
</div>
|
||||
<a ui-sref="frontend.login.info">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-danger validation-errors" ng-show="registerForm.$errors">
|
||||
<h4>Errors have occured</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in registerForm.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="form-group has-feedback" show-errors>
|
||||
<label for="email" class="sr-only">Email</label>
|
||||
<input type="email" id="email" name="Email" class="form-control" placeholder="Email" ng-model="model.email"
|
||||
required api-field />
|
||||
<span class="fa fa-envelope form-control-feedback"></span>
|
||||
<p class="help-block">You'll use your email address to log in.</p>
|
||||
</div>
|
||||
<div class="form-group has-feedback" show-errors>
|
||||
<label for="name" class="sr-only">Your Name</label>
|
||||
<input type="text" id="name" name="Name" class="form-control" ng-model="model.name"
|
||||
placeholder="Your Name" api-field>
|
||||
<span class="fa fa-user form-control-feedback"></span>
|
||||
<p class="help-block">What should we call you?</p>
|
||||
</div>
|
||||
<div class="form-group has-feedback" show-errors>
|
||||
<label for="masterPassword" class="sr-only">Master Password</label>
|
||||
<input type="password" id="masterPassword" name="MasterPasswordHash" class="form-control"
|
||||
ng-model="model.masterPassword" placeholder="Master Password" required api-field>
|
||||
<span class="fa fa-lock form-control-feedback"></span>
|
||||
<p class="help-block">The master password is the password you use to access your vault.</p>
|
||||
</div>
|
||||
<div class="form-group has-feedback" show-errors>
|
||||
<label form="confirmMasterPassword" class="sr-only">Re-type Master Password</label>
|
||||
<input type="password" id="confirmMasterPassword" name="ConfirmMasterPassword" class="form-control"
|
||||
placeholder="Re-type Master Password"
|
||||
ng-model="model.confirmMasterPassword" required api-field>
|
||||
<span class="fa fa-lock form-control-feedback"></span>
|
||||
<p class="help-block">
|
||||
It is very important that you do not forget your master password.
|
||||
There is <u>no way</u> to recover the password in the event that you forget it.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group has-feedback" show-errors>
|
||||
<label for="hint" class="sr-only">Master Password Hint (optional)</label>
|
||||
<input type="text" id="hint" name="MasterPasswordHint" class="form-control" ng-model="model.masterPasswordHint"
|
||||
placeholder="Master Password Hint (optional)" api-field>
|
||||
<span class="fa fa-lightbulb-o form-control-feedback"></span>
|
||||
<p class="help-block">A master password hint can help you remember your password if you forget it.</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-7">
|
||||
<a ui-sref="frontend.login.info">Already have an account?</a>
|
||||
</div>
|
||||
<div class="col-xs-5">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="registerForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="registerForm.$loading"></i>Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,29 +0,0 @@
|
||||
angular
|
||||
.module('bit')
|
||||
|
||||
.factory('apiInterceptor', function ($injector, $q, toastr) {
|
||||
return {
|
||||
request: function (config) {
|
||||
return config;
|
||||
},
|
||||
response: function (response) {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
$injector.get('authService').logOut();
|
||||
$injector.get('$state').go('frontend.login.info').then(function () {
|
||||
toastr.warning('Your login session has expired.', 'Logged out');
|
||||
});
|
||||
}
|
||||
|
||||
return response || $q.when(response);
|
||||
},
|
||||
responseError: function (rejection) {
|
||||
if (rejection.status === 401 || rejection.status === 403) {
|
||||
$injector.get('authService').logOut();
|
||||
$injector.get('$state').go('frontend.login.info').then(function () {
|
||||
toastr.warning('Your login session has expired.', 'Logged out');
|
||||
});
|
||||
}
|
||||
return $q.reject(rejection);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
angular
|
||||
.module('bit', [
|
||||
'ui.router',
|
||||
'ngMessages',
|
||||
'angular-jwt',
|
||||
'angular-md5',
|
||||
'ui.bootstrap.showErrors',
|
||||
'toastr',
|
||||
'angulartics',
|
||||
'angulartics.google.analytics',
|
||||
|
||||
'bit.directives',
|
||||
'bit.services',
|
||||
|
||||
'bit.global',
|
||||
'bit.accounts',
|
||||
'bit.vault',
|
||||
'bit.settings',
|
||||
'bit.tools'
|
||||
]);
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
angular
|
||||
.module('bit.directives')
|
||||
|
||||
.directive('apiField', function () {
|
||||
var linkFn = function (scope, element, attrs, ngModel) {
|
||||
ngModel.$registerApiError = registerApiError;
|
||||
ngModel.$validators.apiValidate = apiValidator;
|
||||
|
||||
function apiValidator() {
|
||||
ngModel.$setValidity('api', true);
|
||||
return true;
|
||||
}
|
||||
|
||||
function registerApiError() {
|
||||
ngModel.$setValidity('api', false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
require: 'ngModel',
|
||||
restrict: 'A',
|
||||
compile: function (elem, attrs) {
|
||||
if (!attrs.name || attrs.name === '') {
|
||||
throw 'api-field element does not have a valid name attribute';
|
||||
}
|
||||
|
||||
return linkFn;
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
angular
|
||||
.module('bit.directives')
|
||||
|
||||
.directive('apiForm', function ($rootScope, validationService) {
|
||||
return {
|
||||
require: 'form',
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs, formCtrl) {
|
||||
var watchPromise = attrs.apiForm || null;
|
||||
if (watchPromise !== void 0) {
|
||||
scope.$watch(watchPromise, formSubmitted.bind(null, formCtrl, scope));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function formSubmitted(form, scope, promise) {
|
||||
if (!promise || !promise.then) {
|
||||
return;
|
||||
}
|
||||
|
||||
// reset errors
|
||||
form.$errors = null;
|
||||
|
||||
// start loading
|
||||
form.$loading = true;
|
||||
|
||||
promise.then(function success(response) {
|
||||
form.$loading = false;
|
||||
}, function failure(reason) {
|
||||
form.$loading = false;
|
||||
validationService.addErrors(form, reason);
|
||||
scope.$broadcast('show-errors-check-validity');
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
angular
|
||||
.module('bit.directives', []);
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
angular
|
||||
.module('bit.directives')
|
||||
|
||||
.directive('pageTitle', function ($rootScope, $timeout, appSettings) {
|
||||
return {
|
||||
link: function (scope, element) {
|
||||
var listener = function (event, toState, toParams, fromState, fromParams) {
|
||||
// Default title
|
||||
var title = 'bitwarden Password Manager';
|
||||
if (toState.data && toState.data.pageTitle) {
|
||||
title = toState.data.pageTitle + ' - bitwarden Password Manager';
|
||||
}
|
||||
|
||||
$timeout(function () {
|
||||
element.text(title);
|
||||
});
|
||||
};
|
||||
|
||||
$rootScope.$on('$stateChangeStart', listener);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
angular
|
||||
.module('bit.directives')
|
||||
|
||||
.directive('passwordMeter', function () {
|
||||
return {
|
||||
template: '<div class="progress {{outerClass}}"><div class="progress-bar progress-bar-{{valueClass}}" ' +
|
||||
'role="progressbar" aria-valuenow="{{value}}" aria-valuemin="0" aria-valuemax="100" ' +
|
||||
'ng-style="{width : ( value + \'%\' ) }"><span class="sr-only">{{value}}%</span></div></div>',
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
password: '=passwordMeter',
|
||||
username: '=passwordMeterUsername',
|
||||
outerClass: '@?'
|
||||
},
|
||||
link: function (scope) {
|
||||
var measureStrength = function (username, password) {
|
||||
if (!password || password === username) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var strength = password.length;
|
||||
|
||||
if (username && username !== '') {
|
||||
if (username.indexOf(password) !== -1) strength -= 15;
|
||||
if (password.indexOf(username) !== -1) strength -= username.length;
|
||||
}
|
||||
|
||||
if (password.length > 0 && password.length <= 4) strength += password.length;
|
||||
else if (password.length >= 5 && password.length <= 7) strength += 6;
|
||||
else if (password.length >= 8 && password.length <= 15) strength += 12;
|
||||
else if (password.length >= 16) strength += 18;
|
||||
|
||||
if (password.match(/[a-z]/)) strength += 1;
|
||||
if (password.match(/[A-Z]/)) strength += 5;
|
||||
if (password.match(/\d/)) strength += 5;
|
||||
if (password.match(/.*\d.*\d.*\d/)) strength += 5;
|
||||
if (password.match(/[!,@,#,$,%,^,&,*,?,_,~]/)) strength += 5;
|
||||
if (password.match(/.*[!,@,#,$,%,^,&,*,?,_,~].*[!,@,#,$,%,^,&,*,?,_,~]/)) strength += 5;
|
||||
if (password.match(/(?=.*[a-z])(?=.*[A-Z])/)) strength += 2;
|
||||
if (password.match(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/)) strength += 2;
|
||||
if (password.match(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!,@,#,$,%,^,&,*,?,_,~])/)) strength += 2;
|
||||
|
||||
strength = Math.round(strength * 2);
|
||||
return Math.max(0, Math.min(100, strength));
|
||||
};
|
||||
|
||||
var getClass = function (strength) {
|
||||
switch (Math.round(strength / 33)) {
|
||||
case 0:
|
||||
case 1:
|
||||
return 'danger';
|
||||
case 2:
|
||||
return 'warning';
|
||||
case 3:
|
||||
return 'success';
|
||||
}
|
||||
};
|
||||
|
||||
var updateMeter = function (scope) {
|
||||
scope.value = measureStrength(scope.username, scope.password);
|
||||
scope.valueClass = getClass(scope.value);
|
||||
};
|
||||
|
||||
scope.$watch('password', function () {
|
||||
updateMeter(scope);
|
||||
});
|
||||
|
||||
scope.$watch('username', function () {
|
||||
updateMeter(scope);
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
angular
|
||||
.module('bit.directives')
|
||||
|
||||
.directive('passwordViewer', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attr) {
|
||||
var passwordViewer = attr.passwordViewer;
|
||||
if (!passwordViewer) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.onclick = function (event) { };
|
||||
element.on('click', function (event) {
|
||||
var passwordElement = $(passwordViewer);
|
||||
if (passwordElement && passwordElement.attr('type') === 'password') {
|
||||
element.removeClass('fa-eye').addClass('fa-eye-slash');
|
||||
passwordElement.attr('type', 'text');
|
||||
}
|
||||
else if (passwordElement && passwordElement.attr('type') === 'text') {
|
||||
element.removeClass('fa-eye-slash').addClass('fa-eye');
|
||||
passwordElement.attr('type', 'password');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
angular
|
||||
.module('bit.global', []);
|
||||
@@ -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');
|
||||
};
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
angular
|
||||
.module('bit.global')
|
||||
|
||||
.controller('sideNavController', function ($scope, $state) {
|
||||
$scope.$state = $state;
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
angular
|
||||
.module('bit.global')
|
||||
|
||||
.controller('topNavController', function ($scope) {
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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: encryptedSite.Uri && encryptedSite.Uri !== '' ? cryptoService.decrypt(encryptedSite.Uri) : null,
|
||||
username: encryptedSite.Username && encryptedSite.Username !== '' ? cryptoService.decrypt(encryptedSite.Username) : null,
|
||||
password: encryptedSite.Password && encryptedSite.Password !== '' ? cryptoService.decrypt(encryptedSite.Password) : null,
|
||||
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: !unencryptedSite.uri || unencryptedSite.uri === '' ? null : cryptoService.encrypt(unencryptedSite.uri, key),
|
||||
name: cryptoService.encrypt(unencryptedSite.name, key),
|
||||
username: !unencryptedSite.username || unencryptedSite.username === '' ? null : cryptoService.encrypt(unencryptedSite.username, key),
|
||||
password: !unencryptedSite.password || unencryptedSite.password === '' ? null : 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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -1,618 +0,0 @@
|
||||
angular
|
||||
.module('bit.services')
|
||||
|
||||
.factory('importService', function () {
|
||||
var _service = {};
|
||||
|
||||
_service.import = function (source, file, success, error) {
|
||||
if (!file) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (source) {
|
||||
case 'local':
|
||||
importLocal(file, success, error);
|
||||
break;
|
||||
case 'lastpass':
|
||||
importLastPass(file, success, error);
|
||||
break;
|
||||
case 'safeincloudcsv':
|
||||
importSafeInCloudCsv(file, success, error);
|
||||
break;
|
||||
case 'safeincloudxml':
|
||||
importSafeInCloudXml(file, success, error);
|
||||
break;
|
||||
case 'keypassxml':
|
||||
importKeyPassXml(file, success, error);
|
||||
break;
|
||||
case 'padlockcsv':
|
||||
importPadlockCsv(file, success, error);
|
||||
break;
|
||||
case '1password1pif':
|
||||
import1Password1Pif(file, success, error);
|
||||
break;
|
||||
default:
|
||||
error();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
function trimUri(uri) {
|
||||
if (uri.length > 1000) {
|
||||
return uri.substring(0, 1000);
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
function parseCsvErrors(results) {
|
||||
if (results.errors && results.errors.length) {
|
||||
for (var i = 0; i < results.errors.length; i++) {
|
||||
console.warn('Error parsing row ' + results.errors[i].row + ': ' + results.errors[i].message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function importLocal(file, success, error) {
|
||||
Papa.parse(file, {
|
||||
header: true,
|
||||
encoding: 'UTF-8',
|
||||
complete: function (results) {
|
||||
parseCsvErrors(results);
|
||||
|
||||
var folders = [],
|
||||
sites = [],
|
||||
folderRelationships = [];
|
||||
|
||||
angular.forEach(results.data, function (value, key) {
|
||||
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 && value.uri !== '' ? trimUri(value.uri) : null,
|
||||
username: value.username && value.username !== '' ? value.username : null,
|
||||
password: value.password && value.password !== '' ? value.password : null,
|
||||
notes: value.notes && value.notes !== '' ? value.notes : null,
|
||||
name: value.name && value.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) {
|
||||
if (file.type === 'text/html') {
|
||||
var reader = new FileReader();
|
||||
reader.readAsText(file, 'utf-8');
|
||||
reader.onload = function (evt) {
|
||||
var doc = $(evt.target.result);
|
||||
var pre = doc.find('pre');
|
||||
var csv, results;
|
||||
|
||||
if (pre.length === 1) {
|
||||
csv = pre.text().trim();
|
||||
results = Papa.parse(csv, {
|
||||
header: true,
|
||||
encoding: 'UTF-8'
|
||||
});
|
||||
parseData(results.data);
|
||||
}
|
||||
else {
|
||||
var foundPre = false;
|
||||
for (var i = 0; i < doc.length; i++) {
|
||||
if (doc[i].tagName.toLowerCase() === 'pre') {
|
||||
foundPre = true;
|
||||
csv = doc[i].outerText.trim();
|
||||
results = Papa.parse(csv, {
|
||||
header: true,
|
||||
encoding: 'UTF-8'
|
||||
});
|
||||
parseData(results.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundPre) {
|
||||
error();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = function (evt) {
|
||||
error();
|
||||
};
|
||||
}
|
||||
else {
|
||||
Papa.parse(file, {
|
||||
header: true,
|
||||
encoding: 'UTF-8',
|
||||
complete: function (results) {
|
||||
parseCsvErrors(results);
|
||||
parseData(results.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function parseData(data) {
|
||||
var folders = [],
|
||||
sites = [],
|
||||
siteRelationships = [];
|
||||
|
||||
angular.forEach(data, function (value, key) {
|
||||
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 && value.url !== '' ? trimUri(value.url) : null,
|
||||
username: value.username && value.username !== '' ? value.username : null,
|
||||
password: value.password && value.password !== '' ? value.password : null,
|
||||
notes: value.extra && value.extra !== '' ? value.extra : null,
|
||||
name: value.name && value.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);
|
||||
}
|
||||
}
|
||||
|
||||
function importSafeInCloudCsv(file, success, error) {
|
||||
Papa.parse(file, {
|
||||
header: true,
|
||||
encoding: 'UTF-8',
|
||||
complete: function (results) {
|
||||
parseCsvErrors(results);
|
||||
|
||||
var folders = [],
|
||||
sites = [],
|
||||
siteRelationships = [];
|
||||
|
||||
angular.forEach(results.data, function (value, key) {
|
||||
sites.push({
|
||||
favorite: false,
|
||||
uri: value.URL && value.URL !== '' ? trimUri(value.URL) : null,
|
||||
username: value.Login && value.Login !== '' ? value.Login : null,
|
||||
password: value.Password && value.Password !== '' ? value.Password : null,
|
||||
notes: value.Notes && value.Notes !== '' ? value.Notes : null,
|
||||
name: value.Title && value.Title !== '' ? value.Title : '--',
|
||||
});
|
||||
});
|
||||
|
||||
success(folders, sites, siteRelationships);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function importSafeInCloudXml(file, success, error) {
|
||||
var folders = [],
|
||||
sites = [],
|
||||
siteRelationships = [],
|
||||
foldersIndex = [];
|
||||
|
||||
var i = 0,
|
||||
j = 0;
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.readAsText(file, 'utf-8');
|
||||
reader.onload = function (evt) {
|
||||
var xmlDoc = $.parseXML(evt.target.result),
|
||||
xml = $(xmlDoc);
|
||||
|
||||
var db = xml.find('database');
|
||||
if (db.length) {
|
||||
var labels = db.find('> label');
|
||||
if (labels.length) {
|
||||
for (i = 0; i < labels.length; i++) {
|
||||
var label = $(labels[i]);
|
||||
foldersIndex[label.attr('id')] = folders.length;
|
||||
folders.push({
|
||||
name: label.attr('name')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var cards = db.find('> card');
|
||||
if (cards.length) {
|
||||
for (i = 0; i < cards.length; i++) {
|
||||
var card = $(cards[i]);
|
||||
if (card.attr('template') === 'true') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var site = {
|
||||
favorite: false,
|
||||
uri: null,
|
||||
username: null,
|
||||
password: null,
|
||||
notes: null,
|
||||
name: card.attr('title'),
|
||||
};
|
||||
|
||||
var fields = card.find('> field');
|
||||
for (j = 0; j < fields.length; j++) {
|
||||
var field = $(fields[j]);
|
||||
|
||||
var text = field.text();
|
||||
var type = field.attr('type');
|
||||
|
||||
if (text && text !== '') {
|
||||
if (type === 'login') {
|
||||
site.username = text;
|
||||
}
|
||||
else if (type === 'password') {
|
||||
site.password = text;
|
||||
}
|
||||
else if (type === 'notes') {
|
||||
site.notes = text;
|
||||
}
|
||||
else if (type === 'website') {
|
||||
site.uri = trimUri(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sites.push(site);
|
||||
|
||||
labels = card.find('> label_id');
|
||||
if (labels.length) {
|
||||
var labelId = $(labels[0]).text();
|
||||
var folderIndex = foldersIndex[labelId];
|
||||
if (labelId !== null && labelId !== '' && folderIndex !== null) {
|
||||
siteRelationships.push({
|
||||
key: sites.length - 1,
|
||||
value: folderIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
success(folders, sites, siteRelationships);
|
||||
}
|
||||
else {
|
||||
error();
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = function (evt) {
|
||||
error();
|
||||
};
|
||||
}
|
||||
|
||||
function importPadlockCsv(file, success, error) {
|
||||
Papa.parse(file, {
|
||||
encoding: 'UTF-8',
|
||||
complete: function (results) {
|
||||
parseCsvErrors(results);
|
||||
|
||||
var folders = [],
|
||||
sites = [],
|
||||
folderRelationships = [];
|
||||
|
||||
var customFieldHeaders = [];
|
||||
|
||||
// CSV index ref: 0 = name, 1 = category, 2 = username, 3 = password, 4+ = custom fields
|
||||
|
||||
var i = 0,
|
||||
j = 0;
|
||||
|
||||
for (i = 0; i < results.data.length; i++) {
|
||||
var value = results.data[i];
|
||||
if (i === 0) {
|
||||
// header row
|
||||
for (j = 4; j < value.length; j++) {
|
||||
customFieldHeaders.push(value[j]);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var folderIndex = folders.length,
|
||||
siteIndex = sites.length,
|
||||
hasFolder = value[1] && value[1] !== '',
|
||||
addFolder = hasFolder;
|
||||
|
||||
if (hasFolder) {
|
||||
for (j = 0; j < folders.length; j++) {
|
||||
if (folders[j].name === value[1]) {
|
||||
addFolder = false;
|
||||
folderIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var site = {
|
||||
favorite: false,
|
||||
uri: null,
|
||||
username: value[2] && value[2] !== '' ? value[2] : null,
|
||||
password: value[3] && value[3] !== '' ? value[3] : null,
|
||||
notes: null,
|
||||
name: value[0] && value[0] !== '' ? value[0] : '--',
|
||||
};
|
||||
|
||||
if (customFieldHeaders.length) {
|
||||
for (j = 4; j < value.length; j++) {
|
||||
var cf = value[j];
|
||||
if (!cf || cf === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var cfHeader = customFieldHeaders[j - 4];
|
||||
if (cfHeader.toLowerCase() === 'url' || cfHeader.toLowerCase() === 'uri') {
|
||||
site.uri = trimUri(cf);
|
||||
}
|
||||
else {
|
||||
if (site.notes === null) {
|
||||
site.notes = '';
|
||||
}
|
||||
|
||||
site.notes += cfHeader + ': ' + cf + '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sites.push(site);
|
||||
|
||||
if (addFolder) {
|
||||
folders.push({
|
||||
name: value[1]
|
||||
});
|
||||
}
|
||||
|
||||
if (hasFolder) {
|
||||
folderRelationships.push({
|
||||
key: siteIndex,
|
||||
value: folderIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
success(folders, sites, folderRelationships);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function importKeyPassXml(file, success, error) {
|
||||
var folders = [],
|
||||
sites = [],
|
||||
siteRelationships = [];
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.readAsText(file, 'utf-8');
|
||||
reader.onload = function (evt) {
|
||||
var xmlDoc = $.parseXML(evt.target.result),
|
||||
xml = $(xmlDoc);
|
||||
|
||||
var root = xml.find('Root');
|
||||
if (root.length) {
|
||||
var group = root.find('> Group');
|
||||
if (group.length) {
|
||||
traverse($(group[0]), true, '');
|
||||
success(folders, sites, siteRelationships);
|
||||
}
|
||||
}
|
||||
else {
|
||||
error();
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = function (evt) {
|
||||
error();
|
||||
};
|
||||
|
||||
function traverse(node, isRootNode, groupNamePrefix) {
|
||||
var nodeEntries = [];
|
||||
var folderIndex = folders.length;
|
||||
var groupName = groupNamePrefix;
|
||||
|
||||
if (!isRootNode) {
|
||||
if (groupName !== '') {
|
||||
groupName += ' > ';
|
||||
}
|
||||
groupName += node.find('> Name').text();
|
||||
folders.push({
|
||||
name: groupName
|
||||
});
|
||||
}
|
||||
|
||||
var entries = node.find('> Entry');
|
||||
if (entries.length) {
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var entry = $(entries[i]);
|
||||
var siteIndex = sites.length;
|
||||
var site = {
|
||||
favorite: false,
|
||||
uri: null,
|
||||
username: null,
|
||||
password: null,
|
||||
notes: null,
|
||||
name: null
|
||||
};
|
||||
|
||||
var entryStrings = entry.find('> String');
|
||||
for (var j = 0; j < entryStrings.length; j++) {
|
||||
var entryString = $(entryStrings[j]);
|
||||
|
||||
var key = entryString.find('> Key').text();
|
||||
var value = entryString.find('> Value').text();
|
||||
if (value === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case 'URL':
|
||||
site.uri = trimUri(value);
|
||||
break;
|
||||
case 'UserName':
|
||||
site.username = value;
|
||||
break;
|
||||
case 'Password':
|
||||
site.password = value;
|
||||
break;
|
||||
case 'Title':
|
||||
site.name = value;
|
||||
break;
|
||||
case 'Notes':
|
||||
site.notes = site.notes === null ? value + '\n' : site.notes + value + '\n';
|
||||
break;
|
||||
default:
|
||||
// other custom fields
|
||||
site.notes = site.notes === null ? key + ': ' + value + '\n'
|
||||
: site.notes + key + ': ' + value + '\n';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (site.name === null) {
|
||||
site.name = '--';
|
||||
}
|
||||
|
||||
sites.push(site);
|
||||
|
||||
if (!isRootNode) {
|
||||
siteRelationships.push({
|
||||
key: siteIndex,
|
||||
value: folderIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var groups = node.find('> Group');
|
||||
if (groups.length) {
|
||||
for (var k = 0; k < groups.length; k++) {
|
||||
traverse($(groups[k]), false, groupName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function import1Password1Pif(file, success, error) {
|
||||
var folders = [],
|
||||
sites = [],
|
||||
siteRelationships = [];
|
||||
|
||||
var i = 0,
|
||||
j = 0;
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.readAsText(file, 'utf-8');
|
||||
reader.onload = function (evt) {
|
||||
var fileContent = evt.target.result;
|
||||
var jsonParts = fileContent.split(/(?:\r\n|\r|\n)\*\*\*.*?\*\*\*(?:\r\n|\r|\n)/);
|
||||
var jsonString = '[' + jsonParts.join(',') + ']';
|
||||
var items = JSON.parse(jsonString);
|
||||
|
||||
for (i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
if (item.typeName !== 'webforms.WebForm') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var site = {
|
||||
favorite: item.openContents && item.openContents.faveIndex ? true : false,
|
||||
uri: item.location && item.location !== '' ? trimUri(item.location) : null,
|
||||
username: null,
|
||||
password: null,
|
||||
notes: null,
|
||||
name: item.title && item.title !== '' ? item.title : '--',
|
||||
};
|
||||
|
||||
if (item.secureContents) {
|
||||
if (item.secureContents.notesPlain) {
|
||||
site.notes = item.secureContents.notesPlain;
|
||||
}
|
||||
|
||||
if (item.secureContents.fields) {
|
||||
for (j = 0; j < item.secureContents.fields.length; j++) {
|
||||
var field = item.secureContents.fields[j];
|
||||
if (field.designation === 'username') {
|
||||
site.username = field.value;
|
||||
}
|
||||
else if (field.designation === 'password') {
|
||||
site.password = field.value;
|
||||
}
|
||||
else {
|
||||
if (site.notes === null) {
|
||||
site.notes = '';
|
||||
}
|
||||
else {
|
||||
site.notes += '\n';
|
||||
}
|
||||
|
||||
site.notes += (field.name + ': ' + field.value + '\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sites.push(site);
|
||||
}
|
||||
|
||||
success(folders, sites, siteRelationships);
|
||||
};
|
||||
|
||||
reader.onerror = function (evt) {
|
||||
error();
|
||||
};
|
||||
}
|
||||
|
||||
return _service;
|
||||
});
|
||||
@@ -1,136 +0,0 @@
|
||||
angular
|
||||
.module('bit.services')
|
||||
|
||||
.factory('passwordService', function () {
|
||||
var _service = {};
|
||||
|
||||
_service.generatePassword = function (options) {
|
||||
var defaults = {
|
||||
length: 10,
|
||||
ambiguous: false,
|
||||
number: true,
|
||||
minNumber: 1,
|
||||
uppercase: true,
|
||||
minUppercase: 1,
|
||||
lowercase: true,
|
||||
minLowercase: 1,
|
||||
special: false,
|
||||
minSpecial: 1
|
||||
};
|
||||
|
||||
// overload defaults with given options
|
||||
var o = angular.extend({}, defaults, options);
|
||||
|
||||
// sanitize
|
||||
if (o.uppercase && o.minUppercase < 0) o.minUppercase = 1;
|
||||
if (o.lowercase && o.minLowercase < 0) o.minLowercase = 1;
|
||||
if (o.number && o.minNumber < 0) o.minNumber = 1;
|
||||
if (o.special && o.minSpecial < 0) o.minSpecial = 1;
|
||||
|
||||
if (!o.length || o.length < 1) o.length = 10;
|
||||
var minLength = o.minUppercase + o.minLowercase + o.minNumber + o.minSpecial;
|
||||
if (o.length < minLength) o.length = minLength;
|
||||
|
||||
var positions = [];
|
||||
if (o.lowercase && o.minLowercase > 0) {
|
||||
for (var i = 0; i < o.minLowercase; i++) {
|
||||
positions.push('l');
|
||||
}
|
||||
}
|
||||
if (o.uppercase && o.minUppercase > 0) {
|
||||
for (var j = 0; j < o.minUppercase; j++) {
|
||||
positions.push('u');
|
||||
}
|
||||
}
|
||||
if (o.number && o.minNumber > 0) {
|
||||
for (var k = 0; k < o.minNumber; k++) {
|
||||
positions.push('n');
|
||||
}
|
||||
}
|
||||
if (o.special && o.minSpecial > 0) {
|
||||
for (var l = 0; l < o.minSpecial; l++) {
|
||||
positions.push('s');
|
||||
}
|
||||
}
|
||||
while (positions.length < o.length) {
|
||||
positions.push('a');
|
||||
}
|
||||
|
||||
// shuffle
|
||||
positions.sort(function () {
|
||||
return randomInt(0, 1) * 2 - 1;
|
||||
});
|
||||
|
||||
// build out the char sets
|
||||
var allCharSet = '';
|
||||
|
||||
var lowercaseCharSet = 'abcdefghijkmnopqrstuvwxyz';
|
||||
if (o.ambiguous) lowercaseCharSet += 'l';
|
||||
if (o.lowercase) allCharSet += lowercaseCharSet;
|
||||
|
||||
var uppercaseCharSet = 'ABCDEFGHIJKLMNPQRSTUVWXYZ';
|
||||
if (o.ambiguous) uppercaseCharSet += 'O';
|
||||
if (o.uppercase) allCharSet += uppercaseCharSet;
|
||||
|
||||
var numberCharSet = '23456789';
|
||||
if (o.ambiguous) numberCharSet += '01';
|
||||
if (o.number) allCharSet += numberCharSet;
|
||||
|
||||
var specialCharSet = '!@#$%^&*';
|
||||
if (o.special) allCharSet += specialCharSet;
|
||||
|
||||
var password = '';
|
||||
for (var m = 0; m < o.length; m++) {
|
||||
var positionChars;
|
||||
switch (positions[m]) {
|
||||
case 'l': positionChars = lowercaseCharSet; break;
|
||||
case 'u': positionChars = uppercaseCharSet; break;
|
||||
case 'n': positionChars = numberCharSet; break;
|
||||
case 's': positionChars = specialCharSet; break;
|
||||
case 'a': positionChars = allCharSet; break;
|
||||
}
|
||||
|
||||
var randomCharIndex = randomInt(0, positionChars.length - 1);
|
||||
password += positionChars.charAt(randomCharIndex);
|
||||
}
|
||||
|
||||
return password;
|
||||
};
|
||||
|
||||
// EFForg/OpenWireless
|
||||
// ref https://github.com/EFForg/OpenWireless/blob/master/app/js/diceware.js
|
||||
function randomInt(min, max) {
|
||||
var rval = 0;
|
||||
var range = max - min;
|
||||
|
||||
var bits_needed = Math.ceil(Math.log2(range));
|
||||
if (bits_needed > 53) {
|
||||
throw new Exception("We cannot generate numbers larger than 53 bits.");
|
||||
}
|
||||
var bytes_needed = Math.ceil(bits_needed / 8);
|
||||
var mask = Math.pow(2, bits_needed) - 1;
|
||||
// 7776 -> (2^13 = 8192) -1 == 8191 or 0x00001111 11111111
|
||||
|
||||
// Create byte array and fill with N random numbers
|
||||
var byteArray = new Uint8Array(bytes_needed);
|
||||
window.crypto.getRandomValues(byteArray);
|
||||
|
||||
var p = (bytes_needed - 1) * 8;
|
||||
for (var i = 0; i < bytes_needed; i++) {
|
||||
rval += byteArray[i] * Math.pow(2, p);
|
||||
p -= 8;
|
||||
}
|
||||
|
||||
// Use & to apply the mask and reduce the number of recursive lookups
|
||||
rval = rval & mask;
|
||||
|
||||
if (rval >= range) {
|
||||
// Integer out of acceptable range
|
||||
return randomInt(min, max);
|
||||
}
|
||||
// Return an integer that falls within the range
|
||||
return min + rval;
|
||||
}
|
||||
|
||||
return _service;
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
angular
|
||||
.module('bit.services', ['ngResource', 'ngStorage', 'angular-jwt']);
|
||||
@@ -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;
|
||||
});
|
||||
@@ -1,62 +0,0 @@
|
||||
angular
|
||||
.module('bit.services')
|
||||
|
||||
.factory('validationService', function () {
|
||||
var _service = {};
|
||||
|
||||
_service.addErrors = function (form, reason) {
|
||||
var data = reason.data;
|
||||
var defaultErrorMessage = 'An unexpected error has occured.';
|
||||
form.$errors = [];
|
||||
|
||||
if (!data || !angular.isObject(data)) {
|
||||
form.$errors.push(defaultErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.ValidationErrors) {
|
||||
if (data.Message) {
|
||||
form.$errors.push(data.Message);
|
||||
}
|
||||
else {
|
||||
form.$errors.push(defaultErrorMessage);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (var key in data.ValidationErrors) {
|
||||
if (!data.ValidationErrors.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var i = 0; i < data.ValidationErrors[key].length; i++) {
|
||||
_service.addError(form, key, data.ValidationErrors[key][i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_service.addError = function (form, key, errorMessage, clearExistingErrors) {
|
||||
if (clearExistingErrors || !form.$errors) {
|
||||
form.$errors = [];
|
||||
}
|
||||
|
||||
var pushError = true;
|
||||
for (var i = 0; i < form.$errors.length; i++) {
|
||||
if (form.$errors[i] === errorMessage) {
|
||||
pushError = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pushError) {
|
||||
form.$errors.push(errorMessage);
|
||||
}
|
||||
|
||||
if (key && key !== '' && form[key] && form[key].$registerApiError) {
|
||||
form[key].$registerApiError();
|
||||
}
|
||||
};
|
||||
|
||||
return _service;
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
angular.module("bit")
|
||||
.constant("appSettings", {"rememberedEmailCookieName":"bit.rememberedEmail","version":"1.2.1","environment":"Development","apiUri":"http://localhost:4000"});
|
||||
@@ -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');
|
||||
};
|
||||
});
|
||||
@@ -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');
|
||||
};
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
};
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
angular
|
||||
.module('bit.settings', ['ui.bootstrap', 'toastr']);
|
||||
@@ -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');
|
||||
};
|
||||
});
|
||||
@@ -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');
|
||||
};
|
||||
});
|
||||
@@ -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>
|
||||
@@ -1,54 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="changeEmailModelLabel"><i class="fa fa-at"></i> Change Email</h4>
|
||||
</div>
|
||||
<form name="changeEmailForm" ng-submit="changeEmailForm.$valid && token(model)" api-form="tokenPromise" ng-show="!tokenSent && !processing">
|
||||
<div class="modal-body">
|
||||
<p>Below you can change your account's email address.</p>
|
||||
<div class="callout callout-danger validation-errors" ng-show="changeEmailForm.$errors">
|
||||
<h4>Errors have occured</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in changeEmailForm.$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="model.masterPassword" class="form-control"
|
||||
required api-field />
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="newEmail">New Email</label>
|
||||
<input type="email" id="newEmail" name="NewEmail" ng-model="model.newEmail" class="form-control" required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="changeEmailForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="changeEmailForm.$loading"></i>Submit
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
<form name="changeEmailConfirmForm" ng-submit="changeEmailConfirmForm.$valid && confirm(model)" api-form="confirmPromise"
|
||||
ng-show="tokenSent && !processing">
|
||||
<div class="modal-body">
|
||||
<p>We have emailed a verification code to <b>{{model.newEmail}}</b>. Please check your email for this code and enter it below to finalize your the email address change.</p>
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> Warning</h4>
|
||||
Proceeding will log you out of your current session, requiring you to log back in.
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="token">Code</label>
|
||||
<input type="number" id="token" name="Token" ng-model="model.token" class="form-control" required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat">
|
||||
Change Email
|
||||
</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 changing your email and reencrypting all of your data. Do not close this window. You will be automatically logged out when this process has finished.</p>
|
||||
</div>
|
||||
@@ -1,46 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="changePasswrdModelLabel"><i class="fa fa-key"></i> Change Password</h4>
|
||||
</div>
|
||||
<form name="changePasswordForm" ng-submit="changePasswordForm.$valid && save(model, changePasswordForm)" api-form="savePromise" ng-show="!processing">
|
||||
<div class="modal-body">
|
||||
<p>Below you can change your account's master password.</p>
|
||||
<p>We recommend that you change your master password immediately if you believe that your credentials have been compromised.</p>
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> Warning</h4>
|
||||
Proceeding will log you out of your current session, requiring you to log back in.
|
||||
</div>
|
||||
<div class="callout callout-danger validation-errors" ng-show="changePasswordForm.$errors">
|
||||
<h4>Errors have occured</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in changePasswordForm.$errors">{{e}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="masterPassword">Current Master Password</label>
|
||||
<input type="password" id="masterPassword" name="MasterPasswordHash" ng-model="model.masterPassword" class="form-control"
|
||||
required api-field />
|
||||
</div>
|
||||
<hr />
|
||||
<div class="form-group" show-errors>
|
||||
<label for="newMasterPassword">New Master Password</label>
|
||||
<input type="password" id="newMasterPassword" name="NewMasterPasswordHash" ng-model="model.newMasterPassword" class="form-control"
|
||||
required api-field />
|
||||
</div>
|
||||
<div class="form-group" show-errors>
|
||||
<label for="confirmNewMasterPassword">Confirm New Master Password</label>
|
||||
<input type="password" id="confirmNewMasterPassword" name="ConfirmNewMasterPasswordHash" ng-model="model.confirmNewMasterPassword"
|
||||
class="form-control" required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat">
|
||||
Change Password
|
||||
</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 changing your password and reencrypting all of your data. Do not close this window. You will be automatically logged out when this process has finished.</p>
|
||||
</div>
|
||||
@@ -1,30 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="deleteAccountModelLabel"><i class="fa fa-trash"></i> Delete Account</h4>
|
||||
</div>
|
||||
<form name="deleteAccountForm" ng-submit="deleteAccountForm.$valid && submit(model)" api-form="submitPromise">
|
||||
<div class="modal-body">
|
||||
<p>Continue below to delete your account and all associated data.</p>
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> Warning</h4>
|
||||
Deleting your account is permanent. It cannot be undone.
|
||||
</div>
|
||||
<div class="callout callout-danger validation-errors" ng-show="deleteAccountForm.$errors">
|
||||
<h4>Errors have occured</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in deleteAccountForm.$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="model.masterPassword" class="form-control"
|
||||
required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="deleteAccountForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="deleteAccountForm.$loading"></i>Delete
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,32 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="logoutSessionsModelLabel"><i class="fa fa-ban"></i> Deauthorize Sessions</h4>
|
||||
</div>
|
||||
<form name="logoutSessionsForm" ng-submit="logoutSessionsForm.$valid && submit(model)" api-form="submitPromise">
|
||||
<div class="modal-body">
|
||||
<p>Concerned your account is logged in on another device?</p>
|
||||
<p>Proceed below to deauthorize all computers or devices that you have previously used.</p>
|
||||
<p>This security step is recommended if you previously used a public PC or accidentally saved your password on a device that isn't yours.</p>
|
||||
<div class="callout callout-warning">
|
||||
<h4><i class="fa fa-warning"></i> Warning</h4>
|
||||
Proceeding will log you out of your current session as well, requiring you to log back in.
|
||||
</div>
|
||||
<div class="callout callout-danger validation-errors" ng-show="logoutSessionsForm.$errors">
|
||||
<h4>Errors have occured</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in logoutSessionsForm.$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="model.masterPassword" class="form-control"
|
||||
required api-field />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="logoutSessionsForm.$loading">
|
||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="logoutSessionsForm.$loading"></i>Deauthorize
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,82 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</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>
|
||||
@@ -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');
|
||||
};
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
angular
|
||||
.module('bit.tools')
|
||||
|
||||
.controller('toolsExportController', function ($scope, apiService, authService, $uibModalInstance, cryptoService, cipherService, $q, toastr, $analytics) {
|
||||
$analytics.eventTrack('toolsExportController', { category: 'Modal' });
|
||||
$scope.export = function (model) {
|
||||
$scope.startedExport = true;
|
||||
apiService.sites.list({ expand: ['folder'] }, function (sites) {
|
||||
try {
|
||||
var decSites = cipherService.decryptSites(sites.Data);
|
||||
|
||||
var exportSites = [];
|
||||
for (var i = 0; i < decSites.length; i++) {
|
||||
var site = {
|
||||
name: decSites[i].name,
|
||||
uri: decSites[i].uri,
|
||||
username: decSites[i].username,
|
||||
password: decSites[i].password,
|
||||
notes: decSites[i].notes,
|
||||
folder: decSites[i].folder ? decSites[i].folder.name : null
|
||||
};
|
||||
|
||||
exportSites.push(site);
|
||||
}
|
||||
|
||||
var csvString = Papa.unparse(exportSites);
|
||||
var csvBlob = new Blob([csvString]);
|
||||
if (window.navigator.msSaveOrOpenBlob) { // IE hack. ref http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx
|
||||
window.navigator.msSaveBlob(csvBlob, makeFileName());
|
||||
}
|
||||
else {
|
||||
var a = window.document.createElement('a');
|
||||
a.href = window.URL.createObjectURL(csvBlob, { type: 'text/plain' });
|
||||
a.download = makeFileName();
|
||||
document.body.appendChild(a);
|
||||
a.click(); // IE: "Access is denied". ref: https://connect.microsoft.com/IE/feedback/details/797361/ie-10-treats-blob-url-as-cross-origin-and-denies-access
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
$analytics.eventTrack('Exported Data');
|
||||
toastr.success('Your data has been exported. Check your browser\'s downloads folder.', 'Success!');
|
||||
$scope.close();
|
||||
}
|
||||
catch (err) {
|
||||
toastr.error('Something went wrong. Please try again.', 'Error!');
|
||||
$scope.close();
|
||||
}
|
||||
}, function () {
|
||||
toastr.error('Something went wrong. Please try again.', 'Error!');
|
||||
$scope.close();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
|
||||
function makeFileName() {
|
||||
var now = new Date();
|
||||
var dateString =
|
||||
now.getFullYear() + '' + padNumber(now.getMonth() + 1, 2) + '' + padNumber(now.getDate(), 2) +
|
||||
padNumber(now.getHours(), 2) + '' + padNumber(now.getMinutes(), 2) +
|
||||
padNumber(now.getSeconds(), 2);
|
||||
|
||||
return 'bitwarden_export_' + dateString + '.csv';
|
||||
}
|
||||
|
||||
function padNumber(number, width, paddingCharacter) {
|
||||
paddingCharacter = paddingCharacter || '0';
|
||||
number = number + '';
|
||||
return number.length >= width ? number : new Array(width - number.length + 1).join(paddingCharacter) + number;
|
||||
}
|
||||
});
|
||||
@@ -1,47 +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) {
|
||||
if (!folder.length && !sites.length) {
|
||||
toastr.error('Nothing was imported.');
|
||||
return;
|
||||
}
|
||||
|
||||
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: $scope.model.source });
|
||||
toastr.success('Data has been successfully imported into your vault.', 'Import Success');
|
||||
});
|
||||
}, importError);
|
||||
}
|
||||
|
||||
function importError(errorMessage) {
|
||||
$analytics.eventTrack('Import Data Failed', { label: $scope.model.source });
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
if (errorMessage) {
|
||||
toastr.error(errorMessage);
|
||||
}
|
||||
else {
|
||||
toastr.error('Something went wrong. Try again.', 'Oh No!');
|
||||
}
|
||||
}
|
||||
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
angular
|
||||
.module('bit.tools', ['ui.bootstrap', 'toastr']);
|
||||
@@ -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>
|
||||
@@ -1,10 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</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>
|
||||
@@ -1,28 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="exportModelLabel"><i class="fa fa-cloud-download"></i> Export</h4>
|
||||
</div>
|
||||
<form name="exportForm" ng-submit="exportForm.$valid && export(model)" api-form="exportPromise" ng-show="!startedExport">
|
||||
<div class="modal-body">
|
||||
<p>Export all of your vault data in <code>.csv</code> format. Enter your master password to continue.</p>
|
||||
<div class="callout callout-danger validation-errors" ng-show="exportForm.$errors">
|
||||
<h4>Errors have occured</h4>
|
||||
<ul>
|
||||
<li ng-repeat="e in exportForm.$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="model.masterPassword" class="form-control"
|
||||
master-password required api-field ng-model-options="{ 'updateOn': 'blur'}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-flat">Export</button>
|
||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="modal-body text-center" ng-show="startedExport">
|
||||
<p><i class="fa fa-cog fa-spin fa-3x"></i></p>
|
||||
<p>Please wait. We are now exporting all of your data to a <code>.csv</code> file.</p>
|
||||
</div>
|
||||
@@ -1,34 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</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 (csv)</option>
|
||||
<option value="lastpass">LastPass (csv)</option>
|
||||
<option value="safeincloudxml">SafeInCloud (xml)</option>
|
||||
<option value="safeincloudcsv">SafeInCloud (csv)</option>
|
||||
<option value="keypassxml">KeyPass (xml)</option>
|
||||
<option value="padlockcsv">Padlock (csv)</option>
|
||||
<option value="1password1pif">1Password (1pif)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="file">File</label>
|
||||
<input type="file" id="file" name="file" required />
|
||||
</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>
|
||||
@@ -1,19 +0,0 @@
|
||||
angular
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultAddFolderController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService, $analytics) {
|
||||
$analytics.eventTrack('vaultAddFolderController', { category: 'Modal' });
|
||||
$scope.savePromise = null;
|
||||
$scope.save = function (model) {
|
||||
var folder = cipherService.encryptFolder(model);
|
||||
$scope.savePromise = apiService.folders.post(folder, function (response) {
|
||||
$analytics.eventTrack('Created Folder');
|
||||
var decFolder = cipherService.decryptFolder(response);
|
||||
$uibModalInstance.close(decFolder);
|
||||
}).$promise;
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('close');
|
||||
};
|
||||
});
|
||||
@@ -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');
|
||||
};
|
||||
});
|
||||
@@ -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;
|
||||
};
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
angular
|
||||
.module('bit.vault')
|
||||
|
||||
.controller('vaultEditFolderController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService, folderId, $analytics) {
|
||||
$analytics.eventTrack('vaultEditFolderController', { category: 'Modal' });
|
||||
$scope.folder = {};
|
||||
|
||||
apiService.folders.get({ id: folderId }, function (folder) {
|
||||
$scope.folder = cipherService.decryptFolder(folder);
|
||||
});
|
||||
|
||||
$scope.savePromise = null;
|
||||
$scope.save = function (model) {
|
||||
var folder = cipherService.encryptFolder(model);
|
||||
$scope.savePromise = apiService.folders.put({ id: folderId }, folder, function (response) {
|
||||
$analytics.eventTrack('Edited Folder');
|
||||
var decFolder = cipherService.decryptFolder(response);
|
||||
$uibModalInstance.close(decFolder);
|
||||
}).$promise;
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
});
|
||||
@@ -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');
|
||||
};
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
angular
|
||||
.module('bit.vault', ['ui.bootstrap', 'ngclipboard']);
|
||||
@@ -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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user