mirror of
https://github.com/bitwarden/web
synced 2025-12-12 14:23:18 +00:00
Compare commits
612 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
c5de290dd4 | ||
|
|
21e9e083f5 | ||
|
|
517ea65bc9 | ||
|
|
e7a9699226 | ||
|
|
52b3dfd0e3 | ||
|
|
4c0bde9d87 | ||
|
|
f29bbe4316 | ||
|
|
3c03ed8636 | ||
|
|
71ca4eb84a | ||
|
|
30d19b4ee1 | ||
|
|
7b88d30aa8 | ||
|
|
0ae11cc40c | ||
|
|
dd5cda867d | ||
|
|
2d11bef262 | ||
|
|
5d5d0bfb66 | ||
|
|
1b169f368b | ||
|
|
3375bda789 | ||
|
|
6569fbe6aa | ||
|
|
1d2b82a302 | ||
|
|
004ddb1e75 | ||
|
|
400826baf7 | ||
|
|
96ae20d81d | ||
|
|
ef5f4df30f | ||
|
|
2ce1b12f6e | ||
|
|
85efba92e6 | ||
|
|
d17211ff86 |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*
|
||||||
|
!dist/*
|
||||||
|
!entrypoint.sh
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -199,4 +199,5 @@ FakesAssemblies/
|
|||||||
*.opt
|
*.opt
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
project.lock.json
|
package-lock.json
|
||||||
|
src/js/*.min.js
|
||||||
10
Dockerfile
Normal file
10
Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM bitwarden/server
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY ./dist .
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
COPY entrypoint.sh /
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
@@ -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>
|
|
||||||
25
README.md
25
README.md
@@ -1,9 +1,32 @@
|
|||||||
|
[](https://ci.appveyor.com/project/bitwarden/web) [](https://gitter.im/bitwarden/Lobby)
|
||||||
|
|
||||||
# bitwarden Web
|
# bitwarden Web
|
||||||
|
|
||||||
The bitwarden Web project is an AngularJS application that powers the web vault (https://vault.bitwarden.com/).
|
The bitwarden Web project is an AngularJS application that powers the web vault (https://vault.bitwarden.com/).
|
||||||
|
|
||||||
|
<img src="https://i.imgur.com/rxrykeX.png" alt="" width="791" height="739" />
|
||||||
|
|
||||||
|
# Build/Run
|
||||||
|
|
||||||
|
**Requirements**
|
||||||
|
|
||||||
|
- Node.js
|
||||||
|
- Gulp
|
||||||
|
|
||||||
|
By default the application points to the production API. If you want to change that to point to a local instance of
|
||||||
|
the [Core](https://github.com/bitwarden/core) API, you can modify the `package.json` `env` property to `Development`
|
||||||
|
and then set your local endpoints in `settings.json`.
|
||||||
|
|
||||||
|
Then run the following commands:
|
||||||
|
|
||||||
|
- `npm install`
|
||||||
|
- `gulp build`
|
||||||
|
- `gulp serve`
|
||||||
|
|
||||||
|
You can now access the web vault at `http://localhost:4001`.
|
||||||
|
|
||||||
# Contribute
|
# Contribute
|
||||||
|
|
||||||
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
||||||
|
|
||||||
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 +1,39 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 14.0.25420.1
|
VisualStudioVersion = 15.0.26228.9
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{860863C9-0436-43D4-840D-FE919C9F6FFC}"
|
Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "bitwarden-web", ".", "{25BEDEF4-2CAF-445A-807D-63C17FF85694}"
|
||||||
EndProject
|
ProjectSection(WebsiteProperties) = preProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{14FE7221-D377-4AD5-9A9E-4541577CF05A}"
|
TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.6.1"
|
||||||
ProjectSection(SolutionItems) = preProject
|
Debug.AspNetCompiler.VirtualPath = "/localhost_15509"
|
||||||
.gitignore = .gitignore
|
Debug.AspNetCompiler.PhysicalPath = "."
|
||||||
CNAME = CNAME
|
Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_15509\"
|
||||||
global.json = global.json
|
Debug.AspNetCompiler.Updateable = "true"
|
||||||
NuGet.Config = NuGet.Config
|
Debug.AspNetCompiler.ForceOverwrite = "true"
|
||||||
README.md = README.md
|
Debug.AspNetCompiler.FixedNames = "false"
|
||||||
|
Debug.AspNetCompiler.Debug = "True"
|
||||||
|
Release.AspNetCompiler.VirtualPath = "/localhost_15509"
|
||||||
|
Release.AspNetCompiler.PhysicalPath = "."
|
||||||
|
Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_15509\"
|
||||||
|
Release.AspNetCompiler.Updateable = "true"
|
||||||
|
Release.AspNetCompiler.ForceOverwrite = "true"
|
||||||
|
Release.AspNetCompiler.FixedNames = "false"
|
||||||
|
Release.AspNetCompiler.Debug = "False"
|
||||||
|
VWDPort = "15509"
|
||||||
|
SlnRelativePath = "."
|
||||||
|
DefaultWebSiteLanguage = "Visual C#"
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Web", "src\Web\Web.xproj", "{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{25BEDEF4-2CAF-445A-807D-63C17FF85694}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{25BEDEF4-2CAF-445A-807D-63C17FF85694}.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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
|
||||||
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5} = {860863C9-0436-43D4-840D-FE919C9F6FFC}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
13
build.ps1
Normal file
13
build.ps1
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
$dir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
|
||||||
|
echo "`n# Building Web"
|
||||||
|
|
||||||
|
echo "`nBuilding app"
|
||||||
|
echo "npm version $(npm --version)"
|
||||||
|
echo "gulp version $(gulp --version)"
|
||||||
|
npm install
|
||||||
|
gulp dist:selfHosted
|
||||||
|
|
||||||
|
echo "`nBuilding docker image"
|
||||||
|
docker --version
|
||||||
|
docker build -t bitwarden/web $dir\.
|
||||||
39
build.sh
Normal file
39
build.sh
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ $# -gt 0 -a "$1" == "push" ]
|
||||||
|
then
|
||||||
|
echo "# Pushing Web"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ $# -gt 1 ]
|
||||||
|
then
|
||||||
|
TAG=$2
|
||||||
|
docker push bitwarden/web:$TAG
|
||||||
|
else
|
||||||
|
docker push bitwarden/web
|
||||||
|
fi
|
||||||
|
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)"
|
||||||
|
echo "gulp version $(gulp --version)"
|
||||||
|
npm install
|
||||||
|
gulp dist:selfHosted
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Building docker image"
|
||||||
|
docker --version
|
||||||
|
docker build -t bitwarden/web $DIR/.
|
||||||
|
fi
|
||||||
5
entrypoint.sh
Normal file
5
entrypoint.sh
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
cp /etc/bitwarden/web/settings.js /app/js/settings.js
|
||||||
|
cp /etc/bitwarden/web/app-id.json /app/app-id.json
|
||||||
|
dotnet /bitwarden_server/Server.dll /contentRoot=/app /webRoot=. /serveUnknown=false
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"projects": [ "src", "test" ],
|
|
||||||
"sdk": {
|
|
||||||
"version": "1.0.0-preview2-003121"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,28 +8,35 @@ var gulp = require('gulp'),
|
|||||||
uglify = require('gulp-uglify'),
|
uglify = require('gulp-uglify'),
|
||||||
ghPages = require('gulp-gh-pages'),
|
ghPages = require('gulp-gh-pages'),
|
||||||
less = require('gulp-less'),
|
less = require('gulp-less'),
|
||||||
|
connect = require('gulp-connect'),
|
||||||
ngAnnotate = require('gulp-ng-annotate'),
|
ngAnnotate = require('gulp-ng-annotate'),
|
||||||
preprocess = require('gulp-preprocess'),
|
preprocess = require('gulp-preprocess'),
|
||||||
runSequence = require('run-sequence'),
|
runSequence = require('run-sequence'),
|
||||||
merge = require('merge-stream'),
|
merge = require('merge-stream'),
|
||||||
ngConfig = require('gulp-ng-config'),
|
ngConfig = require('gulp-ng-config'),
|
||||||
settings = require('./settings.json'),
|
settings = require('./settings.json'),
|
||||||
project = require('./project.json'),
|
project = require('./package.json'),
|
||||||
jshint = require('gulp-jshint'),
|
jshint = require('gulp-jshint'),
|
||||||
_ = require('lodash');
|
_ = require('lodash'),
|
||||||
|
webpack = require('webpack-stream'),
|
||||||
|
browserify = require('browserify'),
|
||||||
|
derequire = require('gulp-derequire'),
|
||||||
|
source = require('vinyl-source-stream');
|
||||||
|
|
||||||
var paths = {};
|
var paths = {};
|
||||||
paths.dist = '../../dist/';
|
paths.dist = './dist/';
|
||||||
paths.webroot = './wwwroot/'
|
paths.webroot = './src/'
|
||||||
paths.js = paths.webroot + 'js/**/*.js';
|
paths.js = paths.webroot + 'js/**/*.js';
|
||||||
paths.minJs = paths.webroot + 'js/**/*.min.js';
|
paths.minJs = paths.webroot + 'js/**/*.min.js';
|
||||||
paths.concatJsDest = paths.webroot + 'js/bw.min.js';
|
paths.concatJsDest = paths.webroot + 'js/bw.min.js';
|
||||||
paths.libDir = paths.webroot + 'lib/';
|
paths.libDir = paths.webroot + 'lib/';
|
||||||
paths.npmDir = 'node_modules/';
|
paths.npmDir = 'node_modules/';
|
||||||
paths.lessDir = 'less/';
|
paths.lessDir = paths.webroot + 'less/';
|
||||||
paths.cssDir = paths.webroot + 'css/';
|
paths.cssDir = paths.webroot + 'css/';
|
||||||
paths.jsDir = paths.webroot + 'js/';
|
paths.jsDir = paths.webroot + 'js/';
|
||||||
|
|
||||||
|
var randomString = Math.random().toString(36).substring(7);
|
||||||
|
|
||||||
gulp.task('lint', function () {
|
gulp.task('lint', function () {
|
||||||
return gulp.src(paths.webroot + 'app/**/*.js')
|
return gulp.src(paths.webroot + 'app/**/*.js')
|
||||||
.pipe(jshint())
|
.pipe(jshint())
|
||||||
@@ -39,7 +46,7 @@ gulp.task('lint', function () {
|
|||||||
gulp.task('build', function (cb) {
|
gulp.task('build', function (cb) {
|
||||||
return runSequence(
|
return runSequence(
|
||||||
'clean',
|
'clean',
|
||||||
['lib', 'less', 'settings', 'lint'],
|
['browserify', 'lib', 'webpack', 'less', 'settings', 'lint', 'min:js'],
|
||||||
cb);
|
cb);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,7 +65,16 @@ gulp.task('clean:lib', function (cb) {
|
|||||||
gulp.task('clean', ['clean:js', 'clean:css', 'clean:lib', 'dist:clean']);
|
gulp.task('clean', ['clean:js', 'clean:css', 'clean:lib', 'dist:clean']);
|
||||||
|
|
||||||
gulp.task('min:js', ['clean:js'], function () {
|
gulp.task('min:js', ['clean:js'], function () {
|
||||||
return gulp.src([paths.js, '!' + paths.minJs], { base: '.' })
|
return gulp.src(
|
||||||
|
[
|
||||||
|
paths.js,
|
||||||
|
'!' + paths.minJs,
|
||||||
|
'!' + paths.jsDir + 'fallback*.js',
|
||||||
|
'!' + paths.jsDir + 'u2f-connector.js',
|
||||||
|
'!' + paths.jsDir + 'duo.js',
|
||||||
|
'!' + paths.jsDir + 'settings.js'
|
||||||
|
], { base: '.' })
|
||||||
|
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||||
.pipe(concat(paths.concatJsDest))
|
.pipe(concat(paths.concatJsDest))
|
||||||
.pipe(uglify())
|
.pipe(uglify())
|
||||||
.pipe(gulp.dest('.'));
|
.pipe(gulp.dest('.'));
|
||||||
@@ -104,8 +120,8 @@ gulp.task('lib', ['clean:lib'], function () {
|
|||||||
dest: paths.libDir + 'angular'
|
dest: paths.libDir + 'angular'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: paths.npmDir + 'angular-bootstrap-npm/dist/*tpls*.js',
|
src: paths.npmDir + 'angular-ui-bootstrap/dist/*tpls*.js',
|
||||||
dest: paths.libDir + 'angular-bootstrap'
|
dest: paths.libDir + 'angular-ui-bootstrap'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: paths.npmDir + 'angular-bootstrap-show-errors/src/*.js',
|
src: paths.npmDir + 'angular-bootstrap-show-errors/src/*.js',
|
||||||
@@ -119,14 +135,14 @@ gulp.task('lib', ['clean:lib'], function () {
|
|||||||
src: paths.npmDir + 'angular-jwt/dist/*.js',
|
src: paths.npmDir + 'angular-jwt/dist/*.js',
|
||||||
dest: paths.libDir + 'angular-jwt'
|
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',
|
src: paths.npmDir + 'angular-resource/*resource*.js',
|
||||||
dest: paths.libDir + 'angular-resource'
|
dest: paths.libDir + 'angular-resource'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'angular-sanitize/*sanitize*.js',
|
||||||
|
dest: paths.libDir + 'angular-sanitize'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
src: [paths.npmDir + 'angular-toastr/dist/**/*.css', paths.npmDir + 'angular-toastr/dist/**/*.js'],
|
src: [paths.npmDir + 'angular-toastr/dist/**/*.css', paths.npmDir + 'angular-toastr/dist/**/*.js'],
|
||||||
dest: paths.libDir + 'angular-toastr'
|
dest: paths.libDir + 'angular-toastr'
|
||||||
@@ -139,10 +155,6 @@ gulp.task('lib', ['clean:lib'], function () {
|
|||||||
src: paths.npmDir + 'angular-messages/*messages*.js',
|
src: paths.npmDir + 'angular-messages/*messages*.js',
|
||||||
dest: paths.libDir + 'angular-messages'
|
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',
|
src: paths.npmDir + 'ngstorage/*.js',
|
||||||
dest: paths.libDir + 'ngstorage'
|
dest: paths.libDir + 'ngstorage'
|
||||||
@@ -159,12 +171,28 @@ gulp.task('lib', ['clean:lib'], function () {
|
|||||||
src: paths.npmDir + 'clipboard/dist/clipboard*.js',
|
src: paths.npmDir + 'clipboard/dist/clipboard*.js',
|
||||||
dest: paths.libDir + 'clipboard'
|
dest: paths.libDir + 'clipboard'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'node-forge/dist/prime.worker.*',
|
||||||
|
dest: paths.libDir + 'forge'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
src: [
|
src: [
|
||||||
paths.npmDir + 'angulartics-google-analytics/lib/angulartics*.js',
|
paths.npmDir + 'angulartics-google-analytics/lib/angulartics*.js',
|
||||||
paths.npmDir + 'angulartics/src/angulartics.js'
|
paths.npmDir + 'angulartics/src/angulartics.js'
|
||||||
],
|
],
|
||||||
dest: paths.libDir + 'angulartics'
|
dest: paths.libDir + 'angulartics'
|
||||||
|
},
|
||||||
|
//{
|
||||||
|
// src: paths.npmDir + 'duo_web_sdk/index.js',
|
||||||
|
// dest: paths.libDir + 'duo'
|
||||||
|
//},
|
||||||
|
{
|
||||||
|
src: paths.jsDir + 'duo.js',
|
||||||
|
dest: paths.libDir + 'duo'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'angular-promise-polyfill/index.js',
|
||||||
|
dest: paths.libDir + 'angular-promise-polyfill'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -175,6 +203,34 @@ gulp.task('lib', ['clean:lib'], function () {
|
|||||||
return merge(tasks);
|
return merge(tasks);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('webpack', ['webpack:forge']);
|
||||||
|
|
||||||
|
gulp.task('webpack:forge', function () {
|
||||||
|
var forgeDir = paths.npmDir + '/node-forge/lib/';
|
||||||
|
|
||||||
|
return gulp.src([
|
||||||
|
forgeDir + 'pbkdf2.js',
|
||||||
|
forgeDir + 'aes.js',
|
||||||
|
forgeDir + 'rsa.js',
|
||||||
|
forgeDir + 'hmac.js',
|
||||||
|
forgeDir + 'sha256.js',
|
||||||
|
forgeDir + 'random.js',
|
||||||
|
forgeDir + 'forge.js'
|
||||||
|
]).pipe(webpack({
|
||||||
|
output: {
|
||||||
|
filename: 'forge.js',
|
||||||
|
library: 'forge',
|
||||||
|
libraryTarget: 'umd'
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
Buffer: false,
|
||||||
|
process: false,
|
||||||
|
crypto: false,
|
||||||
|
setImmediate: false
|
||||||
|
}
|
||||||
|
})).pipe(gulp.dest(paths.libDir + 'forge'));
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('settings', function () {
|
gulp.task('settings', function () {
|
||||||
return config()
|
return config()
|
||||||
.pipe(gulp.dest(paths.webroot + 'app'));
|
.pipe(gulp.dest(paths.webroot + 'app'));
|
||||||
@@ -186,10 +242,11 @@ function config() {
|
|||||||
createModule: false,
|
createModule: false,
|
||||||
constants: _.merge({}, {
|
constants: _.merge({}, {
|
||||||
appSettings: {
|
appSettings: {
|
||||||
|
selfHosted: false,
|
||||||
version: project.version,
|
version: project.version,
|
||||||
environment: project.environment
|
environment: project.env
|
||||||
}
|
}
|
||||||
}, require('./settings.' + project.environment + '.json') || {})
|
}, require('./settings' + (project.env !== 'Development' ? ('.' + project.env) : '') + '.json') || {})
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,14 +261,43 @@ gulp.task('watch', function () {
|
|||||||
gulp.watch('./settings*.json', ['settings']);
|
gulp.watch('./settings*.json', ['settings']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('browserify', ['browserify:stripe', 'browserify:cc']);
|
||||||
|
|
||||||
|
gulp.task('browserify:stripe', function () {
|
||||||
|
return browserify(paths.npmDir + 'angular-stripe/src/index.js',
|
||||||
|
{
|
||||||
|
entry: '.',
|
||||||
|
standalone: 'angularStripe',
|
||||||
|
global: true
|
||||||
|
})
|
||||||
|
.transform('exposify', { expose: { angular: 'angular' } })
|
||||||
|
.bundle()
|
||||||
|
.pipe(source('angular-stripe.js'))
|
||||||
|
.pipe(derequire())
|
||||||
|
.pipe(gulp.dest(paths.libDir + 'angular-stripe'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('browserify:cc', function () {
|
||||||
|
return browserify(paths.npmDir + 'angular-credit-cards/src/index.js',
|
||||||
|
{
|
||||||
|
entry: '.',
|
||||||
|
standalone: 'angularCreditCards'
|
||||||
|
})
|
||||||
|
.transform('exposify', { expose: { angular: 'angular' } })
|
||||||
|
.bundle()
|
||||||
|
.pipe(source('angular-credit-cards.js'))
|
||||||
|
.pipe(derequire())
|
||||||
|
.pipe(gulp.dest(paths.libDir + 'angular-credit-cards'));
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('dist:clean', function (cb) {
|
gulp.task('dist:clean', function (cb) {
|
||||||
return rimraf(paths.dist, cb);
|
return rimraf(paths.dist + '**/*', cb);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('dist:move', function () {
|
gulp.task('dist:move', function () {
|
||||||
var moves = [
|
var moves = [
|
||||||
{
|
{
|
||||||
src: '../../CNAME',
|
src: './CNAME',
|
||||||
dest: paths.dist
|
dest: paths.dist
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -237,12 +323,35 @@ gulp.task('dist:move', function () {
|
|||||||
src: paths.npmDir + 'angular/angular.min.js',
|
src: paths.npmDir + 'angular/angular.min.js',
|
||||||
dest: paths.dist + 'lib/angular'
|
dest: paths.dist + 'lib/angular'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'node-forge/dist/prime.worker.*',
|
||||||
|
dest: paths.dist + 'lib/forge'
|
||||||
|
},
|
||||||
|
//{
|
||||||
|
// src: paths.npmDir + 'duo_web_sdk/index.js',
|
||||||
|
// dest: paths.dist + 'lib/duo'
|
||||||
|
//},
|
||||||
|
{
|
||||||
|
src: paths.jsDir + 'duo.js',
|
||||||
|
dest: paths.dist + 'js'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.jsDir + 'settings.js',
|
||||||
|
dest: paths.dist + 'js'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.jsDir + 'bw.min.js',
|
||||||
|
dest: paths.dist + 'js'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
src: [
|
src: [
|
||||||
paths.webroot + '**/app/**/*.html',
|
paths.webroot + '**/app/**/*.html',
|
||||||
paths.webroot + '**/images/**/*',
|
paths.webroot + '**/images/**/*',
|
||||||
paths.webroot + 'index.html',
|
paths.webroot + 'index.html',
|
||||||
paths.webroot + 'favicon.ico'
|
paths.webroot + 'u2f-connector.html',
|
||||||
|
paths.webroot + 'duo-connector.html',
|
||||||
|
paths.webroot + 'favicon.ico',
|
||||||
|
paths.webroot + 'app-id.json'
|
||||||
],
|
],
|
||||||
dest: paths.dist
|
dest: paths.dist
|
||||||
}
|
}
|
||||||
@@ -261,7 +370,7 @@ gulp.task('dist:css', function () {
|
|||||||
paths.cssDir + '**/*.css',
|
paths.cssDir + '**/*.css',
|
||||||
'!' + paths.cssDir + '**/*.min.css'
|
'!' + paths.cssDir + '**/*.min.css'
|
||||||
])
|
])
|
||||||
.pipe(preprocess({ context: settings }))
|
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||||
.pipe(cssmin())
|
.pipe(cssmin())
|
||||||
.pipe(rename({ suffix: '.min' }))
|
.pipe(rename({ suffix: '.min' }))
|
||||||
.pipe(gulp.dest(paths.dist + 'css'));
|
.pipe(gulp.dest(paths.dist + 'css'));
|
||||||
@@ -277,18 +386,41 @@ gulp.task('dist:js:app', function () {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
merge(mainStream, config())
|
merge(mainStream, config())
|
||||||
.pipe(preprocess({ context: settings }))
|
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||||
.pipe(concat(paths.dist + '/js/app.min.js'))
|
.pipe(concat(paths.dist + '/js/app.min.js'))
|
||||||
.pipe(ngAnnotate())
|
.pipe(ngAnnotate())
|
||||||
.pipe(uglify())
|
.pipe(uglify())
|
||||||
.pipe(gulp.dest('.'));
|
.pipe(gulp.dest('.'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('dist:js:fallback', function () {
|
||||||
|
var mainStream = gulp
|
||||||
|
.src([
|
||||||
|
paths.jsDir + 'fallback*.js'
|
||||||
|
]);
|
||||||
|
|
||||||
|
merge(mainStream)
|
||||||
|
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||||
|
.pipe(uglify())
|
||||||
|
.pipe(rename({ suffix: '.min' }))
|
||||||
|
.pipe(gulp.dest(paths.dist + 'js'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('dist:js:u2f', function () {
|
||||||
|
var mainStream = gulp
|
||||||
|
.src([
|
||||||
|
paths.jsDir + 'u2f*.js'
|
||||||
|
]);
|
||||||
|
|
||||||
|
merge(mainStream)
|
||||||
|
.pipe(concat(paths.dist + '/js/u2f.min.js'))
|
||||||
|
.pipe(uglify())
|
||||||
|
.pipe(gulp.dest('.'));
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('dist:js:lib', function () {
|
gulp.task('dist:js:lib', function () {
|
||||||
return gulp
|
return gulp
|
||||||
.src([
|
.src([
|
||||||
paths.libDir + 'sjcl/sjcl.js',
|
|
||||||
paths.libDir + 'sjcl/*.js',
|
|
||||||
paths.libDir + 'angulartics/angulartics.js',
|
paths.libDir + 'angulartics/angulartics.js',
|
||||||
paths.libDir + '**/*.js',
|
paths.libDir + '**/*.js',
|
||||||
'!' + paths.libDir + '**/*.min.js',
|
'!' + paths.libDir + '**/*.min.js',
|
||||||
@@ -306,19 +438,49 @@ gulp.task('dist:preprocess', function () {
|
|||||||
.src([
|
.src([
|
||||||
paths.dist + '/**/*.html'
|
paths.dist + '/**/*.html'
|
||||||
], { base: '.' })
|
], { base: '.' })
|
||||||
.pipe(preprocess({ context: settings }))
|
.pipe(preprocess({ context: { cacheTag: randomString, selfHosted: selfHosted } }))
|
||||||
.pipe(gulp.dest('.'));
|
.pipe(gulp.dest('.'));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('dist', ['build'], function (cb) {
|
gulp.task('dist', ['build'], function (cb) {
|
||||||
return runSequence(
|
return runSequence(
|
||||||
'dist:clean',
|
'dist:clean',
|
||||||
['dist:move', 'dist:css', 'dist:js:app', 'dist:js:lib'],
|
['dist:move', 'dist:css', 'dist:js:app', 'dist:js:lib', 'dist:js:fallback', 'dist:js:u2f'],
|
||||||
'dist:preprocess',
|
'dist:preprocess',
|
||||||
cb);
|
cb);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var selfHosted = false;
|
||||||
|
gulp.task('dist:selfHosted', function (cb) {
|
||||||
|
selfHosted = true;
|
||||||
|
return runSequence('dist', cb);
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('deploy', ['dist'], function () {
|
gulp.task('deploy', ['dist'], function () {
|
||||||
return gulp.src(paths.dist + '**/*')
|
return gulp.src(paths.dist + '**/*')
|
||||||
.pipe(ghPages({ cacheDir: paths.dist + '.publish' }));
|
.pipe(ghPages({ cacheDir: paths.dist + '.publish' }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('deploy-preview', ['dist'], function () {
|
||||||
|
return gulp.src(paths.dist + '**/*')
|
||||||
|
.pipe(ghPages({
|
||||||
|
cacheDir: paths.dist + '.publish',
|
||||||
|
remoteUrl: 'git@github.com:kspearrin/bitwarden-web-preview.git'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('serve', function () {
|
||||||
|
connect.server({
|
||||||
|
port: 4001,
|
||||||
|
root: ['src'],
|
||||||
|
//https: true,
|
||||||
|
middleware: function (connect, opt) {
|
||||||
|
return [function (req, res, next) {
|
||||||
|
if (req.originalUrl.indexOf('app-id.json') > -1) {
|
||||||
|
res.setHeader('Content-Type', 'application/fido.trusted-apps+json');
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
55
package.json
Normal file
55
package.json
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"name": "bitwarden",
|
||||||
|
"version": "1.18.0",
|
||||||
|
"env": "Production",
|
||||||
|
"devDependencies": {
|
||||||
|
"connect": "3.6.3",
|
||||||
|
"lodash": "4.17.4",
|
||||||
|
"gulp": "3.9.1",
|
||||||
|
"gulp-concat": "2.6.1",
|
||||||
|
"gulp-cssmin": "0.2.0",
|
||||||
|
"gulp-less": "3.3.2",
|
||||||
|
"gulp-rename": "1.2.2",
|
||||||
|
"gulp-uglify": "3.0.0",
|
||||||
|
"gulp-gh-pages": "0.5.4",
|
||||||
|
"gulp-preprocess": "2.0.0",
|
||||||
|
"gulp-ng-annotate": "2.0.0",
|
||||||
|
"gulp-ng-config": "1.4.0",
|
||||||
|
"gulp-connect": "5.0.0",
|
||||||
|
"jshint": "2.9.5",
|
||||||
|
"gulp-jshint": "2.0.4",
|
||||||
|
"rimraf": "2.6.1",
|
||||||
|
"run-sequence": "2.1.0",
|
||||||
|
"merge-stream": "1.0.1",
|
||||||
|
"jquery": "2.2.4",
|
||||||
|
"font-awesome": "4.7.0",
|
||||||
|
"bootstrap": "3.3.7",
|
||||||
|
"angular": "1.6.6",
|
||||||
|
"angular-resource": "1.6.6",
|
||||||
|
"angular-sanitize": "1.6.6",
|
||||||
|
"angular-ui-bootstrap": "2.5.0",
|
||||||
|
"angular-ui-router": "0.4.2",
|
||||||
|
"angular-jwt": "0.1.9",
|
||||||
|
"angular-cookies": "1.6.6",
|
||||||
|
"admin-lte": "2.3.11",
|
||||||
|
"angular-toastr": "2.1.1",
|
||||||
|
"angular-bootstrap-show-errors": "2.3.0",
|
||||||
|
"angular-messages": "1.6.6",
|
||||||
|
"ngstorage": "0.3.11",
|
||||||
|
"papaparse": "4.3.5",
|
||||||
|
"clipboard": "1.7.1",
|
||||||
|
"ngclipboard": "1.1.1",
|
||||||
|
"angulartics": "1.4.0",
|
||||||
|
"angulartics-google-analytics": "0.4.0",
|
||||||
|
"node-forge": "0.7.1",
|
||||||
|
"webpack-stream": "4.0.0",
|
||||||
|
"angular-stripe": "5.0.0",
|
||||||
|
"angular-credit-cards": "3.1.6",
|
||||||
|
"browserify": "14.4.0",
|
||||||
|
"vinyl-source-stream": "1.1.0",
|
||||||
|
"gulp-derequire": "2.1.0",
|
||||||
|
"exposify": "0.5.0",
|
||||||
|
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
|
||||||
|
"angular-promise-polyfill": "0.0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
11
settings.Preview.json
Normal file
11
settings.Preview.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"appSettings": {
|
||||||
|
"apiUri": "https://preview-api.bitwarden.com",
|
||||||
|
"identityUri": "https://preview-identity.bitwarden.com",
|
||||||
|
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
||||||
|
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
||||||
|
"whitelistDomains": [
|
||||||
|
"preview-api.bitwarden.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
11
settings.Production.json
Normal file
11
settings.Production.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"appSettings": {
|
||||||
|
"apiUri": "https://api.bitwarden.com",
|
||||||
|
"identityUri": "https://identity.bitwarden.com",
|
||||||
|
"stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk",
|
||||||
|
"braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd",
|
||||||
|
"whitelistDomains": [
|
||||||
|
"api.bitwarden.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
11
settings.json
Normal file
11
settings.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"appSettings": {
|
||||||
|
"apiUri": "http://localhost:4000",
|
||||||
|
"identityUri": "http://localhost:33656",
|
||||||
|
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
||||||
|
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
||||||
|
"whitelistDomains": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,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.0.1",
|
|
||||||
"environment": "Development",
|
|
||||||
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
|
|
||||||
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
|
|
||||||
"Microsoft.AspNetCore.StaticFiles": "1.0.0"
|
|
||||||
},
|
|
||||||
|
|
||||||
"tools": {
|
|
||||||
"Microsoft.AspNetCore.Server.IISIntegration.Tools": {
|
|
||||||
"version": "1.0.0-preview2-final",
|
|
||||||
"imports": "portable-net45+win8+dnxcore50"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"frameworks": {
|
|
||||||
"netcoreapp1.0": {
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.NETCore.App": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"type": "platform"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"buildOptions": {
|
|
||||||
"emitEntryPoint": true,
|
|
||||||
"preserveCompilationContext": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"runtimeOptions": {
|
|
||||||
"gcServer": false,
|
|
||||||
"gcConcurrent": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"publishOptions": {
|
|
||||||
"include": [
|
|
||||||
"wwwroot",
|
|
||||||
"Views",
|
|
||||||
"settings.json",
|
|
||||||
"settings.Development.json",
|
|
||||||
"settings.Production.json",
|
|
||||||
"settings.Staging.json",
|
|
||||||
"web.config"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
"scripts": {
|
|
||||||
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
|
|
||||||
},
|
|
||||||
|
|
||||||
"userSecretsId": "aspnet-Vault-20160519103145"
|
|
||||||
}
|
|
||||||
@@ -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,70 +0,0 @@
|
|||||||
/// <autosync enabled="true" />
|
|
||||||
/// <reference path="../gulpfile.js" />
|
|
||||||
/// <reference path="app/accounts/accountsLoginController.js" />
|
|
||||||
/// <reference path="app/accounts/accountsLogoutController.js" />
|
|
||||||
/// <reference path="app/accounts/accountsmodule.js" />
|
|
||||||
/// <reference path="app/accounts/accountspasswordhintcontroller.js" />
|
|
||||||
/// <reference path="app/accounts/accountsRegisterController.js" />
|
|
||||||
/// <reference path="app/apiInterceptor.js" />
|
|
||||||
/// <reference path="app/app.js" />
|
|
||||||
/// <reference path="app/config.js" />
|
|
||||||
/// <reference path="app/directives/apiFieldDirective.js" />
|
|
||||||
/// <reference path="app/directives/apiFormDirective.js" />
|
|
||||||
/// <reference path="app/directives/directivesModule.js" />
|
|
||||||
/// <reference path="app/directives/masterPasswordDirective.js" />
|
|
||||||
/// <reference path="app/directives/pageTitleDirective.js" />
|
|
||||||
/// <reference path="app/directives/passwordmeterdirective.js" />
|
|
||||||
/// <reference path="app/directives/passwordviewerdirective.js" />
|
|
||||||
/// <reference path="app/global/globalModule.js" />
|
|
||||||
/// <reference path="app/global/mainController.js" />
|
|
||||||
/// <reference path="app/global/sideNavController.js" />
|
|
||||||
/// <reference path="app/global/topNavController.js" />
|
|
||||||
/// <reference path="app/services/apiService.js" />
|
|
||||||
/// <reference path="app/services/authService.js" />
|
|
||||||
/// <reference path="app/services/cipherService.js" />
|
|
||||||
/// <reference path="app/services/cryptoService.js" />
|
|
||||||
/// <reference path="app/services/importservice.js" />
|
|
||||||
/// <reference path="app/services/passwordservice.js" />
|
|
||||||
/// <reference path="app/services/servicesModule.js" />
|
|
||||||
/// <reference path="app/services/tokenService.js" />
|
|
||||||
/// <reference path="app/services/validationservice.js" />
|
|
||||||
/// <reference path="app/settings.js" />
|
|
||||||
/// <reference path="app/settings/settingsChangeEmailController.js" />
|
|
||||||
/// <reference path="app/settings/settingsChangePasswordController.js" />
|
|
||||||
/// <reference path="app/settings/settingsController.js" />
|
|
||||||
/// <reference path="app/settings/settingsdeletecontroller.js" />
|
|
||||||
/// <reference path="app/settings/settingsmodule.js" />
|
|
||||||
/// <reference path="app/settings/settingsSessionsController.js" />
|
|
||||||
/// <reference path="app/settings/settingsTwoFactorController.js" />
|
|
||||||
/// <reference path="app/tools/toolsAuditsController.js" />
|
|
||||||
/// <reference path="app/tools/toolsController.js" />
|
|
||||||
/// <reference path="app/tools/toolsExportController.js" />
|
|
||||||
/// <reference path="app/tools/toolsImportController.js" />
|
|
||||||
/// <reference path="app/tools/toolsmodule.js" />
|
|
||||||
/// <reference path="app/vault/vaultAddFolderController.js" />
|
|
||||||
/// <reference path="app/vault/vaultAddSiteController.js" />
|
|
||||||
/// <reference path="app/vault/vaultController.js" />
|
|
||||||
/// <reference path="app/vault/vaultEditFolderController.js" />
|
|
||||||
/// <reference path="app/vault/vaultEditSiteController.js" />
|
|
||||||
/// <reference path="app/vault/vaultmodule.js" />
|
|
||||||
/// <reference path="lib/admin-lte/js/app.js" />
|
|
||||||
/// <reference path="lib/angular/angular.js" />
|
|
||||||
/// <reference path="lib/angular-bootstrap/angular-bootstrap-tpls.js" />
|
|
||||||
/// <reference path="lib/angular-bootstrap-show-errors/showErrors.js" />
|
|
||||||
/// <reference path="lib/angular-cookies/angular-cookies.js" />
|
|
||||||
/// <reference path="lib/angular-jwt/angular-jwt.js" />
|
|
||||||
/// <reference path="lib/angular-md5/angular-md5.js" />
|
|
||||||
/// <reference path="lib/angular-messages/angular-messages.js" />
|
|
||||||
/// <reference path="lib/angular-resource/angular-resource.js" />
|
|
||||||
/// <reference path="lib/angular-toastr/angular-toastr.js" />
|
|
||||||
/// <reference path="lib/angular-toastr/angular-toastr.tpls.js" />
|
|
||||||
/// <reference path="lib/angular-ui-router/angular-ui-router.js" />
|
|
||||||
/// <reference path="lib/bootstrap/js/bootstrap.min.js" />
|
|
||||||
/// <reference path="lib/clipboard/clipboard.js" />
|
|
||||||
/// <reference path="lib/jquery/jquery.js" />
|
|
||||||
/// <reference path="lib/ngclipboard/ngclipboard.js" />
|
|
||||||
/// <reference path="lib/ngstorage/ngStorage.js" />
|
|
||||||
/// <reference path="lib/papaparse/papaparse.js" />
|
|
||||||
/// <reference path="lib/sjcl/bitArray.js" />
|
|
||||||
/// <reference path="lib/sjcl/cbc.js" />
|
|
||||||
/// <reference path="lib/sjcl/sjcl.js" />
|
|
||||||
@@ -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,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,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,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,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,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: cryptoService.decrypt(encryptedSite.Uri),
|
|
||||||
username: encryptedSite.Username && encryptedSite.Username !== '' ? cryptoService.decrypt(encryptedSite.Username) : null,
|
|
||||||
password: cryptoService.decrypt(encryptedSite.Password),
|
|
||||||
notes: encryptedSite.Notes && encryptedSite.Notes !== '' ? cryptoService.decrypt(encryptedSite.Notes) : null
|
|
||||||
};
|
|
||||||
|
|
||||||
if (encryptedSite.Folder) {
|
|
||||||
site.folder = {
|
|
||||||
name: cryptoService.decrypt(encryptedSite.Folder.Name)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return site;
|
|
||||||
};
|
|
||||||
|
|
||||||
_service.decryptFolders = function (encryptedFolders) {
|
|
||||||
if (!encryptedFolders) throw "encryptedFolders is undefined or null";
|
|
||||||
|
|
||||||
var unencryptedFolders = [];
|
|
||||||
for (var i = 0; i < encryptedFolders.length; i++) {
|
|
||||||
unencryptedFolders.push(_service.decryptFolder(encryptedFolders[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return unencryptedFolders;
|
|
||||||
};
|
|
||||||
|
|
||||||
_service.decryptFolder = function (encryptedFolder) {
|
|
||||||
if (!encryptedFolder) throw "encryptedFolder is undefined or null";
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: encryptedFolder.Id,
|
|
||||||
'type': 0,
|
|
||||||
name: cryptoService.decrypt(encryptedFolder.Name)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
_service.encryptSites = function (unencryptedSites, key) {
|
|
||||||
if (!unencryptedSites) throw "unencryptedSites is undefined or null";
|
|
||||||
|
|
||||||
var encryptedSites = [];
|
|
||||||
for (var i = 0; i < unencryptedSites.length; i++) {
|
|
||||||
encryptedSites.push(_service.encryptSite(unencryptedSites[i], key));
|
|
||||||
}
|
|
||||||
|
|
||||||
return encryptedSites;
|
|
||||||
};
|
|
||||||
|
|
||||||
_service.encryptSite = function (unencryptedSite, key) {
|
|
||||||
if (!unencryptedSite) throw "unencryptedSite is undefined or null";
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: unencryptedSite.id,
|
|
||||||
'type': 1,
|
|
||||||
folderId: unencryptedSite.folderId === '' ? null : unencryptedSite.folderId,
|
|
||||||
favorite: unencryptedSite.favorite !== null ? unencryptedSite.favorite : false,
|
|
||||||
uri: cryptoService.encrypt(unencryptedSite.uri, key),
|
|
||||||
name: cryptoService.encrypt(unencryptedSite.name, key),
|
|
||||||
username: !unencryptedSite.username || unencryptedSite.username === '' ? null : cryptoService.encrypt(unencryptedSite.username, key),
|
|
||||||
password: cryptoService.encrypt(unencryptedSite.password, key),
|
|
||||||
notes: !unencryptedSite.notes || unencryptedSite.notes === '' ? null : cryptoService.encrypt(unencryptedSite.notes, key)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
_service.encryptFolders = function (unencryptedFolders, key) {
|
|
||||||
if (!unencryptedFolders) throw "unencryptedFolders is undefined or null";
|
|
||||||
|
|
||||||
var encryptedFolders = [];
|
|
||||||
for (var i = 0; i < unencryptedFolders.length; i++) {
|
|
||||||
encryptedFolders.push(_service.encryptFolder(unencryptedFolders[i], key));
|
|
||||||
}
|
|
||||||
|
|
||||||
return encryptedFolders;
|
|
||||||
};
|
|
||||||
|
|
||||||
_service.encryptFolder = function (unencryptedFolder, key) {
|
|
||||||
if (!unencryptedFolder) throw "unencryptedFolder is undefined or null";
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: unencryptedFolder.id,
|
|
||||||
'type': 0,
|
|
||||||
name: cryptoService.encrypt(unencryptedFolder.name, key)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return _service;
|
|
||||||
});
|
|
||||||
@@ -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,136 +0,0 @@
|
|||||||
angular
|
|
||||||
.module('bit.services')
|
|
||||||
|
|
||||||
.factory('importService', function () {
|
|
||||||
var _service = {};
|
|
||||||
|
|
||||||
_service.import = function (source, file, success, error) {
|
|
||||||
switch (source) {
|
|
||||||
case 'local':
|
|
||||||
importLocal(file, success, error);
|
|
||||||
break;
|
|
||||||
case 'lastpass':
|
|
||||||
importLastPass(file, success, error);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
error();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function importLocal(file, success, error) {
|
|
||||||
Papa.parse(file, {
|
|
||||||
header: true,
|
|
||||||
complete: function (results) {
|
|
||||||
var folders = [],
|
|
||||||
sites = [],
|
|
||||||
folderRelationships = [];
|
|
||||||
|
|
||||||
angular.forEach(results.data, function (value, key) {
|
|
||||||
if (!value.uri || value.uri === '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var folderIndex = folders.length,
|
|
||||||
siteIndex = sites.length,
|
|
||||||
hasFolder = value.folder && value.folder !== '',
|
|
||||||
addFolder = hasFolder;
|
|
||||||
|
|
||||||
if (hasFolder) {
|
|
||||||
for (var i = 0; i < folders.length; i++) {
|
|
||||||
if (folders[i].name == value.folder) {
|
|
||||||
addFolder = false;
|
|
||||||
folderIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sites.push({
|
|
||||||
favorite: value.favorite !== null ? value.favorite : false,
|
|
||||||
uri: value.uri,
|
|
||||||
username: value.username && value.username !== '' ? value.username : null,
|
|
||||||
password: value.password,
|
|
||||||
notes: value.notes && value.notes !== '' ? value.notes : null,
|
|
||||||
name: value.name
|
|
||||||
});
|
|
||||||
|
|
||||||
if (addFolder) {
|
|
||||||
folders.push({
|
|
||||||
name: value.folder
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasFolder) {
|
|
||||||
var relationship = {
|
|
||||||
key: siteIndex,
|
|
||||||
value: folderIndex
|
|
||||||
};
|
|
||||||
folderRelationships.push(relationship);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
success(folders, sites, folderRelationships);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function importLastPass(file, success, error) {
|
|
||||||
Papa.parse(file, {
|
|
||||||
header: true,
|
|
||||||
complete: function (results) {
|
|
||||||
var folders = [],
|
|
||||||
sites = [],
|
|
||||||
siteRelationships = [];
|
|
||||||
|
|
||||||
angular.forEach(results.data, function (value, key) {
|
|
||||||
if (!value.url || value.url === '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var folderIndex = folders.length,
|
|
||||||
siteIndex = sites.length,
|
|
||||||
hasFolder = value.grouping && value.grouping !== '' && value.grouping != '(none)',
|
|
||||||
addFolder = hasFolder;
|
|
||||||
|
|
||||||
if (hasFolder) {
|
|
||||||
for (var i = 0; i < folders.length; i++) {
|
|
||||||
if (folders[i].name == value.grouping) {
|
|
||||||
addFolder = false;
|
|
||||||
folderIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sites.push({
|
|
||||||
favorite: value.fav == '1',
|
|
||||||
uri: value.url,
|
|
||||||
username: value.username && value.username !== '' ? value.username : null,
|
|
||||||
password: value.password,
|
|
||||||
notes: value.extra && value.extra !== '' ? value.extra : null,
|
|
||||||
name: value.name
|
|
||||||
});
|
|
||||||
|
|
||||||
if (addFolder) {
|
|
||||||
folders.push({
|
|
||||||
name: value.grouping
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasFolder) {
|
|
||||||
var relationship = {
|
|
||||||
key: siteIndex,
|
|
||||||
value: folderIndex
|
|
||||||
};
|
|
||||||
siteRelationships.push(relationship);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
success(folders, sites, siteRelationships);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return _service;
|
|
||||||
});
|
|
||||||
@@ -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,2 +0,0 @@
|
|||||||
angular.module("bit")
|
|
||||||
.constant("appSettings", {"rememberedEmailCookieName":"bit.rememberedEmail","version":"1.0.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,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,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,36 +0,0 @@
|
|||||||
angular
|
|
||||||
.module('bit.tools')
|
|
||||||
|
|
||||||
.controller('toolsImportController', function ($scope, $state, apiService, $uibModalInstance, cryptoService, cipherService, toastr, importService, $analytics) {
|
|
||||||
$analytics.eventTrack('toolsImportController', { category: 'Modal' });
|
|
||||||
$scope.model = { source: 'local' };
|
|
||||||
|
|
||||||
$scope.import = function (model) {
|
|
||||||
$scope.processing = true;
|
|
||||||
var file = document.getElementById('file').files[0];
|
|
||||||
importService.import(model.source, file, importSuccess, importError);
|
|
||||||
};
|
|
||||||
|
|
||||||
function importSuccess(folders, sites, folderRelationships) {
|
|
||||||
apiService.ciphers.import({
|
|
||||||
folders: cipherService.encryptFolders(folders, cryptoService.getKey()),
|
|
||||||
sites: cipherService.encryptSites(sites, cryptoService.getKey()),
|
|
||||||
folderRelationships: folderRelationships
|
|
||||||
}, function () {
|
|
||||||
$uibModalInstance.dismiss('cancel');
|
|
||||||
$state.go('backend.vault').then(function () {
|
|
||||||
$analytics.eventTrack('Imported Data', { label: model.source });
|
|
||||||
toastr.success('Data has been successfully imported into your vault.', 'Import Success');
|
|
||||||
});
|
|
||||||
}, importError);
|
|
||||||
}
|
|
||||||
|
|
||||||
function importError() {
|
|
||||||
$uibModalInstance.dismiss('cancel');
|
|
||||||
toastr.error('Something went wrong. Try again.', 'Oh No!');
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.close = function () {
|
|
||||||
$uibModalInstance.dismiss('cancel');
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -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,29 +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</option>
|
|
||||||
<option value="lastpass">LastPass</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="file">File</label>
|
|
||||||
<input type="file" id="file" name="file" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="submit" class="btn btn-primary btn-flat">
|
|
||||||
Import
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div ng-show="processing" class="modal-body text-center">
|
|
||||||
<p><i class="fa fa-cog fa-spin fa-3x"></i></p>
|
|
||||||
<p>Please wait. We are now importing all of your data. Do not close this window. You will be redirected to your vault when the import has completed.</p>
|
|
||||||
</div>
|
|
||||||
@@ -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,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,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>
|
|
||||||
@@ -1,98 +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="addSiteModelLabel"><i class="fa fa-globe"></i> Add New Site</h4>
|
|
||||||
</div>
|
|
||||||
<form name="addSiteForm" ng-submit="addSiteForm.$valid && save(site)" api-form="savePromise">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="callout callout-danger validation-errors" ng-show="addSiteForm.$errors">
|
|
||||||
<h4>Errors have occured</h4>
|
|
||||||
<ul>
|
|
||||||
<li ng-repeat="e in addSiteForm.$errors">{{e}}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<label for="uri">URI</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" id="uri" ng-model="site.uri" name="Uri" class="form-control" placeholder="http://..." api-field />
|
|
||||||
<span class="input-group-btn" uib-tooltip="Copy URI" tooltip-placement="left">
|
|
||||||
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
|
|
||||||
ngclipboard-error="clipboardError(e)"
|
|
||||||
data-clipboard-target="#uri">
|
|
||||||
<i class="fa fa-clipboard"></i>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<label for="name">Name</label>
|
|
||||||
<input type="text" id="name" name="Name" ng-model="site.name" class="form-control" required api-field />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<label for="folder">Folder</label>
|
|
||||||
<select id="folder" name="FolderId" ng-model="site.folderId" class="form-control" api-field>
|
|
||||||
<option ng-repeat="folder in folders" value="{{folder.id}}">{{folder.name}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<label for="username">Username</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" id="username" name="Username" ng-model="site.username" class="form-control" api-field />
|
|
||||||
<span class="input-group-btn" uib-tooltip="Copy Username" tooltip-placement="left">
|
|
||||||
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
|
|
||||||
ngclipboard-error="clipboardError(e)"
|
|
||||||
data-clipboard-target="#username">
|
|
||||||
<i class="fa fa-clipboard"></i>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<div class="pull-right password-options">
|
|
||||||
<i class="fa fa-lg fa-refresh" uib-tooltip="Generate Password" tooltip-placement="left" ng-click="generatePassword()"></i>
|
|
||||||
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Password" tooltip-placement="left" password-viewer="#password"></i>
|
|
||||||
</div>
|
|
||||||
<label for="password">Password</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input tabindex="-1" type="text" id="password-text" value="{{site.password}}" style="margin-left: -9999px;" />
|
|
||||||
<input type="password" id="password" name="Password" ng-model="site.password" class="form-control" required api-field />
|
|
||||||
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
|
||||||
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
|
|
||||||
ngclipboard-success="clipboardSuccess(e)"
|
|
||||||
ngclipboard-error="clipboardError(e, true)"
|
|
||||||
data-clipboard-target="#password-text">
|
|
||||||
<i class="fa fa-clipboard"></i>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="margin: -10px 0 15px 0;" password-meter="site.password" password-meter-username="site.username" outer-class="xs"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<label for="notes">Notes</label>
|
|
||||||
<textarea id="notes" name="Notes" class="form-control" ng-model="site.notes" api-field></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" ng-model="site.favorite" name="Favorite" />
|
|
||||||
Favorite
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="addSiteForm.$loading">
|
|
||||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="addSiteForm.$loading"></i>Submit
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
@@ -1,101 +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="editSiteModelLabel"><i class="fa fa-globe"></i> Site Information <small>{{site.name}}</small></h4>
|
|
||||||
</div>
|
|
||||||
<form name="editSiteForm" ng-submit="editSiteForm.$valid && save(site)" api-form="savePromise">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="callout callout-danger validation-errors" ng-show="editSiteForm.$errors">
|
|
||||||
<h4>Errors have occured</h4>
|
|
||||||
<ul>
|
|
||||||
<li ng-repeat="e in editSiteForm.$errors">{{e}}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<label for="uri">URI</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" id="uri" name="Uri" ng-model="site.uri" class="form-control" placeholder="http://..." api-field />
|
|
||||||
<span class="input-group-btn">
|
|
||||||
<button tabindex="-1" class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI" tooltip-placement="left" ngclipboard
|
|
||||||
ngclipboard-error="clipboardError(e)"
|
|
||||||
data-clipboard-target="#uri">
|
|
||||||
<i class="fa fa-clipboard"></i>
|
|
||||||
</button>
|
|
||||||
<a href="{{site.uri}}" target="_blank" class="btn btn-default btn-flat" uib-tooltip="Go To Site" tooltip-placement="left">
|
|
||||||
<i class="fa fa-share"></i>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<label for="name">Name</label>
|
|
||||||
<input type="text" id="name" name="Name" ng-model="site.name" class="form-control" required api-field />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<label for="folder">Folder</label>
|
|
||||||
<select id="folder" name="FolderId" ng-model="site.folderId" class="form-control" api-field>
|
|
||||||
<option ng-repeat="folder in folders" value="{{folder.id}}">{{folder.name}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<label for="username">Username</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" id="username" name="Username" ng-model="site.username" class="form-control" api-field />
|
|
||||||
<span class="input-group-btn" uib-tooltip="Copy Username" tooltip-placement="left">
|
|
||||||
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
|
|
||||||
ngclipboard-error="clipboardError(e)"
|
|
||||||
data-clipboard-target="#username">
|
|
||||||
<i class="fa fa-clipboard"></i>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<div class="pull-right password-options">
|
|
||||||
<i class="fa fa-lg fa-refresh" uib-tooltip="Generate Password" tooltip-placement="left" ng-click="generatePassword()"></i>
|
|
||||||
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Password" tooltip-placement="left" password-viewer="#password"></i>
|
|
||||||
</div>
|
|
||||||
<label for="password">Password</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" id="password-text" value="{{site.password}}" style="margin-left: -9999px;" />
|
|
||||||
<input type="password" id="password" name="Password" ng-model="site.password" class="form-control" required api-field />
|
|
||||||
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
|
||||||
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
|
|
||||||
ngclipboard-success="clipboardSuccess(e)"
|
|
||||||
ngclipboard-error="clipboardError(e, true)"
|
|
||||||
data-clipboard-target="#password-text">
|
|
||||||
<i class="fa fa-clipboard"></i>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="margin: -10px 0 15px 0;" password-meter="site.password" password-meter-username="site.username" outer-class="xs"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group" show-errors>
|
|
||||||
<label for="notes">Notes</label>
|
|
||||||
<textarea id="notes" name="Notes" class="form-control" ng-model="site.notes" api-field></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" ng-model="site.favorite" name="Favorite" />
|
|
||||||
Favorite
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="editSiteForm.$loading">
|
|
||||||
<i class="fa fa-refresh fa-spin loading-icon" ng-show="editSiteForm.$loading"></i>Save
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
<div class="wrapper toast-target">
|
|
||||||
<header class="main-header" ng-controller="topNavController as topNav">
|
|
||||||
<a ui-sref="backend.vault" class="logo">
|
|
||||||
<span class="logo-mini"><i class="fa fa-shield"></i></span>
|
|
||||||
<span class="logo-lg"><i class="fa fa-shield"></i> <b>bit</b>warden</span>
|
|
||||||
</a>
|
|
||||||
<nav class="navbar navbar-static-top" role="navigation">
|
|
||||||
<a class="sidebar-toggle" data-toggle="offcanvas" role="button">
|
|
||||||
<span class="sr-only">Toggle navigation</span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
</a>
|
|
||||||
<div class="navbar-custom-menu">
|
|
||||||
<ul class="nav navbar-nav">
|
|
||||||
<li><a ui-sref="frontend.logout">Log Out</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<aside class="main-sidebar" ng-controller="sideNavController as sideNav">
|
|
||||||
<section class="sidebar">
|
|
||||||
<div class="user-panel">
|
|
||||||
<div class="pull-left image">
|
|
||||||
<img src="//www.gravatar.com/avatar/{{ main.userProfile.email | gravatar }}.jpg?s=45&d=mm"
|
|
||||||
class="img-circle" alt="User Image">
|
|
||||||
</div>
|
|
||||||
<div class="pull-left info">
|
|
||||||
<p>{{main.userProfile.extended && main.userProfile.extended.name ? main.userProfile.extended.name : main.userProfile.email}}</p>
|
|
||||||
<a ui-sref="frontend.logout">Log Out</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form class="sidebar-form">
|
|
||||||
<label for="search" class="sr-only">Search</label>
|
|
||||||
<div class="form-group has-feedback">
|
|
||||||
<input type="text" id="search" class="form-control" placeholder="Search vault..."
|
|
||||||
ng-focus="searchVault()" ng-model="main.searchVaultText" />
|
|
||||||
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<ul class="sidebar-menu">
|
|
||||||
<li class="header">WEB VAULT</li>
|
|
||||||
<li class="treeview" ng-class="{active: $state.includes('backend.vault')}">
|
|
||||||
<a ui-sref="backend.vault"><i class="fa fa-lock"></i> <span>My Vault</span></a>
|
|
||||||
<ul class="treeview-menu menu-open">
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0)" ng-click="addSite()">
|
|
||||||
<i class="fa fa-plus"></i> New Site
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0)" ng-click="addFolder()">
|
|
||||||
<i class="fa fa-folder"></i> New Folder
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li class="treeview" ng-class="{active: $state.includes('backend.settings')}">
|
|
||||||
<a ui-sref="backend.settings"><i class="fa fa-cogs"></i> <span>Settings</span></a>
|
|
||||||
<ul class="treeview-menu">
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0)" ng-click="changePassword()">
|
|
||||||
<i class="fa fa-circle-o"></i> Change Password
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0)" ng-click="changeEmail()">
|
|
||||||
<i class="fa fa-circle-o"></i> Change Email
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0)" ng-click="sessions()">
|
|
||||||
<i class="fa fa-circle-o"></i> Deauthorize Sessions
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0)" ng-click="twoFactor()">
|
|
||||||
<i class="fa fa-circle-o"></i> Two-step Login
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0)" ng-click="delete()">
|
|
||||||
<i class="fa fa-circle-o"></i> Delete Account
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li class="treeview" ng-class="{active: $state.includes('backend.tools')}">
|
|
||||||
<a ui-sref="backend.tools"><i class="fa fa-wrench"></i> <span>Tools</span></a>
|
|
||||||
<ul class="treeview-menu">
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0)" ng-click="import()">
|
|
||||||
<i class="fa fa-circle-o"></i> Import
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0)" ng-click="export()">
|
|
||||||
<i class="fa fa-circle-o"></i> Export
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0)" ng-click="audits()">
|
|
||||||
<i class="fa fa-circle-o"></i> Audits
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://bitwarden.com/contact/" target="_blank"
|
|
||||||
analytics-on="click" analytics-event="Clicked Get Help">
|
|
||||||
<i class="fa fa-info-circle"></i> <span>Get Help</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="header">
|
|
||||||
MOBILE APPS<small class="label pull-right bg-green">FREE</small>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8"
|
|
||||||
target="_blank" analytics-on="click" analytics-event="Clicked iOS">
|
|
||||||
<i class="fa fa-apple"></i> <span>iOS</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden"
|
|
||||||
target="_blank" analytics-on="click" analytics-event="Clicked Android">
|
|
||||||
<i class="fa fa-android"></i> <span>Android</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="header">
|
|
||||||
BROWSER EXTENSIONS <small class="label pull-right bg-green">FREE</small>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb"
|
|
||||||
target="_blank" analytics-on="click" analytics-event="Clicked Chrome">
|
|
||||||
<i class="fa fa-chrome"></i> <span>Chrome</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0)"
|
|
||||||
target="_blank" analytics-on="click" analytics-event="Clicked Firefox">
|
|
||||||
<i class="fa fa-firefox"></i> <span>Firefox</span>
|
|
||||||
<small class="label pull-right bg-gray">coming very soon</small>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0)"
|
|
||||||
target="_blank" analytics-on="click" analytics-event="Clicked Opera">
|
|
||||||
<i class="fa fa-opera"></i> <span>Opera</span>
|
|
||||||
<small class="label pull-right bg-gray">coming very soon</small>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:void(0)"
|
|
||||||
target="_blank" analytics-on="click" analytics-event="Clicked Edge">
|
|
||||||
<i class="fa fa-edge"></i> <span>Edge</span>
|
|
||||||
<small class="label pull-right bg-gray">coming soon</small>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<div class="content-wrapper" ui-view>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="main-footer">
|
|
||||||
<div class="pull-right hidden-xs">
|
|
||||||
<b>Version</b> {{main.version}}
|
|
||||||
</div>
|
|
||||||
<strong>Copyright © <span ng-bind="currentYear"></span></strong>, bitwarden.com
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html ng-app="bit">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
|
||||||
<base href="/" />
|
|
||||||
|
|
||||||
<title page-title>bitwarden.com Password Manager</title>
|
|
||||||
|
|
||||||
<!-- @if true !>
|
|
||||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
|
|
||||||
<meta name="x-stylesheet-test-bs" content="" class="invisible" />
|
|
||||||
<script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName('SCRIPT'),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}('visibility','hidden',['lib\/bootstrap\/css\/bootstrap.min.css']);</script>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" />
|
|
||||||
<meta name="x-stylesheet-test-fa" content="" class="fa" />
|
|
||||||
<script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName('SCRIPT'),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}('font-family','FontAwesome',['lib\/font-awesome\/css\/font-awesome.min.css']);</script>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="css/vault.min.css" />
|
|
||||||
<!-- @endif -->
|
|
||||||
<!-- @exclude -->
|
|
||||||
<link rel="stylesheet" href="lib/bootstrap/css/bootstrap.css" />
|
|
||||||
<link rel="stylesheet" href="lib/font-awesome/css/font-awesome.css" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="css/vault.css" />
|
|
||||||
<!-- @endexclude -->
|
|
||||||
</head>
|
|
||||||
<body ng-controller="mainController as main" class="layout-boxed skin-blue sidebar-mini {{main.bodyClass}}">
|
|
||||||
<div ui-view></div>
|
|
||||||
|
|
||||||
<!-- @if true !>
|
|
||||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
|
|
||||||
<script>(window.jQuery||document.write('<script src="lib\/jquery\/jquery.min.js"><\/script>'));</script>
|
|
||||||
|
|
||||||
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
|
|
||||||
<script>((window.jQuery&&window.jQuery.fn&&window.jQuery.fn.modal)||document.write('<script src="lib\/bootstrap\/js\/bootstrap.min.js"><\/script>'));</script>
|
|
||||||
|
|
||||||
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
|
|
||||||
<script>(window.angular||document.write('<script src="lib\/angular\/angular.min.js"><\/script>'));</script>
|
|
||||||
|
|
||||||
<script src="js/lib.min.js"></script>
|
|
||||||
<script src="js/app.min.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
||||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
||||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
||||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
|
||||||
|
|
||||||
ga('create', 'UA-81915606-3', 'auto');
|
|
||||||
</script>
|
|
||||||
<!-- @endif -->
|
|
||||||
<!-- @exclude -->
|
|
||||||
<script src="lib/jquery/jquery.js"></script>
|
|
||||||
<script src="lib/bootstrap/js/bootstrap.js"></script>
|
|
||||||
<script src="lib/admin-lte/js/app.js"></script>
|
|
||||||
|
|
||||||
<script src="lib/sjcl/sjcl.js"></script>
|
|
||||||
<script src="lib/sjcl/cbc.js"></script>
|
|
||||||
<script src="lib/sjcl/bitArray.js"></script>
|
|
||||||
|
|
||||||
<script src="lib/papaparse/papaparse.js"></script>
|
|
||||||
<script src="lib/clipboard/clipboard.js"></script>
|
|
||||||
|
|
||||||
<script src="lib/angular/angular.js"></script>
|
|
||||||
<script src="lib/angular-cookies/angular-cookies.js"></script>
|
|
||||||
<script src="lib/angular-messages/angular-messages.js"></script>
|
|
||||||
<script src="lib/angular-jwt/angular-jwt.js"></script>
|
|
||||||
<script src="lib/angular-resource/angular-resource.js"></script>
|
|
||||||
<script src="lib/angular-ui-router/angular-ui-router.js"></script>
|
|
||||||
<script src="lib/angular-bootstrap/angular-bootstrap-tpls.js"></script>
|
|
||||||
<script src="lib/angular-md5/angular-md5.js"></script>
|
|
||||||
<script src="lib/angular-toastr/angular-toastr.tpls.js"></script>
|
|
||||||
<script src="lib/angular-bootstrap-show-errors/showErrors.js"></script>
|
|
||||||
<script src="lib/ngstorage/ngStorage.js"></script>
|
|
||||||
<script src="lib/ngclipboard/ngclipboard.js"></script>
|
|
||||||
<script src="lib/angulartics/angulartics.js"></script>
|
|
||||||
<script src="lib/angulartics/angulartics-ga.js"></script>
|
|
||||||
|
|
||||||
<script src="app/app.js"></script>
|
|
||||||
<script src="app/settings.js"></script>
|
|
||||||
<script src="app/config.js"></script>
|
|
||||||
<script src="app/apiInterceptor.js"></script>
|
|
||||||
|
|
||||||
<script src="app/directives/directivesModule.js"></script>
|
|
||||||
<script src="app/directives/pageTitleDirective.js"></script>
|
|
||||||
<script src="app/directives/apiFormDirective.js"></script>
|
|
||||||
<script src="app/directives/apiFieldDirective.js"></script>
|
|
||||||
<script src="app/directives/masterPasswordDirective.js"></script>
|
|
||||||
<script src="app/directives/passwordMeterDirective.js"></script>
|
|
||||||
<script src="app/directives/passwordViewerDirective.js"></script>
|
|
||||||
|
|
||||||
<script src="app/services/servicesModule.js"></script>
|
|
||||||
<script src="app/services/tokenService.js"></script>
|
|
||||||
<script src="app/services/apiService.js"></script>
|
|
||||||
<script src="app/services/authService.js"></script>
|
|
||||||
<script src="app/services/cryptoService.js"></script>
|
|
||||||
<script src="app/services/cipherService.js"></script>
|
|
||||||
<script src="app/services/validationService.js"></script>
|
|
||||||
<script src="app/services/passwordService.js"></script>
|
|
||||||
<script src="app/services/importService.js"></script>
|
|
||||||
|
|
||||||
<script src="app/global/globalModule.js"></script>
|
|
||||||
<script src="app/global/mainController.js"></script>
|
|
||||||
<script src="app/global/topNavController.js"></script>
|
|
||||||
<script src="app/global/sideNavController.js"></script>
|
|
||||||
|
|
||||||
<script src="app/accounts/accountsModule.js"></script>
|
|
||||||
<script src="app/accounts/accountsLoginController.js"></script>
|
|
||||||
<script src="app/accounts/accountsLogoutController.js"></script>
|
|
||||||
<script src="app/accounts/accountsRegisterController.js"></script>
|
|
||||||
<script src="app/accounts/accountsPasswordHintController.js"></script>
|
|
||||||
|
|
||||||
<script src="app/vault/vaultModule.js"></script>
|
|
||||||
<script src="app/vault/vaultController.js"></script>
|
|
||||||
<script src="app/vault/vaultEditSiteController.js"></script>
|
|
||||||
<script src="app/vault/vaultAddSiteController.js"></script>
|
|
||||||
<script src="app/vault/vaultEditFolderController.js"></script>
|
|
||||||
<script src="app/vault/vaultAddFolderController.js"></script>
|
|
||||||
|
|
||||||
<script src="app/settings/settingsModule.js"></script>
|
|
||||||
<script src="app/settings/settingsController.js"></script>
|
|
||||||
<script src="app/settings/settingsChangePasswordController.js"></script>
|
|
||||||
<script src="app/settings/settingsChangeEmailController.js"></script>
|
|
||||||
<script src="app/settings/settingsTwoFactorController.js"></script>
|
|
||||||
<script src="app/settings/settingsSessionsController.js"></script>
|
|
||||||
<script src="app/settings/settingsDeleteController.js"></script>
|
|
||||||
|
|
||||||
<script src="app/tools/toolsModule.js"></script>
|
|
||||||
<script src="app/tools/toolsController.js"></script>
|
|
||||||
<script src="app/tools/toolsImportController.js"></script>
|
|
||||||
<script src="app/tools/toolsExportController.js"></script>
|
|
||||||
<script src="app/tools/toolsAuditsController.js"></script>
|
|
||||||
<!-- @endexclude -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
15
src/app-id.json
Normal file
15
src/app-id.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"trustedFacets": [
|
||||||
|
{
|
||||||
|
"version": {
|
||||||
|
"major": 1,
|
||||||
|
"minor": 0
|
||||||
|
},
|
||||||
|
"ids": [
|
||||||
|
"https://vault.bitwarden.com",
|
||||||
|
"ios:bundle-id:com.8bit.bitwarden",
|
||||||
|
"android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
278
src/app/accounts/accountsLoginController.js
Normal file
278
src/app/accounts/accountsLoginController.js
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts')
|
||||||
|
|
||||||
|
.controller('accountsLoginController', function ($scope, $rootScope, $cookies, apiService, cryptoService, authService,
|
||||||
|
$state, constants, $analytics, $uibModal, $timeout, $window, $filter, toastr) {
|
||||||
|
$scope.state = $state;
|
||||||
|
$scope.twoFactorProviderConstants = constants.twoFactorProvider;
|
||||||
|
$scope.rememberTwoFactor = { checked: false };
|
||||||
|
var stopU2fCheck = true;
|
||||||
|
|
||||||
|
$scope.returnState = $state.params.returnState;
|
||||||
|
$scope.stateEmail = $state.params.email;
|
||||||
|
if (!$scope.returnState && $state.params.org) {
|
||||||
|
$scope.returnState = {
|
||||||
|
name: 'backend.user.settingsCreateOrg',
|
||||||
|
params: { plan: $state.params.org }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (!$scope.returnState && $state.params.premium) {
|
||||||
|
$scope.returnState = {
|
||||||
|
name: 'backend.user.settingsPremium'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($state.current.name.indexOf('twoFactor') > -1 && (!$scope.twoFactorProviders || !$scope.twoFactorProviders.length)) {
|
||||||
|
$state.go('frontend.login.info', { returnState: $scope.returnState });
|
||||||
|
}
|
||||||
|
|
||||||
|
var rememberedEmail = $cookies.get(constants.rememberedEmailCookieName);
|
||||||
|
if (rememberedEmail || $scope.stateEmail) {
|
||||||
|
$scope.model = {
|
||||||
|
email: $scope.stateEmail || rememberedEmail,
|
||||||
|
rememberEmail: rememberedEmail !== null
|
||||||
|
};
|
||||||
|
|
||||||
|
$timeout(function () {
|
||||||
|
$("#masterPassword").focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$timeout(function () {
|
||||||
|
$("#email").focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var _email,
|
||||||
|
_masterPassword;
|
||||||
|
|
||||||
|
$scope.twoFactorProviders = null;
|
||||||
|
$scope.twoFactorProvider = null;
|
||||||
|
|
||||||
|
$scope.login = function (model) {
|
||||||
|
$scope.loginPromise = authService.logIn(model.email, model.masterPassword).then(function (twoFactorProviders) {
|
||||||
|
if (model.rememberEmail) {
|
||||||
|
var cookieExpiration = new Date();
|
||||||
|
cookieExpiration.setFullYear(cookieExpiration.getFullYear() + 10);
|
||||||
|
|
||||||
|
$cookies.put(
|
||||||
|
constants.rememberedEmailCookieName,
|
||||||
|
model.email,
|
||||||
|
{ expires: cookieExpiration });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$cookies.remove(constants.rememberedEmailCookieName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (twoFactorProviders && Object.keys(twoFactorProviders).length > 0) {
|
||||||
|
_email = model.email;
|
||||||
|
_masterPassword = model.masterPassword;
|
||||||
|
|
||||||
|
$scope.twoFactorProviders = cleanProviders(twoFactorProviders);
|
||||||
|
$scope.twoFactorProvider = getDefaultProvider($scope.twoFactorProviders);
|
||||||
|
|
||||||
|
$analytics.eventTrack('Logged In To Two-step');
|
||||||
|
$state.go('frontend.login.twoFactor', { returnState: $scope.returnState }).then(function () {
|
||||||
|
$timeout(function () {
|
||||||
|
$("#code").focus();
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$analytics.eventTrack('Logged In');
|
||||||
|
loggedInGo();
|
||||||
|
}
|
||||||
|
|
||||||
|
model.masterPassword = '';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDefaultProvider(twoFactorProviders) {
|
||||||
|
var keys = Object.keys(twoFactorProviders);
|
||||||
|
var providerType = null;
|
||||||
|
var providerPriority = -1;
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var provider = $filter('filter')(constants.twoFactorProviderInfo, { type: keys[i], active: true });
|
||||||
|
if (provider.length && provider[0].priority > providerPriority) {
|
||||||
|
if (provider[0].type === constants.twoFactorProvider.u2f && !u2f.isSupported) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
providerType = provider[0].type;
|
||||||
|
providerPriority = provider[0].priority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (providerType === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(providerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanProviders(twoFactorProviders) {
|
||||||
|
if (canUseSecurityKey()) {
|
||||||
|
return twoFactorProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = Object.keys(twoFactorProviders);
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var provider = $filter('filter')(constants.twoFactorProviderInfo, {
|
||||||
|
type: keys[i],
|
||||||
|
active: true,
|
||||||
|
requiresUsb: false
|
||||||
|
});
|
||||||
|
if (!provider.length) {
|
||||||
|
delete twoFactorProviders[keys[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return twoFactorProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ref: https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser
|
||||||
|
function canUseSecurityKey() {
|
||||||
|
var mobile = false;
|
||||||
|
(function (a) {
|
||||||
|
if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) {
|
||||||
|
mobile = true;
|
||||||
|
}
|
||||||
|
})(navigator.userAgent || navigator.vendor || window.opera);
|
||||||
|
|
||||||
|
return !mobile && !navigator.userAgent.match(/iPad/i);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.twoFactor = function (token) {
|
||||||
|
if ($scope.twoFactorProvider === constants.twoFactorProvider.email ||
|
||||||
|
$scope.twoFactorProvider === constants.twoFactorProvider.authenticator) {
|
||||||
|
token = token.replace(' ', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.twoFactorPromise = authService.logIn(_email, _masterPassword, token, $scope.twoFactorProvider,
|
||||||
|
$scope.rememberTwoFactor.checked || false);
|
||||||
|
|
||||||
|
$scope.twoFactorPromise.then(function () {
|
||||||
|
$analytics.eventTrack('Logged In From Two-step');
|
||||||
|
loggedInGo();
|
||||||
|
}, function () {
|
||||||
|
if ($scope.twoFactorProvider === constants.twoFactorProvider.u2f) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.anotherMethod = function () {
|
||||||
|
var modal = $uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/accounts/views/accountsTwoFactorMethods.html',
|
||||||
|
controller: 'accountsTwoFactorMethodsController',
|
||||||
|
resolve: {
|
||||||
|
providers: function () { return $scope.twoFactorProviders; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.result.then(function (provider) {
|
||||||
|
$scope.twoFactorProvider = provider;
|
||||||
|
$timeout(function () {
|
||||||
|
$("#code").focus();
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.sendEmail = function (doToast) {
|
||||||
|
if ($scope.twoFactorProvider !== constants.twoFactorProvider.email) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cryptoService.makeKeyAndHash(_email, _masterPassword).then(function (result) {
|
||||||
|
return apiService.twoFactor.sendEmailLogin({
|
||||||
|
email: _email,
|
||||||
|
masterPasswordHash: result.hash
|
||||||
|
}).$promise;
|
||||||
|
}).then(function () {
|
||||||
|
if (doToast) {
|
||||||
|
toastr.success('Verification email sent to ' + $scope.twoFactorEmail + '.');
|
||||||
|
}
|
||||||
|
}, function () {
|
||||||
|
toastr.error('Could not send verification email.');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$on('$destroy', function () {
|
||||||
|
stopU2fCheck = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
function loggedInGo() {
|
||||||
|
if ($scope.returnState) {
|
||||||
|
$state.go($scope.returnState.name, $scope.returnState.params);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$state.go('backend.user.vault');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
stopU2fCheck = true;
|
||||||
|
var params;
|
||||||
|
if ($scope.twoFactorProvider === constants.twoFactorProvider.duo) {
|
||||||
|
params = $scope.twoFactorProviders[constants.twoFactorProvider.duo];
|
||||||
|
|
||||||
|
$window.Duo.init({
|
||||||
|
host: params.Host,
|
||||||
|
sig_request: params.Signature,
|
||||||
|
submit_callback: function (theForm) {
|
||||||
|
var response = $(theForm).find('input[name="sig_response"]').val();
|
||||||
|
$scope.twoFactor(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if ($scope.twoFactorProvider === constants.twoFactorProvider.u2f) {
|
||||||
|
stopU2fCheck = false;
|
||||||
|
params = $scope.twoFactorProviders[constants.twoFactorProvider.u2f];
|
||||||
|
var challenges = JSON.parse(params.Challenges);
|
||||||
|
|
||||||
|
initU2f(challenges);
|
||||||
|
}
|
||||||
|
else if ($scope.twoFactorProvider === constants.twoFactorProvider.email) {
|
||||||
|
params = $scope.twoFactorProviders[constants.twoFactorProvider.email];
|
||||||
|
$scope.twoFactorEmail = params.Email;
|
||||||
|
if (Object.keys($scope.twoFactorProviders).length > 1) {
|
||||||
|
$scope.sendEmail(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initU2f(challenges) {
|
||||||
|
if (stopU2fCheck) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (challenges.length < 1 || $scope.twoFactorProvider !== constants.twoFactorProvider.u2f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('listening for u2f key...');
|
||||||
|
|
||||||
|
$window.u2f.sign(challenges[0].appId, challenges[0].challenge, [{
|
||||||
|
version: challenges[0].version,
|
||||||
|
keyHandle: challenges[0].keyHandle
|
||||||
|
}], function (data) {
|
||||||
|
if ($scope.twoFactorProvider !== constants.twoFactorProvider.u2f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.errorCode) {
|
||||||
|
console.log(data.errorCode);
|
||||||
|
|
||||||
|
$timeout(function () {
|
||||||
|
initU2f(challenges);
|
||||||
|
}, data.errorCode === 5 ? 0 : 1000);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$scope.twoFactor(JSON.stringify(data));
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
});
|
||||||
45
src/app/accounts/accountsOrganizationAcceptController.js
Normal file
45
src/app/accounts/accountsOrganizationAcceptController.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts')
|
||||||
|
|
||||||
|
.controller('accountsOrganizationAcceptController', function ($scope, $state, apiService, authService, toastr, $analytics) {
|
||||||
|
$scope.state = {
|
||||||
|
name: $state.current.name,
|
||||||
|
params: $state.params
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!$state.params.organizationId || !$state.params.organizationUserId || !$state.params.token ||
|
||||||
|
!$state.params.email || !$state.params.organizationName) {
|
||||||
|
$state.go('frontend.login.info').then(function () {
|
||||||
|
toastr.error('Invalid parameters.');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.$on('$viewContentLoaded', function () {
|
||||||
|
if (authService.isAuthenticated()) {
|
||||||
|
$scope.accepting = true;
|
||||||
|
apiService.organizationUsers.accept(
|
||||||
|
{
|
||||||
|
orgId: $state.params.organizationId,
|
||||||
|
id: $state.params.organizationUserId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
token: $state.params.token
|
||||||
|
}, function () {
|
||||||
|
$analytics.eventTrack('Accepted Invitation');
|
||||||
|
$state.go('backend.user.vault', null, { location: 'replace' }).then(function () {
|
||||||
|
toastr.success('You can access this organization once an administrator confirms your membership.' +
|
||||||
|
' We\'ll send an email when that happens.', 'Invite Accepted', { timeOut: 10000 });
|
||||||
|
});
|
||||||
|
}, function () {
|
||||||
|
$analytics.eventTrack('Failed To Accept Invitation');
|
||||||
|
$state.go('backend.user.vault', null, { location: 'replace' }).then(function () {
|
||||||
|
toastr.error('Unable to accept invitation.', 'Error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$scope.loading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
angular
|
angular
|
||||||
.module('bit.accounts')
|
.module('bit.accounts')
|
||||||
|
|
||||||
.controller('accountsPasswordHintController', function ($scope, $rootScope, apiService) {
|
.controller('accountsPasswordHintController', function ($scope, $rootScope, apiService, $analytics) {
|
||||||
$scope.success = false;
|
$scope.success = false;
|
||||||
|
|
||||||
$scope.submit = function (model) {
|
$scope.submit = function (model) {
|
||||||
$scope.submitPromise = apiService.accounts.postPasswordHint({ email: model.email }, function () {
|
$scope.submitPromise = apiService.accounts.postPasswordHint({ email: model.email }, function () {
|
||||||
|
$analytics.eventTrack('Requested Password Hint');
|
||||||
$scope.success = true;
|
$scope.success = true;
|
||||||
}).$promise;
|
}).$promise;
|
||||||
};
|
};
|
||||||
21
src/app/accounts/accountsRecoverController.js
Normal file
21
src/app/accounts/accountsRecoverController.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts')
|
||||||
|
|
||||||
|
.controller('accountsRecoverController', function ($scope, apiService, cryptoService, $analytics) {
|
||||||
|
$scope.success = false;
|
||||||
|
|
||||||
|
$scope.submit = function (model) {
|
||||||
|
var email = model.email.toLowerCase();
|
||||||
|
|
||||||
|
$scope.submitPromise = cryptoService.makeKeyAndHash(model.email, model.masterPassword).then(function (result) {
|
||||||
|
return apiService.twoFactor.recover({
|
||||||
|
email: email,
|
||||||
|
masterPasswordHash: result.hash,
|
||||||
|
recoveryCode: model.code.replace(/\s/g, '').toLowerCase()
|
||||||
|
}).$promise;
|
||||||
|
}).then(function () {
|
||||||
|
$analytics.eventTrack('Recovered 2FA');
|
||||||
|
$scope.success = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
13
src/app/accounts/accountsRecoverDeleteController.js
Normal file
13
src/app/accounts/accountsRecoverDeleteController.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts')
|
||||||
|
|
||||||
|
.controller('accountsRecoverDeleteController', function ($scope, $rootScope, apiService, $analytics) {
|
||||||
|
$scope.success = false;
|
||||||
|
|
||||||
|
$scope.submit = function (model) {
|
||||||
|
$scope.submitPromise = apiService.accounts.postDeleteRecover({ email: model.email }, function () {
|
||||||
|
$analytics.eventTrack('Started Delete Recovery');
|
||||||
|
$scope.success = true;
|
||||||
|
}).$promise;
|
||||||
|
};
|
||||||
|
});
|
||||||
92
src/app/accounts/accountsRegisterController.js
Normal file
92
src/app/accounts/accountsRegisterController.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts')
|
||||||
|
|
||||||
|
.controller('accountsRegisterController', function ($scope, $location, apiService, cryptoService, validationService,
|
||||||
|
$analytics, $state, $timeout) {
|
||||||
|
var params = $location.search();
|
||||||
|
var stateParams = $state.params;
|
||||||
|
$scope.createOrg = stateParams.org;
|
||||||
|
|
||||||
|
if (!stateParams.returnState && stateParams.org) {
|
||||||
|
$scope.returnState = {
|
||||||
|
name: 'backend.user.settingsCreateOrg',
|
||||||
|
params: { plan: $state.params.org }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (!stateParams.returnState && stateParams.premium) {
|
||||||
|
$scope.returnState = {
|
||||||
|
name: 'backend.user.settingsPremium',
|
||||||
|
params: { plan: $state.params.org }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$scope.returnState = stateParams.returnState;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.success = false;
|
||||||
|
$scope.model = {
|
||||||
|
email: params.email ? params.email : stateParams.email
|
||||||
|
};
|
||||||
|
$scope.readOnlyEmail = stateParams.email !== null;
|
||||||
|
|
||||||
|
|
||||||
|
$timeout(function () {
|
||||||
|
if ($scope.model.email) {
|
||||||
|
$("#name").focus();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#email").focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.registerPromise = null;
|
||||||
|
$scope.register = function (form) {
|
||||||
|
var error = false;
|
||||||
|
|
||||||
|
if ($scope.model.masterPassword.length < 8) {
|
||||||
|
validationService.addError(form, 'MasterPassword', 'Master password must be at least 8 characters long.', true);
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
if ($scope.model.masterPassword !== $scope.model.confirmMasterPassword) {
|
||||||
|
validationService.addError(form, 'ConfirmMasterPassword', 'Master password confirmation does not match.', true);
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var email = $scope.model.email.toLowerCase();
|
||||||
|
var makeResult, encKey;
|
||||||
|
|
||||||
|
$scope.registerPromise = cryptoService.makeKeyAndHash(email, $scope.model.masterPassword).then(function (result) {
|
||||||
|
makeResult = result;
|
||||||
|
encKey = cryptoService.makeEncKey(result.key);
|
||||||
|
return cryptoService.makeKeyPair(encKey.encKey);
|
||||||
|
}).then(function (result) {
|
||||||
|
var request = {
|
||||||
|
name: $scope.model.name,
|
||||||
|
email: email,
|
||||||
|
masterPasswordHash: makeResult.hash,
|
||||||
|
masterPasswordHint: $scope.model.masterPasswordHint,
|
||||||
|
key: encKey.encKeyEnc,
|
||||||
|
keys: {
|
||||||
|
publicKey: result.publicKey,
|
||||||
|
encryptedPrivateKey: result.privateKeyEnc
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return apiService.accounts.register(request).$promise;
|
||||||
|
}, function (errors) {
|
||||||
|
validationService.addError(form, null, 'Problem generating keys.', true);
|
||||||
|
return false;
|
||||||
|
}).then(function (result) {
|
||||||
|
if (result === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.success = true;
|
||||||
|
$analytics.eventTrack('Registered');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
40
src/app/accounts/accountsTwoFactorMethodsController.js
Normal file
40
src/app/accounts/accountsTwoFactorMethodsController.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts')
|
||||||
|
|
||||||
|
.controller('accountsTwoFactorMethodsController', function ($scope, $uibModalInstance, $analytics, providers, constants) {
|
||||||
|
$analytics.eventTrack('accountsTwoFactorMethodsController', { category: 'Modal' });
|
||||||
|
|
||||||
|
$scope.providers = [];
|
||||||
|
|
||||||
|
if (providers.hasOwnProperty(constants.twoFactorProvider.authenticator)) {
|
||||||
|
add(constants.twoFactorProvider.authenticator);
|
||||||
|
}
|
||||||
|
if (providers.hasOwnProperty(constants.twoFactorProvider.yubikey)) {
|
||||||
|
add(constants.twoFactorProvider.yubikey);
|
||||||
|
}
|
||||||
|
if (providers.hasOwnProperty(constants.twoFactorProvider.email)) {
|
||||||
|
add(constants.twoFactorProvider.email);
|
||||||
|
}
|
||||||
|
if (providers.hasOwnProperty(constants.twoFactorProvider.duo)) {
|
||||||
|
add(constants.twoFactorProvider.duo);
|
||||||
|
}
|
||||||
|
if (providers.hasOwnProperty(constants.twoFactorProvider.u2f) && u2f.isSupported) {
|
||||||
|
add(constants.twoFactorProvider.u2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.choose = function (provider) {
|
||||||
|
$uibModalInstance.close(provider.type);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('close');
|
||||||
|
};
|
||||||
|
|
||||||
|
function add(type) {
|
||||||
|
for (var i = 0; i < constants.twoFactorProviderInfo.length; i++) {
|
||||||
|
if (constants.twoFactorProviderInfo[i].type === type) {
|
||||||
|
$scope.providers.push(constants.twoFactorProviderInfo[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
28
src/app/accounts/accountsVerifyEmailController.js
Normal file
28
src/app/accounts/accountsVerifyEmailController.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts')
|
||||||
|
|
||||||
|
.controller('accountsVerifyEmailController', function ($scope, $state, apiService, toastr, $analytics) {
|
||||||
|
if (!$state.params.userId || !$state.params.token) {
|
||||||
|
$state.go('frontend.login.info').then(function () {
|
||||||
|
toastr.error('Invalid parameters.');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.$on('$viewContentLoaded', function () {
|
||||||
|
apiService.accounts.verifyEmailToken({},
|
||||||
|
{
|
||||||
|
token: $state.params.token,
|
||||||
|
userId: $state.params.userId
|
||||||
|
}, function () {
|
||||||
|
$analytics.eventTrack('Verified Email');
|
||||||
|
$state.go('frontend.login.info', null, { location: 'replace' }).then(function () {
|
||||||
|
toastr.success('Your email has been verified. Thank you.', 'Success');
|
||||||
|
});
|
||||||
|
}, function () {
|
||||||
|
$state.go('frontend.login.info', null, { location: 'replace' }).then(function () {
|
||||||
|
toastr.error('Unable to verify email.', 'Error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
36
src/app/accounts/accountsVerifyRecoverDeleteController.js
Normal file
36
src/app/accounts/accountsVerifyRecoverDeleteController.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts')
|
||||||
|
|
||||||
|
.controller('accountsVerifyRecoverDeleteController', function ($scope, $state, apiService, toastr, $analytics) {
|
||||||
|
if (!$state.params.userId || !$state.params.token || !$state.params.email) {
|
||||||
|
$state.go('frontend.login.info').then(function () {
|
||||||
|
toastr.error('Invalid parameters.');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.email = $state.params.email;
|
||||||
|
|
||||||
|
$scope.delete = function () {
|
||||||
|
if (!confirm('Are you sure you want to delete this account? This cannot be undone.')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.deleting = true;
|
||||||
|
apiService.accounts.postDeleteRecoverToken({},
|
||||||
|
{
|
||||||
|
token: $state.params.token,
|
||||||
|
userId: $state.params.userId
|
||||||
|
}, function () {
|
||||||
|
$analytics.eventTrack('Recovered Delete');
|
||||||
|
$state.go('frontend.login.info', null, { location: 'replace' }).then(function () {
|
||||||
|
toastr.success('Your account has been deleted. You can register a new account again if you like.',
|
||||||
|
'Success');
|
||||||
|
});
|
||||||
|
}, function () {
|
||||||
|
$state.go('frontend.login.info', null, { location: 'replace' }).then(function () {
|
||||||
|
toastr.error('Unable to delete account.', 'Error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<p class="login-box-msg">Log in to access your vault.</p>
|
<p class="login-box-msg">Log in to access your vault.</p>
|
||||||
<form name="loginForm" ng-submit="loginForm.$valid && login(model)" api-form="loginPromise">
|
<form name="loginForm" ng-submit="loginForm.$valid && login(model)" api-form="loginPromise">
|
||||||
<div class="callout callout-danger validation-errors" ng-show="loginForm.$errors">
|
<div class="callout callout-danger validation-errors" ng-show="loginForm.$errors">
|
||||||
<h4>Errors have occured</h4>
|
<h4>Errors have occurred</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li ng-repeat="e in loginForm.$errors">{{e}}</li>
|
<li ng-repeat="e in loginForm.$errors">{{e}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -35,7 +35,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<ul>
|
<ul>
|
||||||
<li><a ui-sref="frontend.register">Create a new account</a></li>
|
<li>
|
||||||
<li><a ui-sref="frontend.passwordHint">Get master password hint</a></li>
|
<a ui-sref="frontend.register({returnState: returnState, email: stateEmail})">
|
||||||
|
Create a new account
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a ui-sref="frontend.passwordHint">
|
||||||
|
Get master password hint
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</form>
|
</form>
|
||||||
166
src/app/accounts/views/accountsLoginTwoFactor.html
Normal file
166
src/app/accounts/views/accountsLoginTwoFactor.html
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<div ng-if="twoFactorProvider === twoFactorProviderConstants.authenticator ||
|
||||||
|
twoFactorProvider === twoFactorProviderConstants.email">
|
||||||
|
<p class="login-box-msg" ng-if="twoFactorProvider === twoFactorProviderConstants.authenticator">
|
||||||
|
Enter the 6 digit verification code from your authenticator app.
|
||||||
|
</p>
|
||||||
|
<div ng-if="twoFactorProvider === twoFactorProviderConstants.email" class="text-center">
|
||||||
|
<p class="login-box-msg">
|
||||||
|
Enter the 6 digit verification code that was emailed to <b>{{twoFactorEmail}}</b>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Didn't get the email?
|
||||||
|
<a href="#" stop-click ng-click="sendEmail(true)" ng-if="twoFactorProvider === twoFactorProviderConstants.email">
|
||||||
|
Send it again
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
||||||
|
<h4>Errors have occurred</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in twoFactorForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-feedback" show-errors>
|
||||||
|
<label for="code" class="sr-only">Code</label>
|
||||||
|
<input type="text" id="code" name="Code" class="form-control" placeholder="Verification code"
|
||||||
|
ng-model="token" required api-field autocomplete="off" autocorrect="off" autocapitalize="off"
|
||||||
|
spellcheck="false" />
|
||||||
|
<span class="fa fa-lock form-control-feedback"></span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-7">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="rememberMe" ng-model="rememberTwoFactor.checked" /> Remember Me
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-5">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="twoFactorForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="twoFactorForm.$loading"></i>Log In
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-if="twoFactorProvider === twoFactorProviderConstants.yubikey">
|
||||||
|
<p class="login-box-msg">
|
||||||
|
Complete logging in with YubiKey.
|
||||||
|
</p>
|
||||||
|
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
|
||||||
|
autocomplete="off">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
||||||
|
<h4>Errors have occurred</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in twoFactorForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p>Insert your YubiKey into your computer's USB port, then touch its button.</p>
|
||||||
|
<p>
|
||||||
|
<img src="images/two-factor/yubikey.jpg" alt="" class="img-rounded img-responsive" />
|
||||||
|
</p>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="code" class="sr-only">Token</label>
|
||||||
|
<input type="password" id="code" name="Token" class="form-control" ng-model="token" required api-field />
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-7">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="rememberMe" ng-model="rememberTwoFactor.checked" /> Remember Me
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-5">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="twoFactorForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="twoFactorForm.$loading"></i>Log In
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-if="twoFactorProvider === twoFactorProviderConstants.duo">
|
||||||
|
<p class="login-box-msg">
|
||||||
|
Complete logging in with Duo.
|
||||||
|
</p>
|
||||||
|
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise"
|
||||||
|
autocomplete="off">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
||||||
|
<h4>Errors have occurred</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in twoFactorForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div id="duoFrameWrapper">
|
||||||
|
<iframe id="duo_iframe"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-7">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="rememberMe" ng-model="rememberTwoFactor.checked" /> Remember Me
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-5">
|
||||||
|
<span ng-show="twoFactorForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon"></i> Logging in...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-if="twoFactorProvider === twoFactorProviderConstants.u2f">
|
||||||
|
<p class="login-box-msg">
|
||||||
|
Complete logging in with FIDO U2F.
|
||||||
|
</p>
|
||||||
|
<form name="twoFactorForm" api-form="twoFactorPromise" autocomplete="off">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
||||||
|
<h4>Errors have occurred</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in twoFactorForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p>Insert your Security Key into your computer's USB port. If it has a button, touch it.</p>
|
||||||
|
<p>
|
||||||
|
<img src="images/two-factor/u2fkey.jpg" alt="" class="img-rounded img-responsive" />
|
||||||
|
</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-7">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="rememberMe" ng-model="rememberTwoFactor.checked" /> Remember Me
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-5">
|
||||||
|
<span ng-show="twoFactorForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon"></i> Logging in...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-if="twoFactorProvider === null">
|
||||||
|
<p>
|
||||||
|
This account has two-step login enabled, however, none of the configured two-step providers are supported by this
|
||||||
|
web browser.
|
||||||
|
</p>
|
||||||
|
Please use a supported web browser (such as Chrome) and/or add additional providers that are better supported
|
||||||
|
across web browsers (such as an authenticator app).
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a stop-click href="#" ng-click="anotherMethod()">Use another two-step login method</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a ui-sref="frontend.login.info({returnState: returnState})">Back to log in</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
32
src/app/accounts/views/accountsOrganizationAccept.html
Normal file
32
src/app/accounts/views/accountsOrganizationAccept.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<div class="login-box">
|
||||||
|
<div class="login-logo">
|
||||||
|
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||||
|
</div>
|
||||||
|
<div class="login-box-body">
|
||||||
|
<div ng-show="loading">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
<div ng-show="accepting">
|
||||||
|
Accepting invitation...
|
||||||
|
</div>
|
||||||
|
<div ng-show="!loading && !accepting">
|
||||||
|
<p class="login-box-msg">Join {{state.params.organizationName}}</p>
|
||||||
|
<p class="text-center"><strong>{{state.params.email}}</strong></p>
|
||||||
|
<p>
|
||||||
|
You've been invited to join the organization listed above.
|
||||||
|
To accept the invitation, you need to log in or create a new bitwarden account.
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<a ui-sref="frontend.login.info({returnState: state, email: state.params.email})"
|
||||||
|
class="btn btn-primary btn-block btn-flat">Log In</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<a ui-sref="frontend.register({returnState: state, email: state.params.email})"
|
||||||
|
class="btn btn-primary btn-block btn-flat">Create Account</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<form name="passwordHintForm" ng-submit="passwordHintForm.$valid && submit(model)" ng-show="!success"
|
<form name="passwordHintForm" ng-submit="passwordHintForm.$valid && submit(model)" ng-show="!success"
|
||||||
api-form="submitPromise">
|
api-form="submitPromise">
|
||||||
<div class="callout callout-danger validation-errors" ng-show="passwordHintForm.$errors">
|
<div class="callout callout-danger validation-errors" ng-show="passwordHintForm.$errors">
|
||||||
<h4>Errors have occured</h4>
|
<h4>Errors have occurred</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li ng-repeat="e in passwordHintForm.$errors">{{e}}</li>
|
<li ng-repeat="e in passwordHintForm.$errors">{{e}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
56
src/app/accounts/views/accountsRecover.html
Normal file
56
src/app/accounts/views/accountsRecover.html
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<div class="login-box">
|
||||||
|
<div class="login-logo">
|
||||||
|
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||||
|
</div>
|
||||||
|
<div class="login-box-body">
|
||||||
|
<p class="login-box-msg">
|
||||||
|
In the event that you cannot access your account through your normal two-step login methods, you can use your
|
||||||
|
two-step login recovery code to disable all two-step providers on your account.
|
||||||
|
<a href="https://help.bitwarden.com/article/lost-two-step-device/" target="_blank">Learn more</a>
|
||||||
|
</p>
|
||||||
|
<div class="text-center" ng-show="success">
|
||||||
|
<div class="callout callout-success">
|
||||||
|
Two-step login has been successfully disabled on your account.
|
||||||
|
</div>
|
||||||
|
<a ui-sref="frontend.login.info">Ready to log in?</a>
|
||||||
|
</div>
|
||||||
|
<form name="recoverForm" ng-submit="recoverForm.$valid && submit(model)" ng-show="!success"
|
||||||
|
api-form="submitPromise">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="recoverForm.$errors">
|
||||||
|
<h4>Errors have occurred</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in recoverForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-feedback" show-errors>
|
||||||
|
<label for="email" class="sr-only">Email</label>
|
||||||
|
<input type="email" id="email" name="Email" class="form-control" placeholder="Email" ng-model="model.email"
|
||||||
|
required api-field />
|
||||||
|
<span class="fa fa-envelope form-control-feedback"></span>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-feedback" show-errors>
|
||||||
|
<label for="masterPassword" class="sr-only">Master Password</label>
|
||||||
|
<input type="password" id="masterPassword" name="MasterPasswordHash" class="form-control" placeholder="Master Password"
|
||||||
|
ng-model="model.masterPassword"
|
||||||
|
required api-field />
|
||||||
|
<span class="fa fa-lock form-control-feedback"></span>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-feedback" show-errors>
|
||||||
|
<label for="code" class="sr-only">Recovery code</label>
|
||||||
|
<input type="text" id="code" name="RecoveryCode" class="form-control" placeholder="Recovery code"
|
||||||
|
ng-model="model.code" required api-field />
|
||||||
|
<span class="fa fa-key form-control-feedback"></span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-7">
|
||||||
|
<a ui-sref="frontend.login.info">Ready to log in?</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-5">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="recoverForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="recoverForm.$loading"></i>Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
39
src/app/accounts/views/accountsRecoverDelete.html
Normal file
39
src/app/accounts/views/accountsRecoverDelete.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<div class="login-box">
|
||||||
|
<div class="login-logo">
|
||||||
|
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||||
|
</div>
|
||||||
|
<div class="login-box-body">
|
||||||
|
<p class="login-box-msg">Enter your email address below to recover & delete your bitwarden account.</p>
|
||||||
|
<div ng-show="success" class="text-center">
|
||||||
|
<div class="callout callout-success">
|
||||||
|
If your account exists ({{model.email}}) we've sent you an email with further instructions.
|
||||||
|
</div>
|
||||||
|
<a ui-sref="frontend.login.info">Return to log in</a>
|
||||||
|
</div>
|
||||||
|
<form name="form" ng-submit="form.$valid && submit(model)" ng-show="!success"
|
||||||
|
api-form="submitPromise">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="form.$errors">
|
||||||
|
<h4>Errors have occurred</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in form.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-feedback" show-errors>
|
||||||
|
<label for="email" class="sr-only">Your account email address</label>
|
||||||
|
<input type="email" id="email" name="Email" class="form-control" placeholder="Your account email address"
|
||||||
|
ng-model="model.email" required api-field />
|
||||||
|
<span class="fa fa-envelope form-control-feedback"></span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-7">
|
||||||
|
<a ui-sref="frontend.login.info">Return to log in</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-5">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="form.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="form.$loading"></i>Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -9,12 +9,16 @@
|
|||||||
<h4>Account Created!</h4>
|
<h4>Account Created!</h4>
|
||||||
<p>You may now log in to your new account.</p>
|
<p>You may now log in to your new account.</p>
|
||||||
</div>
|
</div>
|
||||||
<a ui-sref="frontend.login.info">Ready to log in?</a>
|
<a ui-sref="frontend.login.info({returnState: returnState, email: model.email})">Ready to log in?</a>
|
||||||
</div>
|
</div>
|
||||||
<form name="registerForm" ng-submit="registerForm.$valid && register(registerForm)" ng-show="!success"
|
<form name="registerForm" ng-submit="registerForm.$valid && register(registerForm)" ng-show="!success"
|
||||||
api-form="registerPromise">
|
api-form="registerPromise">
|
||||||
|
<div class="callout callout-default" ng-show="createOrg">
|
||||||
|
<h4>Create Organization, Step 1</h4>
|
||||||
|
<p>Before creating your organization, you first need to create a free personal account.</p>
|
||||||
|
</div>
|
||||||
<div class="callout callout-danger validation-errors" ng-show="registerForm.$errors">
|
<div class="callout callout-danger validation-errors" ng-show="registerForm.$errors">
|
||||||
<h4>Errors have occured</h4>
|
<h4>Errors have occurred</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li ng-repeat="e in registerForm.$errors">{{e}}</li>
|
<li ng-repeat="e in registerForm.$errors">{{e}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -22,7 +26,7 @@
|
|||||||
<div class="form-group has-feedback" show-errors>
|
<div class="form-group has-feedback" show-errors>
|
||||||
<label for="email" class="sr-only">Email</label>
|
<label for="email" class="sr-only">Email</label>
|
||||||
<input type="email" id="email" name="Email" class="form-control" placeholder="Email" ng-model="model.email"
|
<input type="email" id="email" name="Email" class="form-control" placeholder="Email" ng-model="model.email"
|
||||||
required api-field />
|
ng-readonly="readOnlyEmail" required api-field />
|
||||||
<span class="fa fa-envelope form-control-feedback"></span>
|
<span class="fa fa-envelope form-control-feedback"></span>
|
||||||
<p class="help-block">You'll use your email address to log in.</p>
|
<p class="help-block">You'll use your email address to log in.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-7">
|
<div class="col-xs-7">
|
||||||
<a ui-sref="frontend.login.info">Already have an account?</a>
|
<a ui-sref="frontend.login.info({returnState: returnState})">Already have an account?</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-5">
|
<div class="col-xs-5">
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="registerForm.$loading">
|
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="registerForm.$loading">
|
||||||
@@ -68,6 +72,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr />
|
||||||
|
By clicking the above "Submit" button, you are agreeing to the
|
||||||
|
<a href="https://bitwarden.com/terms/" target="_blank">Terms of Service</a>
|
||||||
|
and the
|
||||||
|
<a href="https://bitwarden.com/privacy/" target="_blank">Privacy Policy</a>.
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
25
src/app/accounts/views/accountsTwoFactorMethods.html
Normal file
25
src/app/accounts/views/accountsTwoFactorMethods.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<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"><i class="fa fa-key"></i> Two-step Providers</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="list-group" ng-repeat="provider in providers | orderBy: 'displayOrder'">
|
||||||
|
<a href="#" stop-click class="list-group-item" ng-click="choose(provider)">
|
||||||
|
<img alt="{{::provider.name}}" ng-src="{{'images/two-factor/' + provider.image}}" class="pull-right hidden-xs" />
|
||||||
|
<h4 class="list-group-item-heading">{{::provider.name}}</h4>
|
||||||
|
<p class="list-group-item-text">{{::provider.description}}</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="list-group" style="margin-bottom: 0;">
|
||||||
|
<a href="https://help.bitwarden.com/article/lost-two-step-device/" target="_blank" class="list-group-item">
|
||||||
|
<h4 class="list-group-item-heading">Recovery Code</h4>
|
||||||
|
<p class="list-group-item-text">
|
||||||
|
Lost access to all of your two-factor providers? Use your recovery code to disable
|
||||||
|
all two-factor providers from your account.
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
8
src/app/accounts/views/accountsVerifyEmail.html
Normal file
8
src/app/accounts/views/accountsVerifyEmail.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<div class="login-box">
|
||||||
|
<div class="login-logo">
|
||||||
|
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||||
|
</div>
|
||||||
|
<div class="login-box-body">
|
||||||
|
Verifying email...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
21
src/app/accounts/views/accountsVerifyRecoverDelete.html
Normal file
21
src/app/accounts/views/accountsVerifyRecoverDelete.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<div class="login-box">
|
||||||
|
<div class="login-logo">
|
||||||
|
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||||
|
</div>
|
||||||
|
<div class="login-box-body">
|
||||||
|
<div ng-if="deleting">
|
||||||
|
Deleting account...
|
||||||
|
</div>
|
||||||
|
<div ng-if="!deleting">
|
||||||
|
<div class="callout callout-warning">
|
||||||
|
<h4><i class="fa fa-warning fa-fw"></i> Warning</h4>
|
||||||
|
This will permanently delete your account. This cannot be undone.
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
You have requested to delete your bitwarden account (<b>{{email}}</b>).
|
||||||
|
Click the button below to confirm and proceed.
|
||||||
|
</p>
|
||||||
|
<button ng-click="delete()" class="btn btn-danger btn-block btn-flat">Delete Account</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -3,18 +3,25 @@
|
|||||||
'ui.router',
|
'ui.router',
|
||||||
'ngMessages',
|
'ngMessages',
|
||||||
'angular-jwt',
|
'angular-jwt',
|
||||||
'angular-md5',
|
|
||||||
'ui.bootstrap.showErrors',
|
'ui.bootstrap.showErrors',
|
||||||
'toastr',
|
'toastr',
|
||||||
'angulartics',
|
'angulartics',
|
||||||
|
// @if !selfHosted
|
||||||
'angulartics.google.analytics',
|
'angulartics.google.analytics',
|
||||||
|
'angular-stripe',
|
||||||
|
'credit-cards',
|
||||||
|
// @endif
|
||||||
|
'angular-promise-polyfill',
|
||||||
|
|
||||||
'bit.directives',
|
'bit.directives',
|
||||||
|
'bit.filters',
|
||||||
'bit.services',
|
'bit.services',
|
||||||
|
|
||||||
'bit.global',
|
'bit.global',
|
||||||
'bit.accounts',
|
'bit.accounts',
|
||||||
'bit.vault',
|
'bit.vault',
|
||||||
'bit.settings',
|
'bit.settings',
|
||||||
'bit.tools'
|
'bit.tools',
|
||||||
|
'bit.organization',
|
||||||
|
'bit.reports'
|
||||||
]);
|
]);
|
||||||
383
src/app/config.js
Normal file
383
src/app/config.js
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
angular
|
||||||
|
.module('bit')
|
||||||
|
|
||||||
|
.config(function ($stateProvider, $urlRouterProvider, $httpProvider, jwtInterceptorProvider, jwtOptionsProvider,
|
||||||
|
$uibTooltipProvider, toastrConfig, $locationProvider, $qProvider, appSettings
|
||||||
|
// @if !selfHosted
|
||||||
|
, stripeProvider
|
||||||
|
// @endif
|
||||||
|
) {
|
||||||
|
angular.extend(appSettings, window.bitwardenAppSettings);
|
||||||
|
|
||||||
|
$qProvider.errorOnUnhandledRejections(false);
|
||||||
|
$locationProvider.hashPrefix('');
|
||||||
|
|
||||||
|
var jwtConfig = {
|
||||||
|
whiteListedDomains: appSettings.whitelistDomains
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!appSettings.selfHosted) {
|
||||||
|
var userAgent = navigator.userAgent.toLowerCase();
|
||||||
|
if (userAgent.indexOf('safari') > -1 && userAgent.indexOf('chrome') === -1) {
|
||||||
|
// Safari doesn't work with unconventional "Content-Language" header for CORS.
|
||||||
|
// See notes here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
|
||||||
|
jwtConfig.urlParam = 'access_token';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Using Content-Language header since it is unused and is a CORS-safelisted header. This avoids pre-flights.
|
||||||
|
jwtConfig.authHeader = 'Content-Language';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtOptionsProvider.config(jwtConfig);
|
||||||
|
var refreshPromise;
|
||||||
|
jwtInterceptorProvider.tokenGetter = /*@ngInject*/ function (options, tokenService, authService) {
|
||||||
|
if (options.url.indexOf(appSettings.apiUri) !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refreshPromise) {
|
||||||
|
return refreshPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = tokenService.getToken();
|
||||||
|
if (!token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tokenService.tokenNeedsRefresh(token)) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshPromise = authService.refreshAccessToken().then(function (newToken) {
|
||||||
|
refreshPromise = null;
|
||||||
|
return newToken || token;
|
||||||
|
});
|
||||||
|
return refreshPromise;
|
||||||
|
};
|
||||||
|
|
||||||
|
// @if !selfHosted
|
||||||
|
stripeProvider.setPublishableKey(appSettings.stripeKey);
|
||||||
|
// @endif
|
||||||
|
|
||||||
|
angular.extend(toastrConfig, {
|
||||||
|
closeButton: true,
|
||||||
|
progressBar: true,
|
||||||
|
showMethod: 'slideDown',
|
||||||
|
target: '.toast-target'
|
||||||
|
});
|
||||||
|
|
||||||
|
$uibTooltipProvider.options({
|
||||||
|
popupDelay: 600,
|
||||||
|
appendToBody: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($httpProvider.defaults.headers.post) {
|
||||||
|
$httpProvider.defaults.headers.post = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpProvider.defaults.headers.post['Content-Type'] = 'text/plain; charset=utf-8';
|
||||||
|
|
||||||
|
// stop IE from caching get requests
|
||||||
|
if (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0) {
|
||||||
|
if (!$httpProvider.defaults.headers.get) {
|
||||||
|
$httpProvider.defaults.headers.get = {};
|
||||||
|
}
|
||||||
|
$httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
|
||||||
|
$httpProvider.defaults.headers.get.Pragma = 'no-cache';
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpProvider.interceptors.push('apiInterceptor');
|
||||||
|
$httpProvider.interceptors.push('jwtInterceptor');
|
||||||
|
|
||||||
|
$urlRouterProvider.otherwise('/');
|
||||||
|
|
||||||
|
$stateProvider
|
||||||
|
// Backend
|
||||||
|
.state('backend', {
|
||||||
|
templateUrl: 'app/views/backendLayout.html',
|
||||||
|
abstract: true,
|
||||||
|
data: {
|
||||||
|
authorize: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('backend.user', {
|
||||||
|
templateUrl: 'app/views/userLayout.html',
|
||||||
|
abstract: true
|
||||||
|
})
|
||||||
|
.state('backend.user.vault', {
|
||||||
|
url: '^/vault',
|
||||||
|
templateUrl: 'app/vault/views/vault.html',
|
||||||
|
controller: 'vaultController',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'My Vault',
|
||||||
|
controlSidebar: true
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
refreshFromServer: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('backend.user.shared', {
|
||||||
|
url: '^/shared',
|
||||||
|
templateUrl: 'app/vault/views/vaultShared.html',
|
||||||
|
controller: 'vaultSharedController',
|
||||||
|
data: { pageTitle: 'Shared' }
|
||||||
|
})
|
||||||
|
.state('backend.user.settings', {
|
||||||
|
url: '^/settings',
|
||||||
|
templateUrl: 'app/settings/views/settings.html',
|
||||||
|
controller: 'settingsController',
|
||||||
|
data: { pageTitle: 'Settings' }
|
||||||
|
})
|
||||||
|
.state('backend.user.settingsDomains', {
|
||||||
|
url: '^/settings/domains',
|
||||||
|
templateUrl: 'app/settings/views/settingsDomains.html',
|
||||||
|
controller: 'settingsDomainsController',
|
||||||
|
data: { pageTitle: 'Domain Settings' }
|
||||||
|
})
|
||||||
|
.state('backend.user.settingsTwoStep', {
|
||||||
|
url: '^/settings/two-step',
|
||||||
|
templateUrl: 'app/settings/views/settingsTwoStep.html',
|
||||||
|
controller: 'settingsTwoStepController',
|
||||||
|
data: { pageTitle: 'Two-step Login' }
|
||||||
|
})
|
||||||
|
.state('backend.user.settingsCreateOrg', {
|
||||||
|
url: '^/settings/create-organization',
|
||||||
|
templateUrl: 'app/settings/views/settingsCreateOrganization.html',
|
||||||
|
controller: 'settingsCreateOrganizationController',
|
||||||
|
data: { pageTitle: 'Create Organization' }
|
||||||
|
})
|
||||||
|
.state('backend.user.settingsBilling', {
|
||||||
|
url: '^/settings/billing',
|
||||||
|
templateUrl: 'app/settings/views/settingsBilling.html',
|
||||||
|
controller: 'settingsBillingController',
|
||||||
|
data: { pageTitle: 'Billing' }
|
||||||
|
})
|
||||||
|
.state('backend.user.settingsPremium', {
|
||||||
|
url: '^/settings/premium',
|
||||||
|
templateUrl: 'app/settings/views/settingsPremium.html',
|
||||||
|
controller: 'settingsPremiumController',
|
||||||
|
data: { pageTitle: 'Go Premium' }
|
||||||
|
})
|
||||||
|
.state('backend.user.tools', {
|
||||||
|
url: '^/tools',
|
||||||
|
templateUrl: 'app/tools/views/tools.html',
|
||||||
|
controller: 'toolsController',
|
||||||
|
data: { pageTitle: 'Tools' }
|
||||||
|
})
|
||||||
|
.state('backend.user.reportsBreach', {
|
||||||
|
url: '^/reports/breach',
|
||||||
|
templateUrl: 'app/reports/views/reportsBreach.html',
|
||||||
|
controller: 'reportsBreachController',
|
||||||
|
data: { pageTitle: 'Data Breach Report' }
|
||||||
|
})
|
||||||
|
.state('backend.user.apps', {
|
||||||
|
url: '^/apps',
|
||||||
|
templateUrl: 'app/views/apps.html',
|
||||||
|
controller: 'appsController',
|
||||||
|
data: { pageTitle: 'Get the Apps' }
|
||||||
|
})
|
||||||
|
.state('backend.org', {
|
||||||
|
templateUrl: 'app/views/organizationLayout.html',
|
||||||
|
abstract: true
|
||||||
|
})
|
||||||
|
.state('backend.org.dashboard', {
|
||||||
|
url: '^/organization/:orgId',
|
||||||
|
templateUrl: 'app/organization/views/organizationDashboard.html',
|
||||||
|
controller: 'organizationDashboardController',
|
||||||
|
data: { pageTitle: 'Organization Dashboard' }
|
||||||
|
})
|
||||||
|
.state('backend.org.people', {
|
||||||
|
url: '/organization/:orgId/people',
|
||||||
|
templateUrl: 'app/organization/views/organizationPeople.html',
|
||||||
|
controller: 'organizationPeopleController',
|
||||||
|
data: { pageTitle: 'Organization People' }
|
||||||
|
})
|
||||||
|
.state('backend.org.collections', {
|
||||||
|
url: '/organization/:orgId/collections',
|
||||||
|
templateUrl: 'app/organization/views/organizationCollections.html',
|
||||||
|
controller: 'organizationCollectionsController',
|
||||||
|
data: { pageTitle: 'Organization Collections' }
|
||||||
|
})
|
||||||
|
.state('backend.org.settings', {
|
||||||
|
url: '/organization/:orgId/settings',
|
||||||
|
templateUrl: 'app/organization/views/organizationSettings.html',
|
||||||
|
controller: 'organizationSettingsController',
|
||||||
|
data: { pageTitle: 'Organization Settings' }
|
||||||
|
})
|
||||||
|
.state('backend.org.billing', {
|
||||||
|
url: '/organization/:orgId/billing',
|
||||||
|
templateUrl: 'app/organization/views/organizationBilling.html',
|
||||||
|
controller: 'organizationBillingController',
|
||||||
|
data: { pageTitle: 'Organization Billing' }
|
||||||
|
})
|
||||||
|
.state('backend.org.vault', {
|
||||||
|
url: '/organization/:orgId/vault',
|
||||||
|
templateUrl: 'app/organization/views/organizationVault.html',
|
||||||
|
controller: 'organizationVaultController',
|
||||||
|
data: { pageTitle: 'Organization Vault' }
|
||||||
|
})
|
||||||
|
.state('backend.org.groups', {
|
||||||
|
url: '/organization/:orgId/groups',
|
||||||
|
templateUrl: 'app/organization/views/organizationGroups.html',
|
||||||
|
controller: 'organizationGroupsController',
|
||||||
|
data: { pageTitle: 'Organization Groups' }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Frontend
|
||||||
|
.state('frontend', {
|
||||||
|
templateUrl: 'app/views/frontendLayout.html',
|
||||||
|
abstract: true,
|
||||||
|
data: {
|
||||||
|
authorize: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.login', {
|
||||||
|
templateUrl: 'app/accounts/views/accountsLogin.html',
|
||||||
|
controller: 'accountsLoginController',
|
||||||
|
params: {
|
||||||
|
returnState: null,
|
||||||
|
email: null,
|
||||||
|
premium: null,
|
||||||
|
org: null
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
bodyClass: 'login-page'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.login.info', {
|
||||||
|
url: '^/?org&premium&email',
|
||||||
|
templateUrl: 'app/accounts/views/accountsLoginInfo.html',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Log In'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.login.twoFactor', {
|
||||||
|
url: '^/two-step?org&premium&email',
|
||||||
|
templateUrl: 'app/accounts/views/accountsLoginTwoFactor.html',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Log In (Two-step)'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.logout', {
|
||||||
|
url: '^/logout',
|
||||||
|
controller: 'accountsLogoutController',
|
||||||
|
data: {
|
||||||
|
authorize: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.passwordHint', {
|
||||||
|
url: '^/password-hint',
|
||||||
|
templateUrl: 'app/accounts/views/accountsPasswordHint.html',
|
||||||
|
controller: 'accountsPasswordHintController',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Master Password Hint',
|
||||||
|
bodyClass: 'login-page'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.recover', {
|
||||||
|
url: '^/recover',
|
||||||
|
templateUrl: 'app/accounts/views/accountsRecover.html',
|
||||||
|
controller: 'accountsRecoverController',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Recover Account',
|
||||||
|
bodyClass: 'login-page'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.recover-delete', {
|
||||||
|
url: '^/recover-delete',
|
||||||
|
templateUrl: 'app/accounts/views/accountsRecoverDelete.html',
|
||||||
|
controller: 'accountsRecoverDeleteController',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Delete Account',
|
||||||
|
bodyClass: 'login-page'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.verify-recover-delete', {
|
||||||
|
url: '^/verify-recover-delete?userId&token&email',
|
||||||
|
templateUrl: 'app/accounts/views/accountsVerifyRecoverDelete.html',
|
||||||
|
controller: 'accountsVerifyRecoverDeleteController',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Confirm Delete Account',
|
||||||
|
bodyClass: 'login-page'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.register', {
|
||||||
|
url: '^/register?org&premium',
|
||||||
|
templateUrl: 'app/accounts/views/accountsRegister.html',
|
||||||
|
controller: 'accountsRegisterController',
|
||||||
|
params: {
|
||||||
|
returnState: null,
|
||||||
|
email: null,
|
||||||
|
org: null,
|
||||||
|
premium: null
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Register',
|
||||||
|
bodyClass: 'register-page'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.organizationAccept', {
|
||||||
|
url: '^/accept-organization?organizationId&organizationUserId&token&email&organizationName',
|
||||||
|
templateUrl: 'app/accounts/views/accountsOrganizationAccept.html',
|
||||||
|
controller: 'accountsOrganizationAcceptController',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Accept Organization Invite',
|
||||||
|
bodyClass: 'login-page',
|
||||||
|
skipAuthorize: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.verifyEmail', {
|
||||||
|
url: '^/verify-email?userId&token',
|
||||||
|
templateUrl: 'app/accounts/views/accountsVerifyEmail.html',
|
||||||
|
controller: 'accountsVerifyEmailController',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Verifying Email',
|
||||||
|
bodyClass: 'login-page',
|
||||||
|
skipAuthorize: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.run(function ($rootScope, authService, $state) {
|
||||||
|
$rootScope.$on('$stateChangeSuccess', function () {
|
||||||
|
$('html, body').animate({ scrollTop: 0 }, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
|
||||||
|
if (!toState.data || !toState.data.authorize) {
|
||||||
|
if (toState.data && toState.data.skipAuthorize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authService.isAuthenticated()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
$state.go('backend.user.vault');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authService.isAuthenticated()) {
|
||||||
|
event.preventDefault();
|
||||||
|
authService.logOut();
|
||||||
|
$state.go('frontend.login.info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// user is guaranteed to be authenticated becuase of previous check
|
||||||
|
if (toState.name.indexOf('backend.org.') > -1 && toParams.orgId) {
|
||||||
|
// clear vault rootScope when visiting org admin section
|
||||||
|
$rootScope.vaultLogins = $rootScope.vaultFolders = null;
|
||||||
|
|
||||||
|
authService.getUserProfile().then(function (profile) {
|
||||||
|
var orgs = profile.organizations;
|
||||||
|
if (!orgs || !(toParams.orgId in orgs) || orgs[toParams.orgId].status !== 2 ||
|
||||||
|
orgs[toParams.orgId].type === 2) {
|
||||||
|
event.preventDefault();
|
||||||
|
$state.go('backend.user.vault');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
138
src/app/constants.js
Normal file
138
src/app/constants.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
angular.module('bit')
|
||||||
|
.constant('constants', {
|
||||||
|
rememberedEmailCookieName: 'bit.rememberedEmail',
|
||||||
|
encType: {
|
||||||
|
AesCbc256_B64: 0,
|
||||||
|
AesCbc128_HmacSha256_B64: 1,
|
||||||
|
AesCbc256_HmacSha256_B64: 2,
|
||||||
|
Rsa2048_OaepSha256_B64: 3,
|
||||||
|
Rsa2048_OaepSha1_B64: 4,
|
||||||
|
Rsa2048_OaepSha256_HmacSha256_B64: 5,
|
||||||
|
Rsa2048_OaepSha1_HmacSha256_B64: 6
|
||||||
|
},
|
||||||
|
orgUserType: {
|
||||||
|
owner: 0,
|
||||||
|
admin: 1,
|
||||||
|
user: 2
|
||||||
|
},
|
||||||
|
orgUserStatus: {
|
||||||
|
invited: 0,
|
||||||
|
accepted: 1,
|
||||||
|
confirmed: 2
|
||||||
|
},
|
||||||
|
twoFactorProvider: {
|
||||||
|
u2f: 4,
|
||||||
|
yubikey: 3,
|
||||||
|
duo: 2,
|
||||||
|
authenticator: 0,
|
||||||
|
email: 1,
|
||||||
|
remember: 5
|
||||||
|
},
|
||||||
|
twoFactorProviderInfo: [
|
||||||
|
{
|
||||||
|
type: 0,
|
||||||
|
name: 'Authenticator App',
|
||||||
|
description: 'Use an authenticator app (such as Authy or Google Authenticator) to generate time-based ' +
|
||||||
|
'verification codes.',
|
||||||
|
enabled: false,
|
||||||
|
active: true,
|
||||||
|
free: true,
|
||||||
|
image: 'authapp.png',
|
||||||
|
displayOrder: 0,
|
||||||
|
priority: 1,
|
||||||
|
requiresUsb: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 3,
|
||||||
|
name: 'YubiKey OTP Security Key',
|
||||||
|
description: 'Use a YubiKey to access your account. Works with YubiKey 4, 4 Nano, 4C, and NEO devices.',
|
||||||
|
enabled: false,
|
||||||
|
active: true,
|
||||||
|
image: 'yubico.png',
|
||||||
|
displayOrder: 1,
|
||||||
|
priority: 3,
|
||||||
|
requiresUsb: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 2,
|
||||||
|
name: 'Duo',
|
||||||
|
description: 'Verify with Duo Security using the Duo Mobile app, SMS, phone call, or U2F security key.',
|
||||||
|
enabled: false,
|
||||||
|
active: true,
|
||||||
|
image: 'duo.png',
|
||||||
|
displayOrder: 2,
|
||||||
|
priority: 2,
|
||||||
|
requiresUsb: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 4,
|
||||||
|
name: 'FIDO U2F Security Key',
|
||||||
|
description: 'Use any FIDO U2F enabled security key to access your account.',
|
||||||
|
enabled: false,
|
||||||
|
active: true,
|
||||||
|
image: 'fido.png',
|
||||||
|
displayOrder: 3,
|
||||||
|
priority: 4,
|
||||||
|
requiresUsb: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 1,
|
||||||
|
name: 'Email',
|
||||||
|
description: 'Verification codes will be emailed to you.',
|
||||||
|
enabled: false,
|
||||||
|
active: true,
|
||||||
|
free: true,
|
||||||
|
image: 'gmail.png',
|
||||||
|
displayOrder: 4,
|
||||||
|
priority: 0,
|
||||||
|
requiresUsb: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
plans: {
|
||||||
|
free: {
|
||||||
|
basePrice: 0,
|
||||||
|
noAdditionalSeats: true,
|
||||||
|
noPayment: true,
|
||||||
|
upgradeSortOrder: -1
|
||||||
|
},
|
||||||
|
personal: {
|
||||||
|
basePrice: 1,
|
||||||
|
annualBasePrice: 12,
|
||||||
|
baseSeats: 5,
|
||||||
|
seatPrice: 1,
|
||||||
|
annualSeatPrice: 12,
|
||||||
|
maxAdditionalSeats: 5,
|
||||||
|
annualPlanType: 'personalAnnually',
|
||||||
|
upgradeSortOrder: 1
|
||||||
|
},
|
||||||
|
teams: {
|
||||||
|
basePrice: 5,
|
||||||
|
annualBasePrice: 60,
|
||||||
|
monthlyBasePrice: 8,
|
||||||
|
baseSeats: 5,
|
||||||
|
seatPrice: 2,
|
||||||
|
annualSeatPrice: 24,
|
||||||
|
monthlySeatPrice: 2.5,
|
||||||
|
monthPlanType: 'teamsMonthly',
|
||||||
|
annualPlanType: 'teamsAnnually',
|
||||||
|
upgradeSortOrder: 2
|
||||||
|
},
|
||||||
|
enterprise: {
|
||||||
|
seatPrice: 3,
|
||||||
|
annualSeatPrice: 36,
|
||||||
|
monthlySeatPrice: 4,
|
||||||
|
monthPlanType: 'enterpriseMonthly',
|
||||||
|
annualPlanType: 'enterpriseAnnually',
|
||||||
|
upgradeSortOrder: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
storageGb: {
|
||||||
|
price: 0.33,
|
||||||
|
monthlyPrice: 0.50,
|
||||||
|
yearlyPrice: 4
|
||||||
|
},
|
||||||
|
premium: {
|
||||||
|
price: 10,
|
||||||
|
yearlyPrice: 10
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
angular
|
angular
|
||||||
.module('bit.directives')
|
.module('bit.directives')
|
||||||
|
|
||||||
.directive('apiForm', function ($rootScope, validationService) {
|
.directive('apiForm', function ($rootScope, validationService, $timeout) {
|
||||||
return {
|
return {
|
||||||
require: 'form',
|
require: 'form',
|
||||||
restrict: 'A',
|
restrict: 'A',
|
||||||
@@ -25,11 +25,21 @@ angular
|
|||||||
form.$loading = true;
|
form.$loading = true;
|
||||||
|
|
||||||
promise.then(function success(response) {
|
promise.then(function success(response) {
|
||||||
|
$timeout(function () {
|
||||||
form.$loading = false;
|
form.$loading = false;
|
||||||
|
});
|
||||||
}, function failure(reason) {
|
}, function failure(reason) {
|
||||||
|
$timeout(function () {
|
||||||
form.$loading = false;
|
form.$loading = false;
|
||||||
|
if (typeof reason === 'string') {
|
||||||
|
validationService.addError(form, null, reason, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
validationService.addErrors(form, reason);
|
validationService.addErrors(form, reason);
|
||||||
|
}
|
||||||
scope.$broadcast('show-errors-check-validity');
|
scope.$broadcast('show-errors-check-validity');
|
||||||
|
$('html, body').animate({ scrollTop: 0 }, 200);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
151
src/app/directives/letterAvatarDirective.js
Normal file
151
src/app/directives/letterAvatarDirective.js
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.directives')
|
||||||
|
|
||||||
|
// adaptation of https://github.com/uttesh/ngletteravatar
|
||||||
|
.directive('letterAvatar', function () {
|
||||||
|
// ref: http://stackoverflow.com/a/16348977/1090359
|
||||||
|
function stringToColor(str) {
|
||||||
|
var hash = 0,
|
||||||
|
i = 0;
|
||||||
|
for (i = 0; i < str.length; i++) {
|
||||||
|
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
var color = '#';
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
var value = (hash >> (i * 8)) & 0xFF;
|
||||||
|
color += ('00' + value.toString(16)).substr(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFirstLetters(data, count) {
|
||||||
|
var parts = data.split(' ');
|
||||||
|
if (parts && parts.length > 1) {
|
||||||
|
var text = '';
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
text += parts[i].substr(0, 1);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSvg(width, height, color) {
|
||||||
|
var svgTag = angular.element('<svg></svg>')
|
||||||
|
.attr({
|
||||||
|
'xmlns': 'http://www.w3.org/2000/svg',
|
||||||
|
'pointer-events': 'none',
|
||||||
|
'width': width,
|
||||||
|
'height': height
|
||||||
|
})
|
||||||
|
.css({
|
||||||
|
'background-color': color,
|
||||||
|
'width': width + 'px',
|
||||||
|
'height': height + 'px'
|
||||||
|
});
|
||||||
|
|
||||||
|
return svgTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCharText(character, textColor, fontFamily, fontWeight, fontsize) {
|
||||||
|
var textTag = angular.element('<text text-anchor="middle"></text>')
|
||||||
|
.attr({
|
||||||
|
'y': '50%',
|
||||||
|
'x': '50%',
|
||||||
|
'dy': '0.35em',
|
||||||
|
'pointer-events': 'auto',
|
||||||
|
'fill': textColor,
|
||||||
|
'font-family': fontFamily
|
||||||
|
})
|
||||||
|
.text(character)
|
||||||
|
.css({
|
||||||
|
'font-weight': fontWeight,
|
||||||
|
'font-size': fontsize + 'px',
|
||||||
|
});
|
||||||
|
|
||||||
|
return textTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
restrict: 'AE',
|
||||||
|
replace: true,
|
||||||
|
scope: {
|
||||||
|
data: '@'
|
||||||
|
},
|
||||||
|
link: function (scope, element, attrs) {
|
||||||
|
var params = {
|
||||||
|
charCount: attrs.charcount || 2,
|
||||||
|
data: attrs.data,
|
||||||
|
textColor: attrs.textcolor || '#ffffff',
|
||||||
|
bgColor: attrs.bgcolor,
|
||||||
|
height: attrs.avheight || 45,
|
||||||
|
width: attrs.avwidth || 45,
|
||||||
|
fontSize: attrs.fontsize || 20,
|
||||||
|
fontWeight: attrs.fontweight || 300,
|
||||||
|
fontFamily: attrs.fontfamily || 'Open Sans, HelveticaNeue-Light, Helvetica Neue Light, ' +
|
||||||
|
'Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif',
|
||||||
|
round: attrs.round || 'true',
|
||||||
|
dynamic: attrs.dynamic || 'true',
|
||||||
|
class: attrs.avclass || '',
|
||||||
|
border: attrs.avborder || 'false',
|
||||||
|
borderStyle: attrs.borderstyle || '3px solid white'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.dynamic === 'true') {
|
||||||
|
scope.$watch('data', function () {
|
||||||
|
generateLetterAvatar();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
generateLetterAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateLetterAvatar() {
|
||||||
|
var c = null,
|
||||||
|
upperData = scope.data.toUpperCase();
|
||||||
|
|
||||||
|
if (params.charCount > 1) {
|
||||||
|
c = getFirstLetters(upperData, params.charCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!c) {
|
||||||
|
c = upperData.substr(0, params.charCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cobj = getCharText(c, params.textColor, params.fontFamily, params.fontWeight, params.fontSize);
|
||||||
|
var color = params.bgColor ? params.bgColor : stringToColor(upperData);
|
||||||
|
var svg = getSvg(params.width, params.height, color);
|
||||||
|
svg.append(cobj);
|
||||||
|
var lvcomponent = angular.element('<div>').append(svg).html();
|
||||||
|
|
||||||
|
var svgHtml = window.btoa(unescape(encodeURIComponent(lvcomponent)));
|
||||||
|
var src = 'data:image/svg+xml;base64,' + svgHtml;
|
||||||
|
|
||||||
|
var img = angular.element('<img>').attr({ src: src, title: scope.data });
|
||||||
|
|
||||||
|
if (params.round === 'true') {
|
||||||
|
img.css('border-radius', '50%');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.border === 'true') {
|
||||||
|
img.css('border', params.borderStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.class) {
|
||||||
|
img.addClass(params.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.dynamic === 'true') {
|
||||||
|
element.empty();
|
||||||
|
element.append(img);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
element.replaceWith(img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user