mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Compare commits
2595 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02fe715903 | ||
|
|
fb35805202 | ||
|
|
b6b7298980 | ||
|
|
52406bc969 | ||
|
|
d7dd2435fc | ||
|
|
ae3788d42b | ||
|
|
0e62e2ec2b | ||
|
|
95d177da38 | ||
|
|
ad0512e344 | ||
|
|
8fe9504cd1 | ||
|
|
b8aa25b981 | ||
|
|
5d09ddbc8d | ||
|
|
bde9a28f2b | ||
|
|
dfb03a53c0 | ||
|
|
b98b391283 | ||
|
|
f195aee90c | ||
|
|
eefcda7e41 | ||
|
|
42cd171685 | ||
|
|
8ef27713f1 | ||
|
|
57b647bde5 | ||
|
|
681ace4b1b | ||
|
|
58d9ac5ebc | ||
|
|
eab478da0c | ||
|
|
5a78853de5 | ||
|
|
b4ddce1da2 | ||
|
|
6ee47f0057 | ||
|
|
7b55c8ad1a | ||
|
|
30057d2ac4 | ||
|
|
6f7b712bc7 | ||
|
|
45da771404 | ||
|
|
902c620eb6 | ||
|
|
ca35ccbd35 | ||
|
|
85aa4274f3 | ||
|
|
ffb63a1cc7 | ||
|
|
3501be9484 | ||
|
|
5d1522b77a | ||
|
|
be30d47038 | ||
|
|
888892b3e7 | ||
|
|
f4f3e8c574 | ||
|
|
3367736c7e | ||
|
|
e3e7fce70a | ||
|
|
74bdfe2602 | ||
|
|
9e01d47a3f | ||
|
|
67de7d4bfb | ||
|
|
f5245a280e | ||
|
|
1dc9502676 | ||
|
|
ccf0d64a7b | ||
|
|
dc2078ae58 | ||
|
|
da470ad709 | ||
|
|
9f977cfc68 | ||
|
|
9627782a04 | ||
|
|
c5877cd063 | ||
|
|
136e8897ae | ||
|
|
81c6a4b1df | ||
|
|
da62cec6f0 | ||
|
|
474df5ba5e | ||
|
|
2c609fc6fd | ||
|
|
f8a2fae82b | ||
|
|
2f04c07262 | ||
|
|
f81195c920 | ||
|
|
d031b53c74 | ||
|
|
468007a984 | ||
|
|
bc054236ad | ||
|
|
1c31d090a3 | ||
|
|
f8d942c02c | ||
|
|
248938ca00 | ||
|
|
06d95bb224 | ||
|
|
446f2027b4 | ||
|
|
1f0d496f21 | ||
|
|
2b03162bfd | ||
|
|
f586359610 | ||
|
|
96641cf195 | ||
|
|
572758c598 | ||
|
|
df7db8ad07 | ||
|
|
0439d37c14 | ||
|
|
97f38aa654 | ||
|
|
0444b78ad1 | ||
|
|
705251fbe2 | ||
|
|
27853481d8 | ||
|
|
c0511f25ca | ||
|
|
1be62ac222 | ||
|
|
8304104a7a | ||
|
|
56808a7dbb | ||
|
|
609c13faf4 | ||
|
|
62b20a5c6d | ||
|
|
d56bf1211e | ||
|
|
8831f96fc2 | ||
|
|
f26dc27515 | ||
|
|
cb8a40d9cd | ||
|
|
2652a2deae | ||
|
|
e1c0c9f009 | ||
|
|
612442c1bb | ||
|
|
23b02a770a | ||
|
|
42ececbcf5 | ||
|
|
11034de7d1 | ||
|
|
571aaf31c4 | ||
|
|
0884e2d761 | ||
|
|
00975e6896 | ||
|
|
2c43249e98 | ||
|
|
575847f252 | ||
|
|
d6c181c997 | ||
|
|
9bb004923c | ||
|
|
e08726463e | ||
|
|
fdf93b610c | ||
|
|
144038ed1c | ||
|
|
5cb5e37270 | ||
|
|
e266a740ba | ||
|
|
3b0fc94239 | ||
|
|
32e27b5f08 | ||
|
|
317c40386f | ||
|
|
c9eeca7def | ||
|
|
902c568c09 | ||
|
|
153870693b | ||
|
|
a8cd2a6cf7 | ||
|
|
7404da9b3c | ||
|
|
9b40ce1024 | ||
|
|
80ffa965e1 | ||
|
|
57f1a5e380 | ||
|
|
18f1929f65 | ||
|
|
5cb3941190 | ||
|
|
0e515bc6c1 | ||
|
|
e103ddf02f | ||
|
|
8242989b9d | ||
|
|
5e7d94efb8 | ||
|
|
3bc8955dd5 | ||
|
|
bc05d27082 | ||
|
|
e93c155885 | ||
|
|
1076749635 | ||
|
|
06e1af6d48 | ||
|
|
cf9a90d10e | ||
|
|
6e8c15bccd | ||
|
|
7d018e4b59 | ||
|
|
f832cb4138 | ||
|
|
b8a23cf014 | ||
|
|
d0c0e80b6c | ||
|
|
98fb71fcb6 | ||
|
|
1b52b5a98a | ||
|
|
c3e5c74253 | ||
|
|
df5b175cdf | ||
|
|
1c495e87c9 | ||
|
|
01f128a4a9 | ||
|
|
a4d5b145ac | ||
|
|
d944e0e25c | ||
|
|
d141ccca52 | ||
|
|
9e872bed2c | ||
|
|
c071b692f2 | ||
|
|
041bb1bf0a | ||
|
|
0b5e1eb256 | ||
|
|
8c39fdb21e | ||
|
|
ca3efc8fee | ||
|
|
c323f38f16 | ||
|
|
9df4eb4c0d | ||
|
|
1712ed53be | ||
|
|
45a39f6200 | ||
|
|
a2d241263b | ||
|
|
5987d3deda | ||
|
|
080a3c655e | ||
|
|
dac48242b7 | ||
|
|
e4d9ab52a0 | ||
|
|
aee8a2661e | ||
|
|
ff6bb236c0 | ||
|
|
f79b20294a | ||
|
|
3a0c34b934 | ||
|
|
e09df347f4 | ||
|
|
e68ab0031d | ||
|
|
64416c9406 | ||
|
|
6779adb064 | ||
|
|
1b28a4b954 | ||
|
|
6320498fb3 | ||
|
|
bfd5f3e564 | ||
|
|
c755443735 | ||
|
|
0e5f2530a9 | ||
|
|
5105633fa4 | ||
|
|
e975056c21 | ||
|
|
be21167ef8 | ||
|
|
e09898e4d8 | ||
|
|
868d235faa | ||
|
|
5c764a95f4 | ||
|
|
596c3e86e9 | ||
|
|
8030da2ed5 | ||
|
|
8910430dfb | ||
|
|
6bf6d4b47f | ||
|
|
ca199a398e | ||
|
|
61ab2fbda3 | ||
|
|
d79f074825 | ||
|
|
e3b962a779 | ||
|
|
cc657eb853 | ||
|
|
e14a266ee0 | ||
|
|
e1732cfa10 | ||
|
|
ce1ae208d1 | ||
|
|
6996b06fa2 | ||
|
|
dc503d3461 | ||
|
|
d95db8fb74 | ||
|
|
1a219daa12 | ||
|
|
2ae98887b7 | ||
|
|
f0c47252e4 | ||
|
|
2ffe3bd6ad | ||
|
|
f387a4d469 | ||
|
|
a0f1b4dd0d | ||
|
|
84a65edc08 | ||
|
|
caad11c571 | ||
|
|
b73449159d | ||
|
|
bf48434d0f | ||
|
|
b6d2d5bf71 | ||
|
|
dfd62c7c3a | ||
|
|
41d3bd8cf2 | ||
|
|
3292d119fe | ||
|
|
b8de92435b | ||
|
|
fd1d512a0f | ||
|
|
14b8903d9a | ||
|
|
45284eefb3 | ||
|
|
49f6cfab7f | ||
|
|
2d271460e3 | ||
|
|
241004f13b | ||
|
|
2f5d0201fe | ||
|
|
7ffb5db310 | ||
|
|
6603521d88 | ||
|
|
d066e0586a | ||
|
|
d0e661b84b | ||
|
|
6fa77cef88 | ||
|
|
6f408b871f | ||
|
|
8a9b992757 | ||
|
|
55ecc4b804 | ||
|
|
a71ce448f4 | ||
|
|
bc82ae961e | ||
|
|
ebcfdcd8a4 | ||
|
|
8991dcbf32 | ||
|
|
cc9b9c91d7 | ||
|
|
3880d60101 | ||
|
|
f5fdb34f7d | ||
|
|
5b8f2034c3 | ||
|
|
56477eb39c | ||
|
|
2b0a9d995e | ||
|
|
595722dfa1 | ||
|
|
6a1e683a93 | ||
|
|
97ca771a00 | ||
|
|
214f82e142 | ||
|
|
17ae5ee57c | ||
|
|
71075cf878 | ||
|
|
56e2c86a7f | ||
|
|
8fba2a693e | ||
|
|
f582d3e7a6 | ||
|
|
75984a2e37 | ||
|
|
1cba6dc3b9 | ||
|
|
a803d58c52 | ||
|
|
d5c0783619 | ||
|
|
35a7d6434a | ||
|
|
78942cabf2 | ||
|
|
d9231ae3f3 | ||
|
|
bca7c14319 | ||
|
|
221931ecaa | ||
|
|
4b856d9016 | ||
|
|
4029554658 | ||
|
|
6ec22a9408 | ||
|
|
9cc7dfb884 | ||
|
|
dca12def8d | ||
|
|
cbf65c5f42 | ||
|
|
f8c943c042 | ||
|
|
346052922e | ||
|
|
2973d06c9f | ||
|
|
0490314cff | ||
|
|
a6abb74810 | ||
|
|
0ce00a15e7 | ||
|
|
cd90949d27 | ||
|
|
0d0eb609d3 | ||
|
|
7c902e61d6 | ||
|
|
1e5c2c35e5 | ||
|
|
977fdef787 | ||
|
|
d6c419bad8 | ||
|
|
f740d8b057 | ||
|
|
8889722388 | ||
|
|
01503f137d | ||
|
|
6171aa89a8 | ||
|
|
40c37143e0 | ||
|
|
57031e7752 | ||
|
|
db5a8df64e | ||
|
|
e5eb5d61fe | ||
|
|
9061af54bf | ||
|
|
83fed7d66f | ||
|
|
f8aea1e861 | ||
|
|
5b6fb16591 | ||
|
|
278cf2ca40 | ||
|
|
fe15de02e5 | ||
|
|
b164a39abc | ||
|
|
e5f77e2c4e | ||
|
|
cf460096af | ||
|
|
1403ecfa6f | ||
|
|
8b60d50050 | ||
|
|
cf5823fe71 | ||
|
|
bb0b5f2d87 | ||
|
|
2700caf2a8 | ||
|
|
523b18156c | ||
|
|
7219b394a0 | ||
|
|
383c29c761 | ||
|
|
b5231425fb | ||
|
|
7cb48e3a81 | ||
|
|
664d10cd06 | ||
|
|
a6a34788a8 | ||
|
|
381ec7af67 | ||
|
|
8be377c7f8 | ||
|
|
c46ca2f9e2 | ||
|
|
6d4f163824 | ||
|
|
6c581b3ebc | ||
|
|
618f950cae | ||
|
|
9dd859af7a | ||
|
|
044ac513ae | ||
|
|
4447b89b05 | ||
|
|
1de569e64d | ||
|
|
3ee61fef96 | ||
|
|
f63b395736 | ||
|
|
ee3c3294f3 | ||
|
|
a7a3381124 | ||
|
|
98bd41d4b1 | ||
|
|
356262975c | ||
|
|
a35024e61d | ||
|
|
df9733081b | ||
|
|
db9ab9f51e | ||
|
|
1b8f316066 | ||
|
|
c3a910e785 | ||
|
|
4b4b5910e3 | ||
|
|
471490f14f | ||
|
|
009e125afd | ||
|
|
c682f460b2 | ||
|
|
fa6f33cbc5 | ||
|
|
ae7493efcf | ||
|
|
fc7a7281fe | ||
|
|
7b21e380cb | ||
|
|
2e4c6b7828 | ||
|
|
d4b13c461d | ||
|
|
37752b566b | ||
|
|
3eda0aa2cd | ||
|
|
4ff38c7148 | ||
|
|
998d36a5d1 | ||
|
|
7a43510cf5 | ||
|
|
0c02cfea2f | ||
|
|
aa58749b34 | ||
|
|
c98a189430 | ||
|
|
1df2225a52 | ||
|
|
f8b0c2ffe4 | ||
|
|
ce3311a0dc | ||
|
|
15ea87d6b6 | ||
|
|
0481bf07e2 | ||
|
|
7d01ad4e20 | ||
|
|
9db6f0bfc2 | ||
|
|
ab0ce71db8 | ||
|
|
582ddc041b | ||
|
|
f1e0f70375 | ||
|
|
eaba23d4ba | ||
|
|
ebb945a0c4 | ||
|
|
7daba63c56 | ||
|
|
30d2aeb6a3 | ||
|
|
c82d1b3c50 | ||
|
|
8180aaa4cc | ||
|
|
a1c1fea976 | ||
|
|
17166dad4d | ||
|
|
7f76084109 | ||
|
|
fb89421b09 | ||
|
|
9972c8ac61 | ||
|
|
7e95476dce | ||
|
|
ded636ba0c | ||
|
|
9269774aed | ||
|
|
dd47eed7c7 | ||
|
|
f584950dda | ||
|
|
3a25b1fb20 | ||
|
|
9832deb20c | ||
|
|
ca00fda023 | ||
|
|
bc73452400 | ||
|
|
cc359e905b | ||
|
|
7fd9427801 | ||
|
|
6878794bd0 | ||
|
|
e69e85d8b3 | ||
|
|
2235664bed | ||
|
|
f08b6e7975 | ||
|
|
2e868c8111 | ||
|
|
1c3488a8db | ||
|
|
9c187e9430 | ||
|
|
b9d0226ede | ||
|
|
bb30f3b7c3 | ||
|
|
fa4e5250b9 | ||
|
|
7c8e95d408 | ||
|
|
ccdf05a635 | ||
|
|
66bd8be2c9 | ||
|
|
2cbe023a38 | ||
|
|
8a259516df | ||
|
|
9bb252f954 | ||
|
|
26cc36a91e | ||
|
|
f9e375f5ad | ||
|
|
c7de347cec | ||
|
|
f2e591086e | ||
|
|
361022fc26 | ||
|
|
d8a684da92 | ||
|
|
c1cdd8a843 | ||
|
|
4e134823df | ||
|
|
cdab6e7091 | ||
|
|
a7153d183b | ||
|
|
bbdddcef6e | ||
|
|
55b27d4607 | ||
|
|
b47835df68 | ||
|
|
919af717b9 | ||
|
|
b9b20bc36b | ||
|
|
709d187498 | ||
|
|
392e29a371 | ||
|
|
77a401a931 | ||
|
|
f0678418d5 | ||
|
|
3857ee2370 | ||
|
|
9bdda9cc4c | ||
|
|
2b0d3f9e0d | ||
|
|
75b0b7a1e1 | ||
|
|
ebbdea8f88 | ||
|
|
7844c23fba | ||
|
|
a34844511c | ||
|
|
44f624e9a6 | ||
|
|
2c1b06a5d8 | ||
|
|
a23c2523d2 | ||
|
|
f6df9983a3 | ||
|
|
8ebefb9a2e | ||
|
|
9fabdbd0a1 | ||
|
|
18aeeef6fe | ||
|
|
aafcc88162 | ||
|
|
5a3bd22132 | ||
|
|
4119713ace | ||
|
|
851a53ec31 | ||
|
|
686c0e854f | ||
|
|
a73cbbb672 | ||
|
|
2b5f61cadd | ||
|
|
35d6a28c94 | ||
|
|
d31150c7a1 | ||
|
|
218259fc7e | ||
|
|
c608a489dd | ||
|
|
a94faf06a9 | ||
|
|
ebe08535e0 | ||
|
|
5e7e035eec | ||
|
|
d85ee9780f | ||
|
|
87d37dd29e | ||
|
|
ba6d61a510 | ||
|
|
623e485641 | ||
|
|
86e6c31820 | ||
|
|
731e191011 | ||
|
|
cc2b717d6b | ||
|
|
41ad5aa26d | ||
|
|
7c9ebed93f | ||
|
|
e1e2edf2e9 | ||
|
|
aa633480a0 | ||
|
|
324b74d5a6 | ||
|
|
c494cab234 | ||
|
|
1320a7c8cb | ||
|
|
e7f3d977c4 | ||
|
|
b304783a7f | ||
|
|
ae091bd738 | ||
|
|
81dfcca06c | ||
|
|
1d22cd05f7 | ||
|
|
5939898b75 | ||
|
|
7b17ba1dee | ||
|
|
2656fb8db3 | ||
|
|
4b85e4b765 | ||
|
|
57f952bc66 | ||
|
|
ef331aba0b | ||
|
|
d510b35c06 | ||
|
|
f74c296ad5 | ||
|
|
f8a7439675 | ||
|
|
d6d71c94f5 | ||
|
|
34cb26416c | ||
|
|
988f4168a0 | ||
|
|
76210c30a8 | ||
|
|
c1a7b85f8b | ||
|
|
44bf90cf6a | ||
|
|
7c64c812fc | ||
|
|
f2c9c9ebbb | ||
|
|
14e9784297 | ||
|
|
b8aff13ce2 | ||
|
|
3e2e7b8622 | ||
|
|
c3f128182c | ||
|
|
6d54740aaf | ||
|
|
c198ec32bb | ||
|
|
5939d590e3 | ||
|
|
fd683e9d71 | ||
|
|
fd328eef2a | ||
|
|
b20206d350 | ||
|
|
82ec4b12f7 | ||
|
|
d6496d51d3 | ||
|
|
b12d0387f6 | ||
|
|
f15e78b91d | ||
|
|
c0f85366bd | ||
|
|
a554c0e660 | ||
|
|
2f8a721033 | ||
|
|
0a0e871696 | ||
|
|
cf24113924 | ||
|
|
1bacc8b774 | ||
|
|
65b52617a8 | ||
|
|
db3cf882d3 | ||
|
|
59f2b51d25 | ||
|
|
945e968e06 | ||
|
|
744e86601f | ||
|
|
91643d40bd | ||
|
|
9b7a1c7760 | ||
|
|
da0df3a73b | ||
|
|
6586af71f8 | ||
|
|
b3f5c72ba9 | ||
|
|
fdbce4d84d | ||
|
|
d31130b79f | ||
|
|
d566c963c1 | ||
|
|
1098adc03d | ||
|
|
e34e4728d0 | ||
|
|
35346613d8 | ||
|
|
0fd89e06c6 | ||
|
|
1c5ce23d35 | ||
|
|
45c31aa089 | ||
|
|
34be07c220 | ||
|
|
968a255269 | ||
|
|
a27be135da | ||
|
|
bb95eb84ea | ||
|
|
54cd5a68b3 | ||
|
|
9abdefa947 | ||
|
|
d9322c1307 | ||
|
|
a8d614628a | ||
|
|
7f9f6d3d0e | ||
|
|
4c1e36462c | ||
|
|
32d04106a1 | ||
|
|
a3506e833a | ||
|
|
51f3fee75d | ||
|
|
3ac2ce079a | ||
|
|
97e1c7a2ea | ||
|
|
29f741316c | ||
|
|
293ae12e33 | ||
|
|
49d1c135db | ||
|
|
d900d2d3f8 | ||
|
|
4a61f0ac04 | ||
|
|
b1635debcc | ||
|
|
b3a4f833a1 | ||
|
|
dd56c9bc87 | ||
|
|
19f92e74f5 | ||
|
|
d71d0d9af6 | ||
|
|
2392d34ed8 | ||
|
|
f6eec08b70 | ||
|
|
9547b72566 | ||
|
|
38097c40d8 | ||
|
|
66b7f4d344 | ||
|
|
a1b77dc9ef | ||
|
|
9b38095aba | ||
|
|
714a574028 | ||
|
|
3e8194a3f7 | ||
|
|
6e4782784c | ||
|
|
ad40c38ca3 | ||
|
|
68f2de171e | ||
|
|
a9ef011cf3 | ||
|
|
53bd9a3b14 | ||
|
|
1466933e2c | ||
|
|
be515dc6a6 | ||
|
|
83859230cd | ||
|
|
ec7a40df0b | ||
|
|
aba98ba944 | ||
|
|
f6e8c7152e | ||
|
|
3a1fd5ba83 | ||
|
|
e43f816a8d | ||
|
|
9e61dbd512 | ||
|
|
8734d028d3 | ||
|
|
58850821ba | ||
|
|
f81ad479dd | ||
|
|
133d30ba97 | ||
|
|
09fba343fc | ||
|
|
ba3d4a2390 | ||
|
|
b1c59f3dc1 | ||
|
|
89dc3b70e1 | ||
|
|
769c247832 | ||
|
|
12e4b614f5 | ||
|
|
b28eaa1aae | ||
|
|
cd20b1c102 | ||
|
|
d6f80378eb | ||
|
|
32e9124b9c | ||
|
|
0aee3b7370 | ||
|
|
6bb6a674ec | ||
|
|
29d7a5e37e | ||
|
|
6067c1610c | ||
|
|
1b74d22b46 | ||
|
|
85a973afd4 | ||
|
|
1ea8762eeb | ||
|
|
35ecbcc11a | ||
|
|
3e988a741b | ||
|
|
db8f13d92f | ||
|
|
1a5885d6b4 | ||
|
|
31d2a09416 | ||
|
|
8ae96a6f88 | ||
|
|
d8aae1358b | ||
|
|
ed53c3b8f6 | ||
|
|
79ffafcc17 | ||
|
|
bdf6dcd8cd | ||
|
|
ec3154ea46 | ||
|
|
08fc18192d | ||
|
|
b01c71f579 | ||
|
|
a6c98f462a | ||
|
|
473dd8739a | ||
|
|
722bcfc31b | ||
|
|
929c3d7662 | ||
|
|
e25a8e051a | ||
|
|
4a1b46dd41 | ||
|
|
16877521e7 | ||
|
|
a16abb94cd | ||
|
|
5c8e9a990c | ||
|
|
c2515ed3ae | ||
|
|
227f457409 | ||
|
|
2e4a3501a2 | ||
|
|
fade7f1713 | ||
|
|
de84468ad8 | ||
|
|
2e20978cee | ||
|
|
2cc24335ef | ||
|
|
721a9f5f69 | ||
|
|
4ebbefa181 | ||
|
|
6ad930c609 | ||
|
|
85856d8390 | ||
|
|
a975f6df2b | ||
|
|
d2f1e39a9b | ||
|
|
8ef7944077 | ||
|
|
2a19189f04 | ||
|
|
cb4f318419 | ||
|
|
f239b0cd34 | ||
|
|
9d1b2b9f60 | ||
|
|
168f9a5525 | ||
|
|
13a04976fd | ||
|
|
84d03158b5 | ||
|
|
af7e2edbf0 | ||
|
|
2e7b88f149 | ||
|
|
5010736ca3 | ||
|
|
986f27294a | ||
|
|
1b8cddede8 | ||
|
|
66c814296b | ||
|
|
b14cdfcc72 | ||
|
|
aba2c70ad7 | ||
|
|
46e9158323 | ||
|
|
8449cdca75 | ||
|
|
8c0bc023b7 | ||
|
|
bcd488bb87 | ||
|
|
a7b7c716d4 | ||
|
|
6b29bb8468 | ||
|
|
3ffc035db3 | ||
|
|
d93392ba8b | ||
|
|
137b3b3490 | ||
|
|
99c8082866 | ||
|
|
24af5aca55 | ||
|
|
1429cb3f76 | ||
|
|
13cbba3e99 | ||
|
|
73d24162a0 | ||
|
|
4964ebd31e | ||
|
|
5a8198a878 | ||
|
|
03aa806af6 | ||
|
|
023bf0474c | ||
|
|
2e22ca9216 | ||
|
|
5a540bba9e | ||
|
|
2047a6378b | ||
|
|
dc87510a7a | ||
|
|
c3f4c6c03b | ||
|
|
e8b72477c9 | ||
|
|
862874c2ae | ||
|
|
4d2d686078 | ||
|
|
6d458646fa | ||
|
|
a1345488d0 | ||
|
|
c43012a5f2 | ||
|
|
577cab24c4 | ||
|
|
5c0a77aec8 | ||
|
|
a0904b14ed | ||
|
|
6774ae0ef3 | ||
|
|
5a76ca4676 | ||
|
|
3c5a972bc9 | ||
|
|
54b68ac543 | ||
|
|
ff378f05fe | ||
|
|
f207aa3a9d | ||
|
|
7b43dcb6a1 | ||
|
|
c487cf3284 | ||
|
|
c2e1d325f2 | ||
|
|
f090e8febf | ||
|
|
087c84bcfb | ||
|
|
a457c83242 | ||
|
|
bcd8963e8b | ||
|
|
1464e0fbe8 | ||
|
|
ec2b048289 | ||
|
|
04811c934f | ||
|
|
f84ee30b9d | ||
|
|
218caa28b0 | ||
|
|
a8af807650 | ||
|
|
826170507e | ||
|
|
c37979e48d | ||
|
|
7ebb046cd8 | ||
|
|
7c4d0a15dd | ||
|
|
512b9e0a92 | ||
|
|
5e95a8565c | ||
|
|
e83d0f2a9d | ||
|
|
eaebbcf6c8 | ||
|
|
7df5ed9b35 | ||
|
|
6b66f14319 | ||
|
|
2db1684b3c | ||
|
|
4625b44703 | ||
|
|
0356ecc17b | ||
|
|
1e7c27fba1 | ||
|
|
03f575f66f | ||
|
|
82b36c1b70 | ||
|
|
6878ab51fb | ||
|
|
8662033979 | ||
|
|
ef61652fba | ||
|
|
933a66b24c | ||
|
|
e2c6a5f8cd | ||
|
|
a818e7dd40 | ||
|
|
759dc647e5 | ||
|
|
37cf46d581 | ||
|
|
407032114e | ||
|
|
94aece134c | ||
|
|
7532bf9825 | ||
|
|
0f4f541b11 | ||
|
|
07a3d38bef | ||
|
|
e9273ff79a | ||
|
|
1aa708aed4 | ||
|
|
ebe5a6030e | ||
|
|
f6946085d8 | ||
|
|
beebe7c98b | ||
|
|
a51331d6b2 | ||
|
|
b7b970e654 | ||
|
|
d823e8522c | ||
|
|
6bc5ac46b7 | ||
|
|
1193a93f86 | ||
|
|
4cd052e009 | ||
|
|
949f61f1a4 | ||
|
|
2145c3f88c | ||
|
|
bb71d5dc0a | ||
|
|
41856ff6af | ||
|
|
a1388ddab7 | ||
|
|
b2d13f586d | ||
|
|
9f0cd586ee | ||
|
|
ce67497d3a | ||
|
|
0dc26e589a | ||
|
|
e14a676eea | ||
|
|
11cf89493d | ||
|
|
5be121ec71 | ||
|
|
95e58b5e69 | ||
|
|
506fd22280 | ||
|
|
d79b12dedc | ||
|
|
599cd7299c | ||
|
|
18d26b79af | ||
|
|
1f81b81a58 | ||
|
|
cc5e420484 | ||
|
|
b4eaa48765 | ||
|
|
76354741be | ||
|
|
1b466609f0 | ||
|
|
7e11b8bb5a | ||
|
|
b251e1f73c | ||
|
|
fa11382c08 | ||
|
|
e17a49acd5 | ||
|
|
bc71ffa6f2 | ||
|
|
95dc3c92c5 | ||
|
|
2135accaf4 | ||
|
|
429c38fc66 | ||
|
|
56e92b1695 | ||
|
|
b2685d455b | ||
|
|
abfd1fa254 | ||
|
|
24a5717e27 | ||
|
|
9d9503b00e | ||
|
|
7b0579ccf3 | ||
|
|
df84dff54f | ||
|
|
367c09f7e6 | ||
|
|
46967dc126 | ||
|
|
e0ede7ba74 | ||
|
|
1fe7554818 | ||
|
|
eff3332fef | ||
|
|
caea4775b3 | ||
|
|
5f04950358 | ||
|
|
c46af91240 | ||
|
|
f5034effd2 | ||
|
|
20408347fb | ||
|
|
49d5bfd3e7 | ||
|
|
e99d1a74fd | ||
|
|
43d1cede98 | ||
|
|
091fc93645 | ||
|
|
dfe2771ba7 | ||
|
|
d3664321fd | ||
|
|
2e01ff7826 | ||
|
|
59d5a7439d | ||
|
|
6e3edd75eb | ||
|
|
78992444bf | ||
|
|
f1dea8fb1a | ||
|
|
04e5ab0d01 | ||
|
|
22a1cef498 | ||
|
|
98eaeddbfd | ||
|
|
00e4df2dd3 | ||
|
|
cfb4133152 | ||
|
|
42361d17b5 | ||
|
|
02ee95506c | ||
|
|
a749946457 | ||
|
|
18fb86c243 | ||
|
|
7597e4006c | ||
|
|
50be5f4895 | ||
|
|
326fb47593 | ||
|
|
240c576bad | ||
|
|
88c8c8ae55 | ||
|
|
394a7e42fb | ||
|
|
869ee217eb | ||
|
|
03dbe272fc | ||
|
|
87973e9775 | ||
|
|
4450b1aa81 | ||
|
|
57575ea322 | ||
|
|
68d3d7abfd | ||
|
|
4502a966a1 | ||
|
|
e523733b2c | ||
|
|
3864f1d950 | ||
|
|
4bdb9c8632 | ||
|
|
b1c098614c | ||
|
|
4309064804 | ||
|
|
f91e67ad6b | ||
|
|
d63ec210c7 | ||
|
|
3d160ee1df | ||
|
|
51b482f57d | ||
|
|
b367c4b4ce | ||
|
|
7432ad310c | ||
|
|
5b02202efb | ||
|
|
23056bcd63 | ||
|
|
2b0c92a4ea | ||
|
|
d669d43fe4 | ||
|
|
426e0edfb5 | ||
|
|
2cc0aa6f3d | ||
|
|
f895916fbb | ||
|
|
fea3bba0df | ||
|
|
7ed7321219 | ||
|
|
b2bf192677 | ||
|
|
d323e775ca | ||
|
|
22a00b2341 | ||
|
|
f36bba6406 | ||
|
|
674c583881 | ||
|
|
eb5ad7c6dc | ||
|
|
ca771eb04c | ||
|
|
d705b8ab33 | ||
|
|
9454eda082 | ||
|
|
7d5329e186 | ||
|
|
18979a7f1a | ||
|
|
7301158e54 | ||
|
|
5b9c41f29a | ||
|
|
179884cf93 | ||
|
|
5bc01ea13e | ||
|
|
ca43db8d93 | ||
|
|
f4cb5e6632 | ||
|
|
da2e740e65 | ||
|
|
2f0d2bdf32 | ||
|
|
97eedb2034 | ||
|
|
3ac46e62cb | ||
|
|
97db3635af | ||
|
|
e3464da19a | ||
|
|
ec3ee8fbb3 | ||
|
|
96208d3760 | ||
|
|
5bb61c0730 | ||
|
|
858f86d9df | ||
|
|
aa1e5a11ad | ||
|
|
ded8865914 | ||
|
|
da1437a268 | ||
|
|
599f831a09 | ||
|
|
23b532e2bf | ||
|
|
9f1b8ae58f | ||
|
|
d62850f82d | ||
|
|
41a0cfd0a2 | ||
|
|
fb6e85c56b | ||
|
|
d58550c2b8 | ||
|
|
5bf3ca2708 | ||
|
|
3e4a7e7a56 | ||
|
|
5d17de227b | ||
|
|
0d985c0221 | ||
|
|
eaa6bc12ce | ||
|
|
b3337df774 | ||
|
|
6c8c5bcde6 | ||
|
|
d255f6add4 | ||
|
|
84dde72990 | ||
|
|
5dfeee548d | ||
|
|
fba2102518 | ||
|
|
09516b4d4e | ||
|
|
b7b74d8f1f | ||
|
|
80d3cd3126 | ||
|
|
bbd416ba24 | ||
|
|
75563660f0 | ||
|
|
c2197bcc53 | ||
|
|
12114c786b | ||
|
|
73c192ad18 | ||
|
|
465564325e | ||
|
|
7c0d093be5 | ||
|
|
a1fbe6b970 | ||
|
|
305d86f765 | ||
|
|
e7e5816ded | ||
|
|
cd9b1b906c | ||
|
|
0b5a74aa9f | ||
|
|
c3407ac35a | ||
|
|
c9699647d7 | ||
|
|
aac011d3b3 | ||
|
|
e2108ff85b | ||
|
|
5c492f893b | ||
|
|
2877b3c63d | ||
|
|
1d94185078 | ||
|
|
a27eddae56 | ||
|
|
5ed830205d | ||
|
|
aeca6f04f9 | ||
|
|
c099ff7662 | ||
|
|
83ba366558 | ||
|
|
6129fdb6e5 | ||
|
|
8db66bf282 | ||
|
|
b7cd18b715 | ||
|
|
6ed991593a | ||
|
|
ccf3d49fc4 | ||
|
|
7e95e44f1d | ||
|
|
a5de11d002 | ||
|
|
756bd82a46 | ||
|
|
f9ce4a2f81 | ||
|
|
088301c4be | ||
|
|
f7f70408c9 | ||
|
|
292d713423 | ||
|
|
e02eadc9f7 | ||
|
|
6e66df59b7 | ||
|
|
00b9f4cab6 | ||
|
|
f6fb56229e | ||
|
|
5b770084c9 | ||
|
|
a2472e0cf5 | ||
|
|
4de7b52044 | ||
|
|
1e100d1bf1 | ||
|
|
d00fb9e0a5 | ||
|
|
f5d8673ad4 | ||
|
|
bd2cba1f31 | ||
|
|
45c07b7c39 | ||
|
|
36244d58aa | ||
|
|
e968d5a2a5 | ||
|
|
84df9cca87 | ||
|
|
e550989ce2 | ||
|
|
94edc1e284 | ||
|
|
b9f8cad578 | ||
|
|
02eb382ae7 | ||
|
|
1ecc092f08 | ||
|
|
191fa922d2 | ||
|
|
fb817f1ca7 | ||
|
|
9c2f128585 | ||
|
|
9ebd700317 | ||
|
|
9ab6cf31fd | ||
|
|
bb5c114b8d | ||
|
|
1f2a724d32 | ||
|
|
9b28203757 | ||
|
|
ac9f30f5f0 | ||
|
|
b13b0a66ce | ||
|
|
fcfdd5bc76 | ||
|
|
cdbbc37d59 | ||
|
|
4ba4af7cf9 | ||
|
|
89708d1fd6 | ||
|
|
6cb48c186e | ||
|
|
a1c9c47c89 | ||
|
|
85cc2865b6 | ||
|
|
2dc74b26f3 | ||
|
|
3d0ed43920 | ||
|
|
dc54943a19 | ||
|
|
c6ae5368fe | ||
|
|
c947354517 | ||
|
|
076f01b65f | ||
|
|
e37292a276 | ||
|
|
7d76473580 | ||
|
|
8bafbbd2ff | ||
|
|
80c5dff5ad | ||
|
|
a4571a2617 | ||
|
|
18608a8b63 | ||
|
|
c9116ad7ab | ||
|
|
d982902986 | ||
|
|
3ab6868460 | ||
|
|
8d5974d0f8 | ||
|
|
35a64afdf9 | ||
|
|
1ed850324d | ||
|
|
8f886df84f | ||
|
|
55481b255b | ||
|
|
b0b9d8445e | ||
|
|
3a2f04006f | ||
|
|
1aacd4ece1 | ||
|
|
f0e3e3b6f9 | ||
|
|
26533713ff | ||
|
|
b55d54eb5b | ||
|
|
01cb57c9fb | ||
|
|
eb85464f8d | ||
|
|
d30fcf8dca | ||
|
|
004d14eaf4 | ||
|
|
d1a7c3390a | ||
|
|
132c4139ad | ||
|
|
0aa664fb4f | ||
|
|
d25dc1a23f | ||
|
|
3d5f22b67d | ||
|
|
cf6ae951d2 | ||
|
|
cca9384cd7 | ||
|
|
e7b2557bcd | ||
|
|
dad084b309 | ||
|
|
e7fea1b138 | ||
|
|
b24d7df789 | ||
|
|
c2f801b6a9 | ||
|
|
20112688ab | ||
|
|
2a19bdd8d1 | ||
|
|
2d95806feb | ||
|
|
40da48a106 | ||
|
|
df81d9fd5f | ||
|
|
1060775cad | ||
|
|
96cc9c681c | ||
|
|
b4200fba60 | ||
|
|
2ded5228cb | ||
|
|
7be58fb884 | ||
|
|
a29e9e11f7 | ||
|
|
18c89e4fa5 | ||
|
|
84dd370cfb | ||
|
|
b45c79d65b | ||
|
|
52a5086f7e | ||
|
|
3980dc7e84 | ||
|
|
ffd0608dda | ||
|
|
322bc90920 | ||
|
|
9685f2c2b3 | ||
|
|
342871a216 | ||
|
|
1cd1ab07a2 | ||
|
|
137be678c0 | ||
|
|
bcf0aaab17 | ||
|
|
9c331e1777 | ||
|
|
789516e573 | ||
|
|
02f964c7d9 | ||
|
|
ea4d1de772 | ||
|
|
0f3d71a504 | ||
|
|
5dc00a8bc6 | ||
|
|
5690e3fe9e | ||
|
|
f6fcb280fc | ||
|
|
65a20815bf | ||
|
|
9a55202a9f | ||
|
|
06ec65fb10 | ||
|
|
371ecd9d3a | ||
|
|
ff3fce821c | ||
|
|
cc706a48da | ||
|
|
e4093209cc | ||
|
|
1f7e5632ac | ||
|
|
bda9e7b2b2 | ||
|
|
5427ddb8d6 | ||
|
|
b34d40252f | ||
|
|
f73d74dd73 | ||
|
|
eb48b8e65f | ||
|
|
ce0fe368ab | ||
|
|
34ef71707a | ||
|
|
ffeb9dbaa5 | ||
|
|
62a1d09f48 | ||
|
|
60039de67d | ||
|
|
526df6e41a | ||
|
|
cf8b451e35 | ||
|
|
059260d318 | ||
|
|
45134f903d | ||
|
|
fefe4edda1 | ||
|
|
aabb1bc264 | ||
|
|
02ba2d3b60 | ||
|
|
925c5aa389 | ||
|
|
2b6ce14a32 | ||
|
|
ed45e524b9 | ||
|
|
e645204e37 | ||
|
|
ff1429c6b3 | ||
|
|
abe17a02c4 | ||
|
|
7bde73102b | ||
|
|
3f27093f82 | ||
|
|
37ed53cb3c | ||
|
|
4b20d3ef0a | ||
|
|
12492b5749 | ||
|
|
af2b422730 | ||
|
|
0c63f65aa7 | ||
|
|
4b2d1e6745 | ||
|
|
25e6a03435 | ||
|
|
d681f91de9 | ||
|
|
12e2bcbbd9 | ||
|
|
ec3e438c99 | ||
|
|
2089237d23 | ||
|
|
7bcd0ac3e5 | ||
|
|
8e9ab12219 | ||
|
|
33b539858f | ||
|
|
cdfd828a8b | ||
|
|
22727b5abe | ||
|
|
041cf1268d | ||
|
|
fb3afbdc76 | ||
|
|
1f6632146b | ||
|
|
944187f276 | ||
|
|
81eb2189ca | ||
|
|
4fc90984d8 | ||
|
|
0b1abc9ab0 | ||
|
|
212d81b93c | ||
|
|
238ac22b85 | ||
|
|
773f0be84a | ||
|
|
e45c988637 | ||
|
|
8305b49046 | ||
|
|
92b2601ba2 | ||
|
|
af8ab752ad | ||
|
|
e45105ccb3 | ||
|
|
9114b68659 | ||
|
|
cd2e091580 | ||
|
|
38d8f83587 | ||
|
|
5f3b6501d7 | ||
|
|
f35efbdd5b | ||
|
|
961954364a | ||
|
|
259725882a | ||
|
|
fb2288c4bc | ||
|
|
0220f4519d | ||
|
|
3432243acb | ||
|
|
27a32463d9 | ||
|
|
b47f7e8cf1 | ||
|
|
459bc69032 | ||
|
|
378b4bb8c1 | ||
|
|
6a5712070f | ||
|
|
48e125881b | ||
|
|
e6cec93f2c | ||
|
|
b5726393f3 | ||
|
|
82010e4fa3 | ||
|
|
eb99fe58dd | ||
|
|
a18e7ab2da | ||
|
|
e1f78f519c | ||
|
|
50a57727fe | ||
|
|
4bbb7f82b4 | ||
|
|
1da4cf8907 | ||
|
|
978a58391b | ||
|
|
b2be44e372 | ||
|
|
650fc6aa27 | ||
|
|
47bda7d789 | ||
|
|
64f41f004d | ||
|
|
68f69074cb | ||
|
|
4d3fb52956 | ||
|
|
18a23d6844 | ||
|
|
bdf653bc70 | ||
|
|
0aaa351797 | ||
|
|
1f39761f8c | ||
|
|
c7914fa8e4 | ||
|
|
a48cc2a7f3 | ||
|
|
3942409c9a | ||
|
|
6b0719db45 | ||
|
|
9728116836 | ||
|
|
6cffabe259 | ||
|
|
28b20cc8ba | ||
|
|
62b012941e | ||
|
|
1b94ac383c | ||
|
|
9a96ef2623 | ||
|
|
6480750757 | ||
|
|
df313560c2 | ||
|
|
be0e832589 | ||
|
|
9f87f551fd | ||
|
|
5804c57236 | ||
|
|
7efd81191a | ||
|
|
84bea20891 | ||
|
|
c2b9b6e162 | ||
|
|
3720b9481f | ||
|
|
b565d40ec7 | ||
|
|
0e1f2e721f | ||
|
|
951a22b90e | ||
|
|
1dd88a690b | ||
|
|
55ba78c66a | ||
|
|
b0364041e2 | ||
|
|
6b9c90f99b | ||
|
|
4050bc1da8 | ||
|
|
4bb9051136 | ||
|
|
ceca4fbe53 | ||
|
|
9b7c0288d4 | ||
|
|
392a90c02c | ||
|
|
7a58f6d967 | ||
|
|
35d1e51f9b | ||
|
|
9729a4c724 | ||
|
|
f13713a055 | ||
|
|
31655f7832 | ||
|
|
fb4bb81595 | ||
|
|
31cb6916c6 | ||
|
|
61d37615af | ||
|
|
eaa7701696 | ||
|
|
d6cff8e0b0 | ||
|
|
8ba761b33c | ||
|
|
05e7e452df | ||
|
|
3f0fd4f771 | ||
|
|
a587c1d1da | ||
|
|
c3355f7fe4 | ||
|
|
c182d874af | ||
|
|
ab9ebfb667 | ||
|
|
cb953eda61 | ||
|
|
93c291dba1 | ||
|
|
603a1ef046 | ||
|
|
5a504b00fb | ||
|
|
dfa59dc93d | ||
|
|
534bcdd52c | ||
|
|
ea032bf551 | ||
|
|
b44eee8d81 | ||
|
|
8f57ada128 | ||
|
|
3963990831 | ||
|
|
dc1ffafdf3 | ||
|
|
4a0b4de322 | ||
|
|
0ede65e9ca | ||
|
|
0ebf30b8b6 | ||
|
|
4222b192c4 | ||
|
|
4a301aaec3 | ||
|
|
97a3a97a15 | ||
|
|
c526d73e23 | ||
|
|
58baf137aa | ||
|
|
066ab1500f | ||
|
|
224a468712 | ||
|
|
dd282383d7 | ||
|
|
9a99a95b15 | ||
|
|
e814494e37 | ||
|
|
90a0155be1 | ||
|
|
555d40408d | ||
|
|
0c3fbeb0b7 | ||
|
|
2f27decaa1 | ||
|
|
867115659f | ||
|
|
6282ab58db | ||
|
|
95c25c1bcd | ||
|
|
74a62de7d0 | ||
|
|
7fc021648d | ||
|
|
95914ad312 | ||
|
|
5ed00e037a | ||
|
|
6f3074536a | ||
|
|
21f5cb36bb | ||
|
|
7b7592822f | ||
|
|
9c7b7b0d75 | ||
|
|
d88b23c42d | ||
|
|
1602c0aca2 | ||
|
|
384978a511 | ||
|
|
afd7a0494f | ||
|
|
ac1f8a69e1 | ||
|
|
05cfa99ea0 | ||
|
|
9b43ccbbc0 | ||
|
|
6d8b156455 | ||
|
|
1b9943a4c8 | ||
|
|
8232a4c9c8 | ||
|
|
9d4d64c95a | ||
|
|
2d0acc7663 | ||
|
|
4231ed74ba | ||
|
|
912e1cf89f | ||
|
|
26d4fb8005 | ||
|
|
4a6c0b39a8 | ||
|
|
9d01bba170 | ||
|
|
85c0ddba10 | ||
|
|
2664059812 | ||
|
|
b7e4d9c806 | ||
|
|
95b91f0ce2 | ||
|
|
f0407e4327 | ||
|
|
a7555f56e7 | ||
|
|
b093ed33b2 | ||
|
|
ec1a45ba18 | ||
|
|
def5dc3b0f | ||
|
|
24ec89c220 | ||
|
|
303e70bb58 | ||
|
|
ec3e92fc19 | ||
|
|
5ae776309d | ||
|
|
76dd606a48 | ||
|
|
8998798fa4 | ||
|
|
60ee82ca47 | ||
|
|
e1284002a9 | ||
|
|
8252512784 | ||
|
|
1390d7eb1d | ||
|
|
8da1bb13ff | ||
|
|
340e377b37 | ||
|
|
171589fb3d | ||
|
|
bcd07cce0d | ||
|
|
68880114b4 | ||
|
|
eb2360ae24 | ||
|
|
62712a352b | ||
|
|
745e6c1715 | ||
|
|
e20a75eb0c | ||
|
|
a24c41ff25 | ||
|
|
69f0339bd5 | ||
|
|
5e7c9a7278 | ||
|
|
726c323fe1 | ||
|
|
e96cbe2710 | ||
|
|
323e54b4bd | ||
|
|
7ab132bbf6 | ||
|
|
6b09210a80 | ||
|
|
be80d62c01 | ||
|
|
30587d625a | ||
|
|
af43cd407e | ||
|
|
647388e475 | ||
|
|
329e06ac30 | ||
|
|
5d96138720 | ||
|
|
66b275605c | ||
|
|
9b7478c0c7 | ||
|
|
668271bb31 | ||
|
|
1aa93e7737 | ||
|
|
a0864f5f67 | ||
|
|
6e9f71f942 | ||
|
|
65211372df | ||
|
|
2ca8d8817a | ||
|
|
ec266ea657 | ||
|
|
d117aa5139 | ||
|
|
4534b7d4dc | ||
|
|
707fe01d77 | ||
|
|
0e09ba0dd5 | ||
|
|
989560f23c | ||
|
|
844a9f934f | ||
|
|
b5348c593a | ||
|
|
7f809ba541 | ||
|
|
f9058fcddc | ||
|
|
05c9957fd2 | ||
|
|
675739d24f | ||
|
|
10be0867ad | ||
|
|
d2a4b85bdd | ||
|
|
782061ac5e | ||
|
|
8d98e9e6f9 | ||
|
|
4aa75e9376 | ||
|
|
c6d6eecb43 | ||
|
|
1d6d7b8aa8 | ||
|
|
68ed8e51bd | ||
|
|
d4dd962193 | ||
|
|
7dfb70eb8e | ||
|
|
53675eeba7 | ||
|
|
6399973bfa | ||
|
|
f1384f5dc1 | ||
|
|
027cad9e52 | ||
|
|
dffcff48a0 | ||
|
|
0391f31b3a | ||
|
|
eb7b0ba92f | ||
|
|
3a136e1464 | ||
|
|
8792bcabcb | ||
|
|
c362fc4677 | ||
|
|
f471fe62ea | ||
|
|
f19aa96f3e | ||
|
|
2d6b4f1216 | ||
|
|
22a8f766c7 | ||
|
|
86bc6fa807 | ||
|
|
14b094cfe0 | ||
|
|
d90b36bd33 | ||
|
|
18b800ff7a | ||
|
|
7ab56a9616 | ||
|
|
6f8352033b | ||
|
|
188ac5051a | ||
|
|
335e0dd575 | ||
|
|
67b187f884 | ||
|
|
c2d262ea1d | ||
|
|
8683465d70 | ||
|
|
8c9705eec0 | ||
|
|
d14a8bc301 | ||
|
|
72aceedab4 | ||
|
|
7c5ee1bd00 | ||
|
|
26aa79db1a | ||
|
|
2629aaf368 | ||
|
|
3973ebc00f | ||
|
|
6cddb5f3ba | ||
|
|
7c55da8cc6 | ||
|
|
0c9f122719 | ||
|
|
1d941baff1 | ||
|
|
e5226d7ffc | ||
|
|
aa3d69cb94 | ||
|
|
e68d386d3d | ||
|
|
b322f20c81 | ||
|
|
7d7a9f3dc6 | ||
|
|
977a5e868f | ||
|
|
41ff511165 | ||
|
|
aadbb970b6 | ||
|
|
1873ce41b6 | ||
|
|
ff51e4cc36 | ||
|
|
73b87f2e97 | ||
|
|
1444c99458 | ||
|
|
85c3056223 | ||
|
|
4a815b0bdf | ||
|
|
b7525e1e7e | ||
|
|
c3f64fe9c4 | ||
|
|
34f6bc2403 | ||
|
|
9ecec972ca | ||
|
|
80febf97d3 | ||
|
|
71073874eb | ||
|
|
f3dfeac125 | ||
|
|
f12e73519e | ||
|
|
19f7dda4cc | ||
|
|
221397b159 | ||
|
|
91766ecea3 | ||
|
|
191be134f9 | ||
|
|
56d279ae1e | ||
|
|
3e61464dac | ||
|
|
85ca10dbb3 | ||
|
|
eaf08c45d9 | ||
|
|
bcb44e8cf7 | ||
|
|
d215e0716e | ||
|
|
f635162832 | ||
|
|
c892480086 | ||
|
|
ea49d17c47 | ||
|
|
39c32b0e62 | ||
|
|
5cdfa35a76 | ||
|
|
147b3ff993 | ||
|
|
662c229de1 | ||
|
|
c90cb2ae6e | ||
|
|
864d070656 | ||
|
|
e8ac2b561a | ||
|
|
c71a432ce4 | ||
|
|
e3ca470a6a | ||
|
|
e7c6fbf423 | ||
|
|
6cef5e614d | ||
|
|
33cf77559f | ||
|
|
b5085d8004 | ||
|
|
80af20ef54 | ||
|
|
4a324ca764 | ||
|
|
85db64ed70 | ||
|
|
d72ba456ae | ||
|
|
b4a0913994 | ||
|
|
6fb8a9ef2f | ||
|
|
4e58708f12 | ||
|
|
32876367c2 | ||
|
|
226aa0b3ba | ||
|
|
c37359cdfd | ||
|
|
d2d89c252a | ||
|
|
b76f1d3c8c | ||
|
|
9ff34a12a2 | ||
|
|
c5f6ce3fe5 | ||
|
|
965d556ac9 | ||
|
|
e52d3f21d1 | ||
|
|
00576053d0 | ||
|
|
1f040efb7f | ||
|
|
3b5e9775c5 | ||
|
|
ea0653460e | ||
|
|
6ed80eb6c9 | ||
|
|
12bdd87705 | ||
|
|
9bdf7ff174 | ||
|
|
2ab2f757a0 | ||
|
|
f830de8d13 | ||
|
|
7eb329db6b | ||
|
|
fecb980c9b | ||
|
|
600218cf7b | ||
|
|
8eb48e4311 | ||
|
|
ac33d2f37c | ||
|
|
bfc462cbec | ||
|
|
8b8bd88adf | ||
|
|
bc768b773b | ||
|
|
950e5a57b1 | ||
|
|
52a4fc8e93 | ||
|
|
dd5c840b61 | ||
|
|
337c01daf8 | ||
|
|
5a9da2a97a | ||
|
|
38c1a0f383 | ||
|
|
2b1718945f | ||
|
|
a6319a3865 | ||
|
|
35241307d3 | ||
|
|
ed8aaa5505 | ||
|
|
a25f6dee73 | ||
|
|
9aa8699617 | ||
|
|
94f671ca6b | ||
|
|
7c9016bf4a | ||
|
|
98d18fb097 | ||
|
|
dc69887628 | ||
|
|
7c882ed40c | ||
|
|
c8909beedd | ||
|
|
15fc4e5f2d | ||
|
|
229b9cf40a | ||
|
|
17ae441c22 | ||
|
|
9a70c0e8a7 | ||
|
|
2a41fee1ff | ||
|
|
f385c3773c | ||
|
|
4b4e847816 | ||
|
|
a70c4c250c | ||
|
|
950e75e7e3 | ||
|
|
2cbf2006c6 | ||
|
|
14c4095a4e | ||
|
|
c755e8a90d | ||
|
|
7c1d824636 | ||
|
|
f71be112c2 | ||
|
|
8a4dba6317 | ||
|
|
87c72bd595 | ||
|
|
f9e756402f | ||
|
|
f576bbd3e1 | ||
|
|
f57d54249c | ||
|
|
20dca960ef | ||
|
|
fe9df3977a | ||
|
|
c5bac554a7 | ||
|
|
0e791fd0ec | ||
|
|
6fb9aafa03 | ||
|
|
12f7f764dc | ||
|
|
d9a09a815a | ||
|
|
489c7934bf | ||
|
|
5cc1438e8b | ||
|
|
6ee2f05fb7 | ||
|
|
695dc98010 | ||
|
|
cbc975399c | ||
|
|
25c643a1a3 | ||
|
|
2a16be2fb2 | ||
|
|
105a7870bf | ||
|
|
c05442dea8 | ||
|
|
370197aea3 | ||
|
|
cf09918a94 | ||
|
|
65713477de | ||
|
|
9d4f29f0a9 | ||
|
|
85429bc505 | ||
|
|
5b66143fbe | ||
|
|
ca78953889 | ||
|
|
865d475083 | ||
|
|
6b9c9f9f78 | ||
|
|
97dfb6fdec | ||
|
|
473f5bfb62 | ||
|
|
183f5fea5c | ||
|
|
69e059ba01 | ||
|
|
3544c96a64 | ||
|
|
56c2515f01 | ||
|
|
90d3639796 | ||
|
|
a3e994fc95 | ||
|
|
98b3cfaaa0 | ||
|
|
a8f28af2b3 | ||
|
|
c2603c3f53 | ||
|
|
949433b65f | ||
|
|
dda1a9ecc4 | ||
|
|
abdb40179c | ||
|
|
c1efe268d0 | ||
|
|
58818dabc5 | ||
|
|
24ee5c2d5d | ||
|
|
93cec9a2d6 | ||
|
|
fe1c5b4b38 | ||
|
|
e3e94ede65 | ||
|
|
b9ea3de860 | ||
|
|
b99df5905f | ||
|
|
a5246df3ed | ||
|
|
f4127a575b | ||
|
|
65860b166f | ||
|
|
59f5304d87 | ||
|
|
3d4848da90 | ||
|
|
f43fd34a8c | ||
|
|
bb68303b03 | ||
|
|
236bcdfb68 | ||
|
|
6c7df2788e | ||
|
|
b3a344635a | ||
|
|
4ca29a4a75 | ||
|
|
de35a62984 | ||
|
|
ef886c8742 | ||
|
|
1b754b435b | ||
|
|
50b7ce764e | ||
|
|
090fa76d15 | ||
|
|
898137c8a5 | ||
|
|
901a9ae9d7 | ||
|
|
40a5c6f55e | ||
|
|
6d0651a1cb | ||
|
|
ee4d2400c9 | ||
|
|
5131ebb9c9 | ||
|
|
0a86709440 | ||
|
|
9d5b9f0bde | ||
|
|
19fa769bd3 | ||
|
|
de8e2d1be7 | ||
|
|
f546d682bd | ||
|
|
197d1c673c | ||
|
|
16e6a88ccf | ||
|
|
de43eb4a57 | ||
|
|
ff64ad8df0 | ||
|
|
e55408424e | ||
|
|
3c9354ba2f | ||
|
|
2382d5028b | ||
|
|
7346958b27 | ||
|
|
78e12775e4 | ||
|
|
a76053be58 | ||
|
|
028731458b | ||
|
|
1cee1c6e8f | ||
|
|
1f6dd079cd | ||
|
|
faddfe8506 | ||
|
|
223cd61220 | ||
|
|
8278a8f3e4 | ||
|
|
8bd9eafa37 | ||
|
|
9a23d5fa97 | ||
|
|
4557366154 | ||
|
|
8f95ba03ab | ||
|
|
89b7672630 | ||
|
|
49f948844f | ||
|
|
d1395e37fd | ||
|
|
06de7b5176 | ||
|
|
56b9cb5c9e | ||
|
|
e4a684ff10 | ||
|
|
e4f12ed47f | ||
|
|
6fcda290c7 | ||
|
|
243a00e326 | ||
|
|
d274a83c24 | ||
|
|
bdb95e58e6 | ||
|
|
0650cafb28 | ||
|
|
786f6953e7 | ||
|
|
a1d52af0ba | ||
|
|
1da85c96cf | ||
|
|
84dc1d1b74 | ||
|
|
63aa55baf1 | ||
|
|
3b28e68e31 | ||
|
|
19d835c793 | ||
|
|
df9282e759 | ||
|
|
c679726564 | ||
|
|
7e6f9eb67a | ||
|
|
3cfe8bf751 | ||
|
|
a1495a8f0c | ||
|
|
fe6a40f7d0 | ||
|
|
e5733b83a0 | ||
|
|
1dc4f851cb | ||
|
|
ba2debf577 | ||
|
|
470a767eaf | ||
|
|
8e3d5b99c5 | ||
|
|
763e43905a | ||
|
|
ab4005ae00 | ||
|
|
0c61e48977 | ||
|
|
8526ad17c4 | ||
|
|
ee91f4610e | ||
|
|
1afc05310e | ||
|
|
ed381bcf0a | ||
|
|
59aaa07e3b | ||
|
|
4d759a6995 | ||
|
|
ce00587041 | ||
|
|
50aad69189 | ||
|
|
2dc04fa041 | ||
|
|
6dd21fe9e9 | ||
|
|
98d3b42728 | ||
|
|
6d225beb46 | ||
|
|
1f7ca7386a | ||
|
|
455df290bf | ||
|
|
2d2192f2f6 | ||
|
|
175d5ec57f | ||
|
|
255886df53 | ||
|
|
f0bc2d9c9b | ||
|
|
dc778ea578 | ||
|
|
bd0d321dba | ||
|
|
57e13c25b5 | ||
|
|
b428660f92 | ||
|
|
a8618f1c79 | ||
|
|
716619e7f1 | ||
|
|
b8da4cf6fc | ||
|
|
9432abbd1a | ||
|
|
edef454043 | ||
|
|
febc3093a9 | ||
|
|
ddd832d016 | ||
|
|
137bb3a4c2 | ||
|
|
5ade229cb9 | ||
|
|
a6bd9ecfa3 | ||
|
|
1cb396dc51 | ||
|
|
a27357213a | ||
|
|
2d4676ed7e | ||
|
|
3e9ac965e8 | ||
|
|
a45c77bda9 | ||
|
|
e659fd1262 | ||
|
|
7459f1d1ad | ||
|
|
637120d0c5 | ||
|
|
cd131b2d21 | ||
|
|
90dd0d0b9b | ||
|
|
3358846d41 | ||
|
|
473fc1a766 | ||
|
|
ecfecd295a | ||
|
|
b774091b83 | ||
|
|
f49640cbe6 | ||
|
|
8b4fadde2b | ||
|
|
1b94b22360 | ||
|
|
b090de0da1 | ||
|
|
0294c2cb6d | ||
|
|
d9bcce781a | ||
|
|
3451322a38 | ||
|
|
ba604fcd3c | ||
|
|
c7df888d39 | ||
|
|
7b80505f69 | ||
|
|
de5914194e | ||
|
|
e9da73b930 | ||
|
|
15cc46bba5 | ||
|
|
417743ccdd | ||
|
|
0f04286783 | ||
|
|
3e0f2126b3 | ||
|
|
656d17cc07 | ||
|
|
35bb106654 | ||
|
|
634e5e27d3 | ||
|
|
0f9186628b | ||
|
|
6267ca52fc | ||
|
|
06b32dc6ec | ||
|
|
93582da044 | ||
|
|
d830499c76 | ||
|
|
30d1a119f3 | ||
|
|
5efab1d495 | ||
|
|
1228010488 | ||
|
|
b8921713eb | ||
|
|
0b779dfd3d | ||
|
|
311baaa3d1 | ||
|
|
6f75e0bba0 | ||
|
|
6193bf431d | ||
|
|
7baf72d3db | ||
|
|
ba3b2fbed1 | ||
|
|
9d4e0849d6 | ||
|
|
1a51a7bf5b | ||
|
|
7d42c4eaa0 | ||
|
|
1d60e881ee | ||
|
|
fda8155894 | ||
|
|
f089b5e3d1 | ||
|
|
f578ebe4ef | ||
|
|
b97378dd40 | ||
|
|
32f62b7ceb | ||
|
|
db43f817f7 | ||
|
|
3c45e7dac9 | ||
|
|
f1584ad7d7 | ||
|
|
8f503f4f99 | ||
|
|
499ecf9c39 | ||
|
|
9cf15e37ce | ||
|
|
6d731e2939 | ||
|
|
1e7c2c2362 | ||
|
|
ae51847f02 | ||
|
|
738eaa6ca7 | ||
|
|
70dbca67e7 | ||
|
|
16c3d4c253 | ||
|
|
1f62b9fdcb | ||
|
|
bd070ff066 | ||
|
|
3bb667f524 | ||
|
|
463c1f8b77 | ||
|
|
c5d575b9b6 | ||
|
|
d509637623 | ||
|
|
37026e556f | ||
|
|
f7b9416460 | ||
|
|
1ac22c6d48 | ||
|
|
66cb7f9bf0 | ||
|
|
87f7b782dc | ||
|
|
15d6e4937f | ||
|
|
dec6b17aa4 | ||
|
|
4a2b3d6293 | ||
|
|
04ef16a94b | ||
|
|
fd88a066da | ||
|
|
7267045475 | ||
|
|
318b750603 | ||
|
|
38e87b1af0 | ||
|
|
ce96a32af3 | ||
|
|
0ec9045849 | ||
|
|
b9895ba79a | ||
|
|
ff65297275 | ||
|
|
c647c0f509 | ||
|
|
5760efdaa1 | ||
|
|
bf5d8cab1c | ||
|
|
890bf49294 | ||
|
|
a097793b0d | ||
|
|
4b4bedaef3 | ||
|
|
246d605e5c | ||
|
|
f43319ba7a | ||
|
|
09ef907673 | ||
|
|
e345bbcae0 | ||
|
|
381da132f8 | ||
|
|
998a63612f | ||
|
|
63ccc49ce2 | ||
|
|
ce3b4cd817 | ||
|
|
28f4ed9144 | ||
|
|
4bd47f728a | ||
|
|
03dfda7a17 | ||
|
|
1717960a8c | ||
|
|
bb14aa821b | ||
|
|
fd8128dfe9 | ||
|
|
9b67f6e398 | ||
|
|
e59277742d | ||
|
|
53d23ec831 | ||
|
|
a2a6d89908 | ||
|
|
be29e6d847 | ||
|
|
c26a6a5252 | ||
|
|
ce01fe6141 | ||
|
|
39ff952667 | ||
|
|
cafc65ffa5 | ||
|
|
cccd2abb55 | ||
|
|
cb1a62ee27 | ||
|
|
51efa59728 | ||
|
|
aed5db0a8c | ||
|
|
0599dd1525 | ||
|
|
989f4c3aa5 | ||
|
|
22093d5111 | ||
|
|
7022bf005f | ||
|
|
68b8ad7e28 | ||
|
|
306f8a43e1 | ||
|
|
e46f3073b4 | ||
|
|
ed65bcf185 | ||
|
|
9633800977 | ||
|
|
1fb4f2946a | ||
|
|
d73b01674f | ||
|
|
271510ffb5 | ||
|
|
16b29c6116 | ||
|
|
367104f0a7 | ||
|
|
7979953f33 | ||
|
|
5ffd13e2c8 | ||
|
|
edb1700218 | ||
|
|
9729a2595d | ||
|
|
7c3468fbcb | ||
|
|
94c5bdb5aa | ||
|
|
e714d61a66 | ||
|
|
14ecc534e0 | ||
|
|
e18f76d2b0 | ||
|
|
3edf761549 | ||
|
|
27f18c1630 | ||
|
|
314ab61349 | ||
|
|
3666ee5a87 | ||
|
|
1f3ecbab33 | ||
|
|
4aacc06af0 | ||
|
|
ddab383b55 | ||
|
|
edef3f90f1 | ||
|
|
85fd36f5c3 | ||
|
|
d16f599db9 | ||
|
|
ba6451856a | ||
|
|
f1651078e4 | ||
|
|
a6aef345d5 | ||
|
|
4df4f57de3 | ||
|
|
5bd9a9a81d | ||
|
|
a0ac8ec9c2 | ||
|
|
2bac2f1a39 | ||
|
|
ab353d8498 | ||
|
|
f054365a46 | ||
|
|
cfc3fae67c | ||
|
|
5d8e32222a | ||
|
|
21a126f31f | ||
|
|
d21540878e | ||
|
|
894ab16b35 | ||
|
|
945d661a1e | ||
|
|
a176a4819f | ||
|
|
6892badf36 | ||
|
|
5db9e9531f | ||
|
|
7ebd18b00d | ||
|
|
9f1c7a0a32 | ||
|
|
561120c51c | ||
|
|
b93b9feee4 | ||
|
|
ea6317e3a2 | ||
|
|
5ecb26b032 | ||
|
|
c1a0818376 | ||
|
|
fbc756c6e3 | ||
|
|
8f258a2d05 | ||
|
|
d4d01cc186 | ||
|
|
5098153fde | ||
|
|
63a56b359b | ||
|
|
38a95c3745 | ||
|
|
f05c6ccddd | ||
|
|
de9780a756 | ||
|
|
b34e511ddc | ||
|
|
3de3c7a189 | ||
|
|
d256a872fc | ||
|
|
3db86e2a6b | ||
|
|
6436bb65e2 | ||
|
|
059df9c45d | ||
|
|
a18a591f0a | ||
|
|
08dc36fbb0 | ||
|
|
a0ace6e70f | ||
|
|
a52aadd37d | ||
|
|
0fb66e247a | ||
|
|
ecddc1691f | ||
|
|
b0f683bacf | ||
|
|
a89cf28812 | ||
|
|
cc9410602c | ||
|
|
e814b8ef09 | ||
|
|
ce34ef902f | ||
|
|
db5a5e1b37 | ||
|
|
9e3f3e324c | ||
|
|
a4ff241574 | ||
|
|
28197970bd | ||
|
|
7aa4450556 | ||
|
|
dabc37a1ef | ||
|
|
4315000905 | ||
|
|
8d4bd1171b | ||
|
|
82c5a53f5d | ||
|
|
29d0803a5c | ||
|
|
8f14ced5af | ||
|
|
9cafb76781 | ||
|
|
22c1b0627e | ||
|
|
995835a96c | ||
|
|
bf796fe80c | ||
|
|
425029783b | ||
|
|
8f399f3c64 | ||
|
|
192eb7e7c9 | ||
|
|
48a36649fa | ||
|
|
6b83b53401 | ||
|
|
c953c2b93f | ||
|
|
7cdce165fb | ||
|
|
b393064f26 | ||
|
|
31bf22063e | ||
|
|
cd9a43f359 | ||
|
|
c326e03eb2 | ||
|
|
f0a986aa04 | ||
|
|
4225da355d | ||
|
|
14bac6a744 | ||
|
|
05cc9b45e6 | ||
|
|
dba596bf35 | ||
|
|
db39d58ea8 | ||
|
|
c0f38216ef | ||
|
|
3643222b3c | ||
|
|
551217ea38 | ||
|
|
24bf1363ab | ||
|
|
08b2184e12 | ||
|
|
b73161882c | ||
|
|
e2186ecd62 | ||
|
|
b407402f3f | ||
|
|
8bb4132458 | ||
|
|
443822fd52 | ||
|
|
68427fd2de | ||
|
|
c3d3369601 | ||
|
|
3c5022d628 | ||
|
|
832ddddc58 | ||
|
|
0fc1415a06 | ||
|
|
1ab408c591 | ||
|
|
3160d3f275 | ||
|
|
d083f1ddc3 | ||
|
|
5fbc09b135 | ||
|
|
6282fabf98 | ||
|
|
2b528bad97 | ||
|
|
c3be8195fd | ||
|
|
39471d0421 | ||
|
|
7a50c0536c | ||
|
|
4ccd9501a8 | ||
|
|
75c05a4a85 | ||
|
|
ca7e12370f | ||
|
|
8bc9dafff2 | ||
|
|
dcb0416fd6 | ||
|
|
bbb69bba26 | ||
|
|
c1838b48ff | ||
|
|
d53f40002c | ||
|
|
866954b180 | ||
|
|
befa9cbf08 | ||
|
|
859f44db43 | ||
|
|
cca9c3c561 | ||
|
|
27e68e4c75 | ||
|
|
5c92350ed2 | ||
|
|
b94c62d1e5 | ||
|
|
de888d8a37 | ||
|
|
f8d6816101 | ||
|
|
119c6d5817 | ||
|
|
aaa21daa29 | ||
|
|
10f41bf288 | ||
|
|
91582691d8 | ||
|
|
463efc2254 | ||
|
|
0333354271 | ||
|
|
b85f56c681 | ||
|
|
be491be2cd | ||
|
|
4be4a8115d | ||
|
|
c0eb499f4d | ||
|
|
1b43f3facd | ||
|
|
26d41d3cb9 | ||
|
|
179765f6e4 | ||
|
|
df2e332134 | ||
|
|
2952f9d158 | ||
|
|
3c9face597 | ||
|
|
25f2e9c1b7 | ||
|
|
a6f8e1b9a3 | ||
|
|
d832031cec | ||
|
|
7a1a3ab64d | ||
|
|
19491a684e | ||
|
|
757224287e | ||
|
|
c9b5426f6f | ||
|
|
bf885c184f | ||
|
|
0d2bf4f7a1 | ||
|
|
01ffc68fc2 | ||
|
|
16892239fb | ||
|
|
d5765d8814 | ||
|
|
8d6a96074d | ||
|
|
f54884eb79 | ||
|
|
828149b2d6 | ||
|
|
501c4fc263 | ||
|
|
1d0b45e17d | ||
|
|
a0f7ed68fb | ||
|
|
7bd0c17188 | ||
|
|
1ea9d28523 | ||
|
|
8a3fb92bbe | ||
|
|
de3a9b9903 | ||
|
|
9834f3d2aa | ||
|
|
ac079b9d88 | ||
|
|
9e96906f32 | ||
|
|
90c079e743 | ||
|
|
4ecf307285 | ||
|
|
6cf4c453d9 | ||
|
|
d2899d14c7 | ||
|
|
f3b438d514 | ||
|
|
2997f694f8 | ||
|
|
b78ab4db27 | ||
|
|
37dddea515 | ||
|
|
e307d1e87d | ||
|
|
62e1dbb642 | ||
|
|
b8a425f530 | ||
|
|
cafb6fa694 | ||
|
|
0482ddea2c | ||
|
|
b411176c8d | ||
|
|
2f13449cb6 | ||
|
|
b0c1b7b683 | ||
|
|
7e8978c7fc | ||
|
|
d58b422bd0 | ||
|
|
3563601382 | ||
|
|
d42e6ca3fd | ||
|
|
7f0d8c99e3 | ||
|
|
48a67dc2b3 | ||
|
|
8d0b42492d | ||
|
|
e4076e95dd | ||
|
|
30a2b878f6 | ||
|
|
e17f94a67d | ||
|
|
4dd60c3844 | ||
|
|
9d76990f24 | ||
|
|
ed3d15f075 | ||
|
|
2c36a2aa96 | ||
|
|
16930aa422 | ||
|
|
263f5ba147 | ||
|
|
6a60c00e22 | ||
|
|
f3eaf644b0 | ||
|
|
a57110b935 | ||
|
|
cae8beaa8f | ||
|
|
df94d81d07 | ||
|
|
f03c22cc07 | ||
|
|
5b31fe37f2 | ||
|
|
c60a596995 | ||
|
|
b52ecd8085 | ||
|
|
4323341d19 | ||
|
|
e13992ba27 | ||
|
|
52a4317d09 | ||
|
|
d53187935b | ||
|
|
0d6c96e38b | ||
|
|
b0832578a4 | ||
|
|
805393b4db | ||
|
|
c3653577c6 | ||
|
|
1eb5a99ba3 | ||
|
|
a035d73545 | ||
|
|
79fc3056a6 | ||
|
|
e44cf6e7ee | ||
|
|
641c76ae62 | ||
|
|
1efcd69148 | ||
|
|
49ee41f7d3 | ||
|
|
598c7ea068 | ||
|
|
001a116c8b | ||
|
|
106e71fe54 | ||
|
|
cd93d6cc32 | ||
|
|
d63c89bae7 | ||
|
|
fb3a7733a3 | ||
|
|
852363cb77 | ||
|
|
7f6ee21a8e | ||
|
|
2963516d5c | ||
|
|
1f26ff5c80 | ||
|
|
de3f310082 | ||
|
|
4af2edafd3 | ||
|
|
4de08f2e71 | ||
|
|
d978e1dfa3 | ||
|
|
f828288b84 | ||
|
|
7a36f13034 | ||
|
|
422b48fa36 | ||
|
|
fe9e29a057 | ||
|
|
88c302ca2e | ||
|
|
52f3032483 | ||
|
|
b13edfeeae | ||
|
|
4046339569 | ||
|
|
52f4a9d961 | ||
|
|
ca0fb6d66a | ||
|
|
7c93c82d24 | ||
|
|
3b71760f9e | ||
|
|
c4d2045884 | ||
|
|
d28c59544f | ||
|
|
acff0b19d6 | ||
|
|
94bfcb2865 | ||
|
|
1bb6244337 | ||
|
|
a132ec4fd7 | ||
|
|
8291fa0ce1 | ||
|
|
37364ecd7e | ||
|
|
48d9e626f5 | ||
|
|
f0fbf664d4 | ||
|
|
7b8b4dc164 | ||
|
|
21635dd728 | ||
|
|
c7802940b1 | ||
|
|
f7b60febe9 | ||
|
|
6c93a63c06 | ||
|
|
c44a638644 | ||
|
|
0d3fead0f3 | ||
|
|
5ba4b37610 | ||
|
|
44a2d071ae | ||
|
|
3b22764368 | ||
|
|
11336da6df | ||
|
|
a0e5591f8e | ||
|
|
e952073c3c | ||
|
|
9bdd0d116a | ||
|
|
05c8a39e6d | ||
|
|
8fa6ff48cf | ||
|
|
7a31783ea4 | ||
|
|
96585b183d | ||
|
|
f81e7b02dc | ||
|
|
f7fbdf2081 | ||
|
|
06a877c755 | ||
|
|
30abd52189 | ||
|
|
6af0e62976 | ||
|
|
84a36a18d6 | ||
|
|
595cf6c375 | ||
|
|
4262e2cc1d | ||
|
|
c134986bbf | ||
|
|
d9981e1d71 | ||
|
|
2b6d7ec361 | ||
|
|
aaa91e50b7 | ||
|
|
ff9030e7af | ||
|
|
cc39e6402e | ||
|
|
c89b641b88 | ||
|
|
465304b004 | ||
|
|
63033ca12d | ||
|
|
f019dc6575 | ||
|
|
d15e3a64e7 | ||
|
|
7099b0579a | ||
|
|
2c2d08c7cc | ||
|
|
671e9ccb1c | ||
|
|
f93c5cb9a1 | ||
|
|
8c7f1c4359 | ||
|
|
d7c1c6efa1 | ||
|
|
30a2301697 | ||
|
|
c639186c60 | ||
|
|
5618cfb031 | ||
|
|
7e97c04d1e | ||
|
|
4d25077108 | ||
|
|
635caa9ad0 | ||
|
|
2772bffd09 | ||
|
|
995fc96a5d | ||
|
|
4660ad824d | ||
|
|
801049cbd0 | ||
|
|
09a7b4ea90 | ||
|
|
226c201925 | ||
|
|
4749a3da89 | ||
|
|
ae567ab462 | ||
|
|
bf382889d3 | ||
|
|
2272bcac71 | ||
|
|
a209c9450a | ||
|
|
2539a9c23f | ||
|
|
e95ede73ba | ||
|
|
ad970b1cb7 | ||
|
|
161e7d1763 | ||
|
|
3a823d32b5 | ||
|
|
4c46317f24 | ||
|
|
0271c223a6 | ||
|
|
9a4669067d | ||
|
|
53f3124345 | ||
|
|
b49a40b077 | ||
|
|
fb10da8ce3 | ||
|
|
b286c1a29b | ||
|
|
e5e7712716 | ||
|
|
2beb22e8cf | ||
|
|
747b5608e8 | ||
|
|
dad3cd9414 | ||
|
|
0c1fb3e118 | ||
|
|
afe223f410 | ||
|
|
e1ec50bcad | ||
|
|
04da844b22 | ||
|
|
f944910975 | ||
|
|
96b8467859 | ||
|
|
84554174ac | ||
|
|
65e03e707c | ||
|
|
fd9fcbea38 | ||
|
|
a1dfd7493a | ||
|
|
d4759d4056 | ||
|
|
d879518233 | ||
|
|
ef6cb3779b | ||
|
|
fc22114855 | ||
|
|
6b1eb5a479 | ||
|
|
bbd8a1265b | ||
|
|
444f63db42 | ||
|
|
f46a6aefea | ||
|
|
10792f714e | ||
|
|
d6d535ed9e | ||
|
|
55a50fac83 | ||
|
|
a7beed334f | ||
|
|
83274ad7a4 | ||
|
|
24056163dd | ||
|
|
79383ed693 | ||
|
|
d2da3f6e00 | ||
|
|
c40193c861 | ||
|
|
715835c12f | ||
|
|
0242de9145 | ||
|
|
b075f25d7c | ||
|
|
0b34b7a980 | ||
|
|
f291b24a7a | ||
|
|
9707fa34e4 | ||
|
|
cd19e0c9e4 | ||
|
|
38883b9550 | ||
|
|
f761733d0b | ||
|
|
842b157955 | ||
|
|
87f0e2be0e | ||
|
|
c3bea80ec7 | ||
|
|
a1529bc4e9 | ||
|
|
ccb7ede4fa | ||
|
|
1dbf831bda | ||
|
|
ea4d772dda | ||
|
|
25536e10ef | ||
|
|
51e30b2f7a | ||
|
|
47cb20f01e | ||
|
|
204ee72926 | ||
|
|
b9cbc1546c | ||
|
|
bc8892a237 | ||
|
|
b62950fa2b | ||
|
|
ab12c990bc | ||
|
|
abed4df973 | ||
|
|
76da9b1f18 | ||
|
|
11cbe3b7bb | ||
|
|
08b432775e | ||
|
|
49dbf4945f | ||
|
|
ff729608e1 | ||
|
|
b380d723b7 | ||
|
|
ed13644a02 | ||
|
|
8a90f562ef | ||
|
|
dfd791ecf9 | ||
|
|
8df16f28e7 | ||
|
|
1fb220c25e | ||
|
|
b24f892f60 | ||
|
|
5d81ed6a96 | ||
|
|
7ff79a0fdd | ||
|
|
7b4cf53ec4 | ||
|
|
9c7b47c277 | ||
|
|
547c7b8b70 | ||
|
|
1d70434ed1 | ||
|
|
06d53d350d | ||
|
|
742d7240f7 | ||
|
|
9b3ca76934 | ||
|
|
9f1c445214 | ||
|
|
075ba931ea | ||
|
|
29cbe48eb5 | ||
|
|
be1cc945a2 | ||
|
|
3e61d938bc | ||
|
|
0ee928cdce | ||
|
|
5d87fae906 | ||
|
|
afcc5ceb5b | ||
|
|
74d8e595f2 | ||
|
|
bc988181f9 | ||
|
|
1030654ce2 | ||
|
|
1c25143a75 | ||
|
|
39281811f5 | ||
|
|
2f07d22a9e | ||
|
|
1d1b9706ce | ||
|
|
7a19d444f1 | ||
|
|
73eb743f54 | ||
|
|
181ee74ba3 | ||
|
|
b8e9567501 | ||
|
|
dda64b301e | ||
|
|
af56551fd2 | ||
|
|
c55d0449cb | ||
|
|
0135476b68 | ||
|
|
e366b7c7a7 | ||
|
|
ca9a0b072e | ||
|
|
2f3035a08f | ||
|
|
cf5b0635e4 | ||
|
|
4db5c96781 | ||
|
|
e49948b512 | ||
|
|
1298d42b09 | ||
|
|
00e74dd2c8 | ||
|
|
10fe79c558 | ||
|
|
cddabebe86 | ||
|
|
9a7dac706c | ||
|
|
2e2998bb8b | ||
|
|
ce1352cb9f | ||
|
|
00007c20a7 | ||
|
|
cdaf3cb428 | ||
|
|
d640bb5a04 | ||
|
|
b1ebcb76f0 | ||
|
|
488dbb6715 | ||
|
|
f170157817 | ||
|
|
c094a26cbf | ||
|
|
366506555a | ||
|
|
9eb4043595 | ||
|
|
3359e78047 | ||
|
|
7ebafaf0fc | ||
|
|
fadd070663 | ||
|
|
27d291b0e9 | ||
|
|
f07f58733c | ||
|
|
b5521425ae | ||
|
|
b191ecd29e | ||
|
|
5989918300 | ||
|
|
f5720cf20e | ||
|
|
2106e48e0e | ||
|
|
1dd9e459c6 | ||
|
|
138b57b33d | ||
|
|
3845c55155 | ||
|
|
9aa2014e85 | ||
|
|
9239588757 | ||
|
|
5904b269e7 | ||
|
|
9bf3e31d6f | ||
|
|
618cb07ead | ||
|
|
1e3a39defc | ||
|
|
0aab548b87 | ||
|
|
489b93d5df | ||
|
|
cfb2a4d404 | ||
|
|
3b8ad132bc | ||
|
|
8510711e5d | ||
|
|
9918e903b2 | ||
|
|
6a292d6905 | ||
|
|
62926d6e28 | ||
|
|
51edf80e48 | ||
|
|
804f1f5610 | ||
|
|
3f0b14e48a | ||
|
|
3e0ce5544c | ||
|
|
933cbb72aa | ||
|
|
6bda5d5983 | ||
|
|
bfae8e7def | ||
|
|
96a91b97e9 | ||
|
|
12096a8fb3 | ||
|
|
e03d4d52c4 | ||
|
|
ea24d72f01 | ||
|
|
a4473ad739 | ||
|
|
08c28950f4 | ||
|
|
5cc8439f5b | ||
|
|
eb7fd4a015 | ||
|
|
dce609d141 | ||
|
|
f31360ecbf | ||
|
|
93e88d8b23 | ||
|
|
816cc0b17b | ||
|
|
1f73269480 | ||
|
|
f7d1b8821c | ||
|
|
cd5ad9f85b | ||
|
|
9c706f07f0 | ||
|
|
ea82925e14 | ||
|
|
1c5f208ef1 | ||
|
|
aeae0ba535 | ||
|
|
f59b227c44 | ||
|
|
4518e7056c | ||
|
|
565c6bafae | ||
|
|
584e8131cd | ||
|
|
20e958b1ee | ||
|
|
21ca3abc7e | ||
|
|
612ad32722 | ||
|
|
8ec07266b9 | ||
|
|
a9a7b0b317 | ||
|
|
e634e3e28f | ||
|
|
86de4b721f | ||
|
|
1d95a78e75 | ||
|
|
f5e44163be | ||
|
|
1ffc005479 | ||
|
|
31f67d412b | ||
|
|
cc62237ab5 | ||
|
|
f11d4a92df | ||
|
|
0be6249c2b | ||
|
|
a083fc9084 | ||
|
|
54172c441f | ||
|
|
b5f8b1014e | ||
|
|
df42c6176d | ||
|
|
7d0a34fceb | ||
|
|
b3e94b13f7 | ||
|
|
4eee908f2f | ||
|
|
1ebae5c284 | ||
|
|
361f03eb5f | ||
|
|
d8f54fc15a | ||
|
|
90b0f3201e | ||
|
|
b0d2374960 | ||
|
|
5c471e43dd | ||
|
|
c69169cbf9 | ||
|
|
f2c670dfd0 | ||
|
|
cfdd6dc0d9 | ||
|
|
d61b6c2faa | ||
|
|
e010995b19 | ||
|
|
053a1c1394 | ||
|
|
581184e2ae | ||
|
|
84e617b201 | ||
|
|
4ba21638b1 | ||
|
|
f92c5a214f | ||
|
|
180101400f | ||
|
|
ede10677f9 | ||
|
|
7627601ff8 | ||
|
|
cb120d2e75 | ||
|
|
ec86ccd956 | ||
|
|
63a657cac5 | ||
|
|
c3eb6bb972 | ||
|
|
eab5c0db12 | ||
|
|
0b9083915a | ||
|
|
051703234c | ||
|
|
6d555bcf84 | ||
|
|
d99fcd8e59 | ||
|
|
04eee919e8 | ||
|
|
0926c82878 | ||
|
|
79744d89ce | ||
|
|
214274f495 | ||
|
|
2425eb0ff8 | ||
|
|
c8931cde6e | ||
|
|
af698c7628 | ||
|
|
52745993cb | ||
|
|
7a8d23ba84 | ||
|
|
34559f0dbd | ||
|
|
b34a205ace | ||
|
|
799fbeba72 | ||
|
|
0e36abe1ad | ||
|
|
69ce07ef01 | ||
|
|
9f32e76a99 | ||
|
|
e89e48014c | ||
|
|
9863a95a71 | ||
|
|
dc0bf54401 | ||
|
|
3728f012d7 | ||
|
|
f904558315 | ||
|
|
a79556dfce | ||
|
|
901332dbee | ||
|
|
1ab75115f0 | ||
|
|
bc431b896b | ||
|
|
aa7a3c442c | ||
|
|
6825967cb9 | ||
|
|
cdc06a2b49 | ||
|
|
309c73a972 | ||
|
|
c4a3e5c4fd | ||
|
|
8d6cbe8e1e | ||
|
|
ff4e76b723 | ||
|
|
acdbc6b9a3 | ||
|
|
6714390890 | ||
|
|
249d00b285 | ||
|
|
e4ffdf6815 | ||
|
|
2228263b9f | ||
|
|
ee1c884ef1 | ||
|
|
0d29c75e7f | ||
|
|
7042f4bca8 | ||
|
|
ea42ed5381 | ||
|
|
ba6ca4a6bb | ||
|
|
ce68c1599f | ||
|
|
ce64601e38 | ||
|
|
b9f6351720 | ||
|
|
da8b31533a | ||
|
|
0591f106d3 | ||
|
|
40f9961541 | ||
|
|
5c8117539c | ||
|
|
af7400642b | ||
|
|
f8c5f31f97 | ||
|
|
5f2c2a8064 | ||
|
|
08aa53748e | ||
|
|
673485b5c4 | ||
|
|
18bea7edb2 | ||
|
|
cdf029bc84 | ||
|
|
31ce92fa9d | ||
|
|
f6b1666cd7 | ||
|
|
5f130bdda7 | ||
|
|
d619167c02 | ||
|
|
400932c6de | ||
|
|
8984ec3127 | ||
|
|
02076fadf4 | ||
|
|
1d93d5c687 | ||
|
|
5f028ea65f | ||
|
|
cf22ea2b78 | ||
|
|
58df3e692b | ||
|
|
80ca89b3f6 | ||
|
|
4209d91c43 | ||
|
|
79b878209d | ||
|
|
24cbe13ca7 | ||
|
|
f8fcbbea85 | ||
|
|
40d38ec0db | ||
|
|
f63f4e0aa3 | ||
|
|
d4b4c7bd71 | ||
|
|
bdef522da7 | ||
|
|
bb1ba1dbc4 | ||
|
|
2b880d322a | ||
|
|
60f62b2b50 | ||
|
|
b11d7be990 | ||
|
|
05d153e1d2 | ||
|
|
eaba45369b | ||
|
|
71adf31f7b | ||
|
|
d39d49fb8f | ||
|
|
7c91066618 | ||
|
|
57116c4f54 | ||
|
|
80e4d2329a | ||
|
|
7591843220 | ||
|
|
653afe9f8b | ||
|
|
8f007a70db | ||
|
|
0feea6091b | ||
|
|
b27b4bef44 | ||
|
|
2798a05e8e | ||
|
|
fe039f7b35 | ||
|
|
ea5dc4b7fc | ||
|
|
acc214d7c1 | ||
|
|
83c232ecb5 | ||
|
|
157875f7d5 | ||
|
|
ef00e57f72 | ||
|
|
8098ab50e8 | ||
|
|
ebb1044c43 | ||
|
|
751935e90b | ||
|
|
a81572914a | ||
|
|
e00f033ffd | ||
|
|
bf9414199c | ||
|
|
3011e9a804 | ||
|
|
a678f03284 | ||
|
|
11002c2881 | ||
|
|
2692bbaa63 | ||
|
|
1db6d7f32b | ||
|
|
61cce7e8e7 | ||
|
|
616a442fcb | ||
|
|
916519a43a | ||
|
|
af2f7a7a5a | ||
|
|
9ab9fcd577 | ||
|
|
853d1f4cfa | ||
|
|
cbcfdafef6 | ||
|
|
b156a27d1f | ||
|
|
f6ce6426f1 | ||
|
|
e12582c2c2 | ||
|
|
4d2cae0b0f | ||
|
|
35e0f27f52 | ||
|
|
77ddc83a04 | ||
|
|
3c83741b13 | ||
|
|
636c709671 | ||
|
|
f3f1b413b7 | ||
|
|
8eaad64dd6 | ||
|
|
f80ba6b87c | ||
|
|
5e5e3b5359 | ||
|
|
19203e976b | ||
|
|
2154607d11 | ||
|
|
072de1ea44 | ||
|
|
1818dad0d1 | ||
|
|
d51eab779c | ||
|
|
9f1ab6f961 | ||
|
|
0b875fc6f7 | ||
|
|
fd62938db0 | ||
|
|
4499ec6a22 | ||
|
|
dde20f4451 | ||
|
|
715b91ab96 | ||
|
|
7d26361680 | ||
|
|
b85a45d8f9 | ||
|
|
22ab5d334e | ||
|
|
acf124c81e | ||
|
|
51d81dea9f | ||
|
|
4a6066bb88 | ||
|
|
6ece16ccc9 | ||
|
|
0acab61f2e | ||
|
|
1cbd322105 | ||
|
|
ed9d26fd1b | ||
|
|
14e290c489 | ||
|
|
429b2b8a21 | ||
|
|
e7707c4826 | ||
|
|
290cbe6b55 | ||
|
|
d5708f24e6 | ||
|
|
3d273f041e | ||
|
|
22299c03cd | ||
|
|
0ea4b4400f | ||
|
|
b3c8337f83 | ||
|
|
a9e85f8765 | ||
|
|
b36799bf0c | ||
|
|
4d71a05d2a | ||
|
|
4fdf2a98bf | ||
|
|
880be03211 | ||
|
|
27495d5055 | ||
|
|
492e2e693c | ||
|
|
05a92ebd26 | ||
|
|
0d2e296eda | ||
|
|
ad25267ed7 | ||
|
|
1ed86899bb | ||
|
|
63c136a1ff | ||
|
|
3905b2b945 | ||
|
|
afaaf7d73a | ||
|
|
642b35582f | ||
|
|
117188769c | ||
|
|
bd7aad37e6 | ||
|
|
08b4e08820 | ||
|
|
aa4f360f59 | ||
|
|
2420375d56 | ||
|
|
bc5c738c25 | ||
|
|
ccc527f329 | ||
|
|
cf144aa2c1 | ||
|
|
086d924f06 | ||
|
|
24862f31b3 | ||
|
|
877eb4d423 | ||
|
|
a37a5fa1b5 | ||
|
|
2478a8f3cc | ||
|
|
3ed69d887f | ||
|
|
f0d440d204 | ||
|
|
3e18f812db | ||
|
|
8cf02fd59a | ||
|
|
06bfab3afa | ||
|
|
71e4697562 | ||
|
|
cf1bffe2f1 | ||
|
|
55a5fd49dc | ||
|
|
3f6637eb8f | ||
|
|
7373e281ac | ||
|
|
52b89455d7 | ||
|
|
bca7592c77 | ||
|
|
f6ab0bfe82 | ||
|
|
012a5c491d | ||
|
|
7666d6136d | ||
|
|
7bdda34f14 | ||
|
|
df21f89fcb | ||
|
|
f3b4cdca8a | ||
|
|
52460bf47b | ||
|
|
e674e7287e | ||
|
|
a20e8b6228 | ||
|
|
8d50e96dab | ||
|
|
1fe673951b | ||
|
|
3df5a9454e | ||
|
|
79fecd6b03 | ||
|
|
f3445c24b9 | ||
|
|
a3150d8505 | ||
|
|
828b5d8703 | ||
|
|
39559e203a | ||
|
|
605bdd0ea0 | ||
|
|
74945e03ce | ||
|
|
c772502af5 | ||
|
|
401d9db0f2 | ||
|
|
2306da94fe | ||
|
|
9f7ed11082 | ||
|
|
fff0efb095 | ||
|
|
9aa61f4bca | ||
|
|
bd70dc5966 | ||
|
|
ac6a3caa8f | ||
|
|
0914776152 | ||
|
|
45d0f43e90 | ||
|
|
7264367fa3 | ||
|
|
022fa34478 | ||
|
|
f7fd28fded | ||
|
|
e01a22de48 | ||
|
|
711c8e63c1 | ||
|
|
f186ec160a | ||
|
|
53fcfd13ee | ||
|
|
c684d66ec0 | ||
|
|
6496f750b0 | ||
|
|
6aaa47cccd | ||
|
|
1f6677d610 | ||
|
|
54b659aff0 | ||
|
|
b9db21309e | ||
|
|
8649c3b2b1 | ||
|
|
6bf6cc365b | ||
|
|
7c2d5448e8 | ||
|
|
a9f2ef7c10 | ||
|
|
f86bce970e | ||
|
|
6cbd618fb8 | ||
|
|
11787193ed | ||
|
|
45aae6810c | ||
|
|
7f6d571ef1 | ||
|
|
908dc4727c | ||
|
|
264759cfa0 | ||
|
|
b5d265526a | ||
|
|
22290eafb8 | ||
|
|
3101e57c36 | ||
|
|
b72a52232d | ||
|
|
a5b8e703fc | ||
|
|
fb26425f17 | ||
|
|
3114e20aef | ||
|
|
a52d2f4b7a | ||
|
|
34e484c377 | ||
|
|
08e8e9ff64 | ||
|
|
ebf55390eb | ||
|
|
c328144a58 | ||
|
|
4b583bea9b | ||
|
|
3f0ca412c6 | ||
|
|
0050b570b4 | ||
|
|
9405be03b0 | ||
|
|
b5b706fe06 | ||
|
|
8313d9fa90 | ||
|
|
93b96b3be7 | ||
|
|
50e6818f2b | ||
|
|
104fb57bd8 | ||
|
|
7ba6b2f00a | ||
|
|
d8e8939eca | ||
|
|
26c5f4049d | ||
|
|
b6c9dba0fc | ||
|
|
8badc1a354 |
202
.gitignore
vendored
202
.gitignore
vendored
@@ -1,202 +0,0 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
build/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
# Visual Studo 2015 cache/options directory
|
||||
.vs/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding addin-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
|
||||
# Others
|
||||
*.[Cc]ache
|
||||
ClientBin/
|
||||
[Ss]tyle[Cc]op.*
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
bower_components/
|
||||
lib/
|
||||
css/
|
||||
dist/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Other
|
||||
project.lock.json
|
||||
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
|
||||
<clear />
|
||||
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
11
README.md
11
README.md
@@ -1,11 +0,0 @@
|
||||
[](https://gitter.im/bitwarden/Lobby)
|
||||
|
||||
# bitwarden Web
|
||||
|
||||
The bitwarden Web project is an AngularJS application that powers the web vault (https://vault.bitwarden.com/).
|
||||
|
||||
# Contribute
|
||||
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
||||
|
||||
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature.
|
||||
3
apps/web/.dockerignore
Normal file
3
apps/web/.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
*
|
||||
!build/*
|
||||
!entrypoint.sh
|
||||
18
apps/web/.editorconfig
Normal file
18
apps/web/.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# Set default charset
|
||||
[*.{js,ts,scss,html}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{ts}]
|
||||
quote_type = single
|
||||
8
apps/web/.eslintignore
Normal file
8
apps/web/.eslintignore
Normal file
@@ -0,0 +1,8 @@
|
||||
**/dist
|
||||
**/build
|
||||
jslib
|
||||
webpack.config.js
|
||||
scripts/optimize.js
|
||||
config.js
|
||||
|
||||
**/node_modules
|
||||
31
apps/web/.eslintrc.json
Normal file
31
apps/web/.eslintrc.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"extends": ["./jslib/shared/eslintrc.json"],
|
||||
"rules": {
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"alphabetize": {
|
||||
"order": "asc"
|
||||
},
|
||||
"newlines-between": "always",
|
||||
"pathGroups": [
|
||||
{
|
||||
"pattern": "jslib-*/**",
|
||||
"group": "external",
|
||||
"position": "after"
|
||||
},
|
||||
{
|
||||
"pattern": "src/**/*",
|
||||
"group": "parent",
|
||||
"position": "before"
|
||||
}
|
||||
],
|
||||
"pathGroupsExcludedImportTypes": ["builtin"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
2
apps/web/.git-blame-ignore-revs
Normal file
2
apps/web/.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
||||
# Apply Prettier https://github.com/bitwarden/web/pull/1347
|
||||
56477eb39cfd8a73c9920577d24d75fed36e2cf5
|
||||
1
apps/web/.gitattributes
vendored
Normal file
1
apps/web/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
93
apps/web/.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
93
apps/web/.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
|
||||
Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests.
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: How can we reproduce the behavior.
|
||||
value: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. Click on '...'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Result
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: Actual Result
|
||||
description: A clear and concise description of what is happening.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots or Videos
|
||||
description: If applicable, add screenshots and/or a short video to help explain your problem.
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any other context about the problem here.
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: What operating system are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Windows
|
||||
- macOS
|
||||
- Linux
|
||||
- Android
|
||||
- iOS
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os-version
|
||||
attributes:
|
||||
label: Operating System Version
|
||||
description: What version of the operating system(s) are you seeing the problem on?
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: Web Browser
|
||||
description: What browser(s) are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Chrome
|
||||
- Safari
|
||||
- Microsoft Edge
|
||||
- Firefox
|
||||
- Opera
|
||||
- Brave
|
||||
- Vivaldi
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: browser-version
|
||||
attributes:
|
||||
label: Browser Version
|
||||
description: What version of the browser(s) are you seeing the problem on?
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Build Version
|
||||
description: What version of our software are you running? (Bottom of the page)
|
||||
validations:
|
||||
required: true
|
||||
14
apps/web/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
14
apps/web/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Feature Requests
|
||||
url: https://community.bitwarden.com/c/feature-requests/
|
||||
about: Request new features using the Community Forums. Please search existing feature requests before making a new one.
|
||||
- name: Bitwarden Community Forums
|
||||
url: https://community.bitwarden.com
|
||||
about: Please visit the community forums for general community discussion, support and the development roadmap.
|
||||
- name: Customer Support
|
||||
url: https://bitwarden.com/contact/
|
||||
about: Please contact our customer support for account issues and general customer support.
|
||||
- name: Security Issues
|
||||
url: https://hackerone.com/bitwarden
|
||||
about: We use HackerOne to manage security disclosures.
|
||||
28
apps/web/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
28
apps/web/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
## Type of change
|
||||
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature development
|
||||
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
||||
- [ ] Build/deploy pipeline (DevOps)
|
||||
- [ ] Other
|
||||
|
||||
## Objective
|
||||
|
||||
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
|
||||
|
||||
## Code changes
|
||||
|
||||
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
|
||||
<!--Also refer to any related changes or PRs in other repositories-->
|
||||
|
||||
- **file.ext:** Description of what was changed and why
|
||||
|
||||
## Screenshots
|
||||
|
||||
<!--Required for any UI changes. Delete if not applicable-->
|
||||
|
||||
## Before you submit
|
||||
|
||||
- [ ] I have checked for **linting** errors (`npm run lint`) (required)
|
||||
- [ ] This change requires a **documentation update** (notify the documentation team)
|
||||
- [ ] This change has particular **deployment requirements** (notify the DevOps team)
|
||||
525
apps/web/.github/workflows/build.yml
vendored
Normal file
525
apps/web/.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,525 @@
|
||||
---
|
||||
name: Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
custom_tag_extension:
|
||||
description: "Custom image tag extension"
|
||||
required: false
|
||||
push:
|
||||
branches-ignore:
|
||||
- "l10n_master"
|
||||
- "gh-pages"
|
||||
- "deploy"
|
||||
paths-ignore:
|
||||
- '.github/workflows/**'
|
||||
|
||||
|
||||
jobs:
|
||||
cloc:
|
||||
name: CLOC
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- name: Set up cloc
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt -y install cloc
|
||||
|
||||
- name: Print lines of code
|
||||
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
|
||||
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
|
||||
- name: Cache npm
|
||||
id: npm-cache
|
||||
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
|
||||
with:
|
||||
path: "~/.npm"
|
||||
key: ${{ runner.os }}-npm-lint-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run linter
|
||||
run: npm run lint
|
||||
|
||||
|
||||
setup:
|
||||
name: Setup
|
||||
runs-on: ubuntu-20.04
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.value }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- name: Get GitHub sha as version
|
||||
id: version
|
||||
run: echo "::set-output name=value::${GITHUB_SHA:0:7}"
|
||||
|
||||
|
||||
build-oss-selfhost:
|
||||
name: Build OSS zip
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- setup
|
||||
- lint
|
||||
env:
|
||||
_VERSION: ${{ needs.setup.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: "16"
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
whoami
|
||||
node --version
|
||||
npm --version
|
||||
gulp --version
|
||||
docker --version
|
||||
echo "GitHub ref: $GITHUB_REF"
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build OSS selfhost
|
||||
run: |
|
||||
npm run dist:oss:selfhost
|
||||
zip -r web-$_VERSION-selfhosted-open-source.zip build
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
||||
with:
|
||||
name: web-${{ env._VERSION }}-selfhosted-open-source.zip
|
||||
path: ./web-${{ env._VERSION }}-selfhosted-open-source.zip
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
build-cloud:
|
||||
name: Build Cloud zip
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- setup
|
||||
- lint
|
||||
env:
|
||||
_VERSION: ${{ needs.setup.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: "16"
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
whoami
|
||||
node --version
|
||||
npm --version
|
||||
gulp --version
|
||||
docker --version
|
||||
echo "GitHub ref: $GITHUB_REF"
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build Cloud
|
||||
run: |
|
||||
npm run dist:bit:cloud
|
||||
zip -r web-$_VERSION-cloud-COMMERCIAL.zip build
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
||||
with:
|
||||
name: web-${{ env._VERSION }}-cloud-COMMERCIAL.zip
|
||||
path: ./web-${{ env._VERSION }}-cloud-COMMERCIAL.zip
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
build-commercial-selfhost:
|
||||
name: Build SelfHost Docker image
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- setup
|
||||
- lint
|
||||
env:
|
||||
_VERSION: ${{ needs.setup.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: "16"
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
whoami
|
||||
node --version
|
||||
npm --version
|
||||
gulp --version
|
||||
docker --version
|
||||
echo "GitHub ref: $GITHUB_REF"
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
|
||||
- name: Setup DCT
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
|
||||
id: setup-dct
|
||||
uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff
|
||||
with:
|
||||
azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
azure-keyvault-name: "bitwarden-prod-kv"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
echo -e "# Building Web\n"
|
||||
echo "Building app"
|
||||
echo "npm version $(npm --version)"
|
||||
|
||||
npm run dist:bit:selfhost
|
||||
zip -r web-$_VERSION-selfhosted-COMMERCIAL.zip build
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
||||
with:
|
||||
name: web-${{ env._VERSION }}-selfhosted-COMMERCIAL.zip
|
||||
path: ./web-${{ env._VERSION }}-selfhosted-COMMERCIAL.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
echo -e "\nBuilding Docker image"
|
||||
docker --version
|
||||
docker build -t bitwarden/web .
|
||||
|
||||
- name: Tag rc branch
|
||||
if: github.ref == 'refs/heads/rc'
|
||||
run: docker tag bitwarden/web bitwarden/web:rc
|
||||
|
||||
- name: Tag dev
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: docker tag bitwarden/web bitwarden/web:dev
|
||||
|
||||
- name: Tag hotfix branch
|
||||
if: github.ref == 'refs/heads/hotfix-rc'
|
||||
run: docker tag bitwarden/web bitwarden/web:hotfix-rc
|
||||
|
||||
- name: List Docker images
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
|
||||
run: docker images
|
||||
|
||||
- name: Push rc image
|
||||
if: github.ref == 'refs/heads/rc'
|
||||
run: docker push bitwarden/web:rc
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
|
||||
|
||||
- name: Push dev image
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: docker push bitwarden/web:dev
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
|
||||
|
||||
- name: Push hotfix image
|
||||
if: github.ref == 'refs/heads/hotfix-rc'
|
||||
run: docker push bitwarden/web:hotfix-rc
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
|
||||
|
||||
- name: Log out of Docker
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
|
||||
run: |
|
||||
docker logout
|
||||
echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV
|
||||
|
||||
- name: Login to Azure - QA Subscription
|
||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
|
||||
|
||||
- name: Login to Azure ACR
|
||||
run: az acr login -n bitwardenqa
|
||||
|
||||
- name: Tag and Push RC to Azure ACR QA registry
|
||||
env:
|
||||
REGISTRY: bitwardenqa.azurecr.io
|
||||
run: |
|
||||
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name
|
||||
if [[ "$IMAGE_TAG" == "master" ]]; then
|
||||
IMAGE_TAG=dev
|
||||
fi
|
||||
docker tag bitwarden/web \
|
||||
$REGISTRY/web-sh:$IMAGE_TAG
|
||||
docker push $REGISTRY/web-sh:$IMAGE_TAG
|
||||
|
||||
- name: Log out of Docker
|
||||
run: docker logout
|
||||
|
||||
|
||||
build-qa:
|
||||
name: Build Docker images for QA environment
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- setup
|
||||
- lint
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: "16"
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
whoami
|
||||
node --version
|
||||
npm --version
|
||||
gulp --version
|
||||
docker --version
|
||||
echo "GitHub ref: $GITHUB_REF"
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
|
||||
- name: Login to Azure
|
||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
|
||||
|
||||
- name: Log into container registry
|
||||
run: az acr login -n bitwardenqa
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
echo -e "# Building Web\n"
|
||||
echo "Building app"
|
||||
echo "npm version $(npm --version)"
|
||||
VERSION=$( jq -r ".version" package.json)
|
||||
jq --arg version "$VERSION - ${GITHUB_SHA:0:7}" '.version = $version' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
|
||||
npm run build:bit:qa
|
||||
|
||||
echo "{\"commit_hash\": \"$GITHUB_SHA\", \"ref\": \"$GITHUB_REF\"}" | jq . > build/info.json
|
||||
|
||||
echo -e "\nBuilding Docker image"
|
||||
docker --version
|
||||
docker build -t bitwardenqa.azurecr.io/web .
|
||||
|
||||
- name: Get image tag
|
||||
id: image-tag
|
||||
run: |
|
||||
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g")
|
||||
TAG_EXTENSION=${{ github.event.inputs.custom_tag_extension }}
|
||||
|
||||
if [[ $TAG_EXTENSION ]]; then
|
||||
IMAGE_TAG=$IMAGE_TAG-$TAG_EXTENSION
|
||||
fi
|
||||
echo "::set-output name=value::$IMAGE_TAG"
|
||||
|
||||
- name: Tag image
|
||||
env:
|
||||
IMAGE_TAG: ${{ steps.image-tag.outputs.value }}
|
||||
run: docker tag bitwardenqa.azurecr.io/web "bitwardenqa.azurecr.io/web:$IMAGE_TAG"
|
||||
|
||||
- name: Tag dev
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: docker tag bitwardenqa.azurecr.io/web bitwardenqa.azurecr.io/web:dev
|
||||
|
||||
- name: List Docker images
|
||||
run: docker images
|
||||
|
||||
- name: Push image
|
||||
env:
|
||||
IMAGE_TAG: ${{ steps.image-tag.outputs.value }}
|
||||
run: docker push "bitwardenqa.azurecr.io/web:$IMAGE_TAG"
|
||||
|
||||
- name: Push dev images
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: docker push bitwardenqa.azurecr.io/web:dev
|
||||
|
||||
- name: Log out of Docker
|
||||
run: docker logout
|
||||
|
||||
|
||||
windows:
|
||||
name: Test code on Windows
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- name: Set up NuGet
|
||||
uses: nuget/setup-nuget@04b0c2b8d1b97922f67eca497d7cf0bf17b8ffe1
|
||||
with:
|
||||
nuget-version: "latest"
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: "16"
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
nuget help | grep Version
|
||||
node --version
|
||||
npm --version
|
||||
echo "GitHub ref: $GITHUB_REF"
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_EVENT: ${{ github.event_name }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: NPM build
|
||||
run: npm run build:bit:cloud
|
||||
|
||||
|
||||
crowdin-push:
|
||||
name: Crowdin Push
|
||||
if: github.ref == 'refs/heads/master'
|
||||
needs:
|
||||
- build-oss-selfhost
|
||||
- build-cloud
|
||||
- build-commercial-selfhost
|
||||
- build-qa
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
_CROWDIN_PROJECT_ID: "308189"
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||
|
||||
- name: Login to Azure
|
||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
||||
with:
|
||||
keyvault: "bitwarden-prod-kv"
|
||||
secrets: "crowdin-api-token"
|
||||
|
||||
- name: Upload Sources
|
||||
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
with:
|
||||
config: crowdin.yml
|
||||
crowdin_branch_name: master
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
|
||||
|
||||
check-failures:
|
||||
name: Check for failures
|
||||
if: always()
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- cloc
|
||||
- setup
|
||||
- lint
|
||||
- build-oss-selfhost
|
||||
- build-cloud
|
||||
- build-commercial-selfhost
|
||||
- build-qa
|
||||
- crowdin-push
|
||||
- windows
|
||||
steps:
|
||||
- name: Check if any job failed
|
||||
if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') }}
|
||||
env:
|
||||
CLOC_STATUS: ${{ needs.cloc.result }}
|
||||
LINT_STATUS: ${{ needs.lint.result }}
|
||||
SETUP_STATUS: ${{ needs.setup.result }}
|
||||
BUILD_OSS_SELFHOST_STATUS: ${{ needs.build-oss-selfhost.result }}
|
||||
BUILD_CLOUD_STATUS: ${{ needs.build-cloud.result }}
|
||||
BUILD_COMMERCIAL_SELFHOST_STATUS: ${{ needs.build-commercial-selfhost.result }}
|
||||
BUILD_QA_STATUS: ${{ needs.build-qa.result }}
|
||||
CROWDIN_PUSH_STATUS: ${{ needs.crowdin-push.result }}
|
||||
WINDOWS_STATUS: ${{ needs.windows.result }}
|
||||
run: |
|
||||
if [ "$CLOC_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$LINT_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$SETUP_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$BUILD_OSS_SELFHOST_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$BUILD_CLOUD_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$BUILD_COMMERCIAL_SELFHOST_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$BUILD_QA_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$CROWDIN_PUSH_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
elif [ "$WINDOWS_STATUS" = "failure" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Login to Azure - Prod Subscription
|
||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||
if: failure()
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
||||
if: failure()
|
||||
with:
|
||||
keyvault: "bitwarden-prod-kv"
|
||||
secrets: "devops-alerts-slack-webhook-url"
|
||||
|
||||
- name: Notify Slack on failure
|
||||
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
|
||||
if: failure()
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
49
apps/web/.github/workflows/crowdin-pull.yml
vendored
Normal file
49
apps/web/.github/workflows/crowdin-pull.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: Crowdin Pull
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
schedule:
|
||||
- cron: "0 0 * * 5"
|
||||
|
||||
jobs:
|
||||
crowdin-pull:
|
||||
name: Pull
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
_CROWDIN_PROJECT_ID: "308189"
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||
|
||||
- name: Login to Azure
|
||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
||||
with:
|
||||
keyvault: "bitwarden-prod-kv"
|
||||
secrets: "crowdin-api-token"
|
||||
|
||||
- name: Download translations
|
||||
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
with:
|
||||
config: crowdin.yml
|
||||
crowdin_branch_name: master
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
github_user_name: "github-actions"
|
||||
github_user_email: "<>"
|
||||
commit_message: "Autosync the updated translations"
|
||||
localization_branch_name: crowdin-auto-sync
|
||||
create_pull_request: true
|
||||
pull_request_title: "Autosync Crowdin Translations"
|
||||
pull_request_body: "Autosync the updated translations"
|
||||
16
apps/web/.github/workflows/enforce-labels.yml
vendored
Normal file
16
apps/web/.github/workflows/enforce-labels.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Enforce PR labels
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, unlabeled, opened, edited, synchronize]
|
||||
jobs:
|
||||
enforce-label:
|
||||
name: EnforceLabel
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Enforce Label
|
||||
uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb
|
||||
with:
|
||||
BANNED_LABELS: "hold"
|
||||
BANNED_LABELS_DESCRIPTION: "PRs on hold cannot be merged"
|
||||
71
apps/web/.github/workflows/qa-deploy.yml
vendored
Normal file
71
apps/web/.github/workflows/qa-deploy.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: QA Deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
image_extension:
|
||||
description: "Image tag extension"
|
||||
required: false
|
||||
|
||||
env:
|
||||
_QA_CLUSTER_RESOURCE_GROUP: "bw-env-qa"
|
||||
_QA_CLUSTER_NAME: "bw-aks-qa"
|
||||
_QA_K8S_NAMESPACE: "bw-qa"
|
||||
_QA_K8S_APP_NAME: "bw-web"
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy QA Web
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||
|
||||
- name: Setup
|
||||
run: export PATH=$PATH:~/work/web/web
|
||||
|
||||
- name: Login to Azure
|
||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
||||
with:
|
||||
keyvault: "bitwarden-qa-kv"
|
||||
secrets: "qa-aks-kubectl-credentials"
|
||||
|
||||
- name: Login with qa-aks-kubectl-credentials SP
|
||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||
with:
|
||||
creds: ${{ env.qa-aks-kubectl-credentials }}
|
||||
|
||||
- name: Setup AKS access
|
||||
#env:
|
||||
# USER_ID: ${{ env.qa-kubectl-managed-identity-clientId }}
|
||||
run: |
|
||||
echo "---az install---"
|
||||
az aks install-cli --install-location ./kubectl --kubelogin-install-location ./kubelogin
|
||||
echo "---az get-creds---"
|
||||
az aks get-credentials -n $_QA_CLUSTER_NAME -g $_QA_CLUSTER_RESOURCE_GROUP
|
||||
|
||||
- name: Get image tag
|
||||
id: image_tag
|
||||
run: |
|
||||
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g")
|
||||
TAG_EXTENSION=${{ github.event.inputs.image_extension }}
|
||||
|
||||
if [[ $TAG_EXTENSION ]]; then
|
||||
IMAGE_TAG=$IMAGE_TAG-$TAG_EXTENSION
|
||||
fi
|
||||
echo "::set-output name=value::$IMAGE_TAG"
|
||||
|
||||
- name: Deploy Web image
|
||||
env:
|
||||
IMAGE_TAG: ${{ steps.image_tag.outputs.value }}
|
||||
run: |
|
||||
kubectl set image -n $_QA_K8S_NAMESPACE deployment/web web=bitwardenqa.azurecr.io/web:$IMAGE_TAG --record
|
||||
kubectl rollout restart -n $_QA_K8S_NAMESPACE deployment/web
|
||||
kubectl rollout status deployment/web -n $_QA_K8S_NAMESPACE
|
||||
334
apps/web/.github/workflows/release.yml
vendored
Normal file
334
apps/web/.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,334 @@
|
||||
---
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_type:
|
||||
description: 'Release Options'
|
||||
required: true
|
||||
default: 'Initial Release'
|
||||
type: choice
|
||||
options:
|
||||
- Initial Release
|
||||
- Redeploy
|
||||
- Dry Run
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
name: Setup
|
||||
runs-on: ubuntu-20.04
|
||||
outputs:
|
||||
release_version: ${{ steps.version.outputs.version }}
|
||||
tag_version: ${{ steps.version.outputs.version }}
|
||||
branch_name: ${{ steps.branch.outputs.branch_name }}
|
||||
steps:
|
||||
- name: Branch check
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
run: |
|
||||
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
|
||||
echo "==================================="
|
||||
echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches"
|
||||
echo "==================================="
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # 2.4.0
|
||||
|
||||
- name: Check Release Version
|
||||
id: version
|
||||
uses: bitwarden/gh-actions/release-version-check@ea9fab01d76940267b4147cc1c4542431246b9f6
|
||||
with:
|
||||
release-type: ${{ github.event.inputs.release_type }}
|
||||
project-type: ts
|
||||
file: package.json
|
||||
|
||||
- name: Get branch name
|
||||
id: branch
|
||||
run: |
|
||||
BRANCH_NAME=$(basename ${{ github.ref }})
|
||||
echo "::set-output name=branch_name::$BRANCH_NAME"
|
||||
|
||||
|
||||
self-host:
|
||||
name: Release self-host docker
|
||||
runs-on: ubuntu-20.04
|
||||
needs: setup
|
||||
env:
|
||||
_BRANCH_NAME: ${{ needs.setup.outputs.branch_name }}
|
||||
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
|
||||
_RELEASE_OPTION: ${{ github.event.inputs.release_type }}
|
||||
steps:
|
||||
- name: Print environment
|
||||
run: |
|
||||
whoami
|
||||
docker --version
|
||||
echo "GitHub ref: $GITHUB_REF"
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
echo "Github Release Option: $_RELEASE_OPTION"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||
|
||||
########## DockerHub ##########
|
||||
- name: Setup DCT
|
||||
id: setup-dct
|
||||
uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff
|
||||
with:
|
||||
azure-creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
azure-keyvault-name: "bitwarden-prod-kv"
|
||||
|
||||
- name: Pull latest selfhost image
|
||||
run: |
|
||||
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
|
||||
docker pull bitwarden/web:latest
|
||||
else
|
||||
docker pull bitwarden/web:$_BRANCH_NAME
|
||||
fi
|
||||
|
||||
- name: Tag version and latest
|
||||
run: |
|
||||
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
|
||||
docker tag bitwarden/web:latest bitwarden/web:dryrun
|
||||
else
|
||||
docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:$_RELEASE_VERSION
|
||||
docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:latest
|
||||
fi
|
||||
|
||||
- name: Push version and latest image
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }}
|
||||
run: |
|
||||
docker push bitwarden/web:$_RELEASE_VERSION
|
||||
docker push bitwarden/web:latest
|
||||
|
||||
- name: Log out of Docker and disable Docker Notary
|
||||
run: |
|
||||
docker logout
|
||||
echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV
|
||||
|
||||
########## ACR ##########
|
||||
- name: Login to Azure - QA Subscription
|
||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
|
||||
|
||||
- name: Login to Azure ACR
|
||||
run: az acr login -n bitwardenqa
|
||||
|
||||
- name: Tag version and latest
|
||||
env:
|
||||
REGISTRY: bitwardenqa.azurecr.io
|
||||
run: |
|
||||
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
|
||||
docker tag bitwarden/web:latest $REGISTRY/web:dryrun
|
||||
else
|
||||
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web:$_RELEASE_VERSION
|
||||
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web:latest
|
||||
|
||||
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web-sh:$_RELEASE_VERSION
|
||||
docker tag bitwarden/web:$_BRANCH_NAME $REGISTRY/web-sh:latest
|
||||
fi
|
||||
|
||||
- name: Push version and latest image
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
env:
|
||||
REGISTRY: bitwardenqa.azurecr.io
|
||||
run: |
|
||||
docker push $REGISTRY/web:$_RELEASE_VERSION
|
||||
docker push $REGISTRY/web:latest
|
||||
|
||||
docker push $REGISTRY/web-sh:$_RELEASE_VERSION
|
||||
docker push $REGISTRY/web-sh:latest
|
||||
|
||||
- name: Log out of Docker
|
||||
run: docker logout
|
||||
|
||||
|
||||
ghpages-deploy:
|
||||
name: Deploy Web Vault to GitHub Pages
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- setup
|
||||
- self-host
|
||||
env:
|
||||
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
|
||||
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
||||
with:
|
||||
ref: gh-pages
|
||||
|
||||
- name: Create gh-pages-deploy branch
|
||||
run: |
|
||||
git switch -c gh-pages-deploy-$_TAG_VERSION
|
||||
git push -u origin gh-pages-deploy-$_TAG_VERSION
|
||||
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
||||
|
||||
- name: Setup git config
|
||||
run: |
|
||||
git config user.name = "GitHub Action Bot"
|
||||
git config user.email = "<>"
|
||||
git config --global url."https://github.com/".insteadOf ssh://git@github.com/
|
||||
git config --global url."https://".insteadOf ssh://
|
||||
|
||||
- name: Download latest cloud asset
|
||||
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
|
||||
with:
|
||||
workflow: build.yml
|
||||
workflow_conclusion: success
|
||||
branch: ${{ needs.setup.outputs.branch_name }}
|
||||
artifacts: web-*-cloud-COMMERCIAL.zip
|
||||
|
||||
# This should result in a build directory in the current working directory
|
||||
- name: Unzip build asset
|
||||
run: unzip web-*-cloud-COMMERCIAL.zip
|
||||
|
||||
- name: Deploy GitHub Pages
|
||||
uses: crazy-max/ghaction-github-pages@a117e4aa1fb4854d021546d2abdfac95be568a3a # v2.6.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
target_branch: gh-pages-deploy-${{ needs.setup.outputs.tag_version }}
|
||||
build_dir: build
|
||||
keep_history: true
|
||||
commit_message: "Staging deploy ${{ needs.setup.outputs.release_version }}"
|
||||
dry_run: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
||||
|
||||
- name: Create GitHub Pages Deploy PR
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
env:
|
||||
PR_BRANCH: gh-pages-deploy-${{ env._TAG_VERSION }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh pr create --title "Deploy $_RELEASE_VERSION to GitHub Pages" \
|
||||
--body "Deploying $_RELEASE_VERSION" \
|
||||
--base gh-pages \
|
||||
--head "$PR_BRANCH"
|
||||
|
||||
|
||||
cfpages-deploy:
|
||||
name: Deploy Web Vault to CloudFlare Pages branch
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- setup
|
||||
- self-host
|
||||
env:
|
||||
_RELEASE_VERSION: ${{ needs.setup.outputs.release_version }}
|
||||
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
||||
|
||||
- name: Download latest cloud asset
|
||||
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
|
||||
with:
|
||||
workflow: build.yml
|
||||
workflow_conclusion: success
|
||||
branch: ${{ needs.setup.outputs.branch_name }}
|
||||
artifacts: web-*-cloud-COMMERCIAL.zip
|
||||
|
||||
# This should result in a build directory in the current working directory
|
||||
- name: Unzip build asset
|
||||
run: unzip web-*-cloud-COMMERCIAL.zip
|
||||
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
||||
with:
|
||||
ref: deploy
|
||||
path: deployment
|
||||
|
||||
- name: Setup git config
|
||||
run: |
|
||||
git config --global user.name = "GitHub Action Bot"
|
||||
git config --global user.email = "<>"
|
||||
git config --global url."https://github.com/".insteadOf ssh://git@github.com/
|
||||
git config --global url."https://".insteadOf ssh://
|
||||
|
||||
- name: Deploy CloudFlare Pages
|
||||
run: |
|
||||
rm -rf ./*
|
||||
cp -R ../build/* .
|
||||
working-directory: deployment
|
||||
|
||||
- name: Create cf-pages-deploy branch
|
||||
run: |
|
||||
git switch -c cf-pages-deploy-$_TAG_VERSION
|
||||
git add .
|
||||
git commit -m "Staging deploy ${{ needs.setup.outputs.release_version }}"
|
||||
git push -u origin cf-pages-deploy-$_TAG_VERSION
|
||||
working-directory: deployment
|
||||
|
||||
- name: Create CloudFlare Pages Deploy PR
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
env:
|
||||
PR_BRANCH: cf-pages-deploy-${{ env._TAG_VERSION }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh pr create --title "Deploy $_RELEASE_VERSION to CloudFlare Pages" \
|
||||
--body "Deploying $_RELEASE_VERSION" \
|
||||
--base deploy \
|
||||
--head "$PR_BRANCH"
|
||||
|
||||
|
||||
release:
|
||||
name: Create GitHub Release
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- setup
|
||||
- self-host
|
||||
- ghpages-deploy
|
||||
- cfpages-deploy
|
||||
steps:
|
||||
- name: Download latest build artifacts
|
||||
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
|
||||
with:
|
||||
workflow: build.yml
|
||||
workflow_conclusion: success
|
||||
branch: ${{ needs.setup.outputs.branch_name }}
|
||||
artifacts: "web-*-selfhosted-COMMERCIAL.zip,
|
||||
web-*-selfhosted-open-source.zip"
|
||||
|
||||
- name: Rename assets
|
||||
run: |
|
||||
mv web-*-selfhosted-COMMERCIAL.zip web-${{ needs.setup.outputs.release_version }}-selfhosted-COMMERCIAL.zip
|
||||
mv web-*-selfhosted-open-source.zip web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip
|
||||
|
||||
- name: Create release
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01
|
||||
with:
|
||||
name: "Version ${{ needs.setup.outputs.release_version }}"
|
||||
commit: ${{ github.sha }}
|
||||
tag: "${{ needs.setup.outputs.tag_version }}"
|
||||
body: "<insert release notes here>"
|
||||
artifacts: "web-${{ needs.setup.outputs.release_version }}-selfhosted-COMMERCIAL.zip,
|
||||
web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
draft: true
|
||||
|
||||
|
||||
dry-run:
|
||||
name: Dry Run Cleanup
|
||||
runs-on: ubuntu-20.04
|
||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
||||
env:
|
||||
_TAG_VERSION: ${{ needs.setup.outputs.tag_version }}
|
||||
needs:
|
||||
- setup
|
||||
- release
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # 2.4.0
|
||||
|
||||
- name: Remove gh-pages-deploy branch
|
||||
run: git push origin --delete gh-pages-deploy-$_TAG_VERSION
|
||||
|
||||
- name: Remove cf-pages-deploy branch
|
||||
run: git push origin --delete cf-pages-deploy-$_TAG_VERSION
|
||||
71
apps/web/.github/workflows/version-bump.yml
vendored
Normal file
71
apps/web/.github/workflows/version-bump.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: Version Bump
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_number:
|
||||
description: "New Version"
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
bump_props_version:
|
||||
name: "Create version_bump_${{ github.event.inputs.version_number }} branch"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||
|
||||
- name: Create Version Branch
|
||||
run: |
|
||||
git switch -c version_bump_${{ github.event.inputs.version_number }}
|
||||
git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
||||
|
||||
- name: Checkout Version Branch
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||
with:
|
||||
ref: version_bump_${{ github.event.inputs.version_number }}
|
||||
|
||||
- name: Bump Version - package.json
|
||||
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||
with:
|
||||
version: ${{ github.event.inputs.version_number }}
|
||||
file_path: "./package.json"
|
||||
|
||||
- name: Bump Version - package-lock.json
|
||||
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||
with:
|
||||
version: ${{ github.event.inputs.version_number }}
|
||||
file_path: "./package-lock.json"
|
||||
|
||||
- name: Commit files
|
||||
run: |
|
||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
|
||||
|
||||
- name: Push changes
|
||||
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
||||
|
||||
- name: Create Version PR
|
||||
env:
|
||||
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
BASE_BRANCH: master
|
||||
TITLE: "Bump version to ${{ github.event.inputs.version_number }}"
|
||||
run: |
|
||||
gh pr create --title "$TITLE" \
|
||||
--base "$BASE" \
|
||||
--head "$PR_BRANCH" \
|
||||
--label "version update" \
|
||||
--label "automated pr" \
|
||||
--body "
|
||||
## Type of change
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature development
|
||||
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
||||
- [ ] Build/deploy pipeline (DevOps)
|
||||
- [X] Other
|
||||
|
||||
## Objective
|
||||
Automated version bump to ${{ github.event.inputs.version_number }}"
|
||||
11
apps/web/.github/workflows/workflow-linter.yml
vendored
Normal file
11
apps/web/.github/workflows/workflow-linter.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Workflow Linter
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/**
|
||||
|
||||
jobs:
|
||||
call-workflow:
|
||||
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master
|
||||
15
apps/web/.gitignore
vendored
Normal file
15
apps/web/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
.vs
|
||||
.idea
|
||||
.DS_Store
|
||||
node_modules
|
||||
npm-debug.log
|
||||
vwd.webinfo
|
||||
css/
|
||||
dist/
|
||||
*.pem
|
||||
*.crx
|
||||
*.zip
|
||||
*.swp
|
||||
build/
|
||||
!dev-server.shared.pem
|
||||
config/local.json
|
||||
4
apps/web/.husky/pre-commit
Normal file
4
apps/web/.husky/pre-commit
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
12
apps/web/.prettierignore
Normal file
12
apps/web/.prettierignore
Normal file
@@ -0,0 +1,12 @@
|
||||
# Build directories
|
||||
build
|
||||
dist
|
||||
|
||||
#jslib
|
||||
|
||||
# External libraries / auto synced locales
|
||||
src/locales
|
||||
src/404/*.min.css
|
||||
|
||||
# Github Workflows
|
||||
.github/workflows
|
||||
3
apps/web/.prettierrc.json
Normal file
3
apps/web/.prettierrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"printWidth": 100
|
||||
}
|
||||
36
apps/web/CONTRIBUTING.md
Normal file
36
apps/web/CONTRIBUTING.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# How to Contribute
|
||||
|
||||
Contributions of all kinds are welcome!
|
||||
|
||||
Please visit our [Community Forums](https://community.bitwarden.com/) for general community discussion and the development roadmap.
|
||||
|
||||
Here is how you can get involved:
|
||||
|
||||
- **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
|
||||
- **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
|
||||
- **Report a bug or submit a bugfix:** Use Github issues and pull requests
|
||||
- **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
|
||||
- **Help other users:** Go to the [Ask the Bitwarden Community category](https://community.bitwarden.com/c/support/) on the Community Forums
|
||||
- **Translate:** See the localization (l10n) section below
|
||||
|
||||
## Contributor Agreement
|
||||
|
||||
Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/web) if you intend on contributing to any Github repository. Pull requests cannot be accepted and merged unless the author has signed the Contributor Agreement.
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
- use `npm run lint` and fix any linting suggestions before submitting a pull request
|
||||
- commit any pull requests against the `master` branch
|
||||
- include a link to your Community Forums post
|
||||
|
||||
# Localization (l10n)
|
||||
|
||||
[](https://crowdin.com/project/bitwarden-web)
|
||||
|
||||
We use a translation tool called [Crowdin](https://crowdin.com) to help manage our localization efforts across many different languages.
|
||||
|
||||
If you are interested in helping translate the Bitwarden web vault into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-web
|
||||
|
||||
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/dwbit).
|
||||
|
||||
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/
|
||||
20
apps/web/Dockerfile
Normal file
20
apps/web/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM bitwarden/server
|
||||
|
||||
LABEL com.bitwarden.product="bitwarden"
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
gosu \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV ASPNETCORE_URLS http://+:5000
|
||||
WORKDIR /app
|
||||
EXPOSE 5000
|
||||
COPY ./build .
|
||||
COPY entrypoint.sh /
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
HEALTHCHECK CMD curl -f http://localhost:5000 || exit 1
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
17
apps/web/LICENSE.txt
Normal file
17
apps/web/LICENSE.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
Source code in this repository is covered by one of two licenses: (i) the
|
||||
GNU General Public License (GPL) v3.0 (ii) the Bitwarden License v1.0. The
|
||||
default license throughout the repository is GPL v3.0 unless the header
|
||||
specifies another license. Bitwarden Licensed code is found only in the
|
||||
/bitwarden_license directory.
|
||||
|
||||
GPL v3.0:
|
||||
https://github.com/bitwarden/web/blob/master/LICENSE_GPL.txt
|
||||
|
||||
Bitwarden License v1.0:
|
||||
https://github.com/bitwarden/web/blob/master/LICENSE_BITWARDEN.txt
|
||||
|
||||
No grant of any rights in the trademarks, service marks, or logos of Bitwarden is
|
||||
made (except as may be necessary to comply with the notice requirements as
|
||||
applicable), and use of any Bitwarden trademarks must comply with Bitwarden
|
||||
Trademark Guidelines
|
||||
<https://github.com/bitwarden/server/blob/master/TRADEMARK_GUIDELINES.md>.
|
||||
182
apps/web/LICENSE_BITWARDEN.txt
Normal file
182
apps/web/LICENSE_BITWARDEN.txt
Normal file
@@ -0,0 +1,182 @@
|
||||
BITWARDEN LICENSE AGREEMENT
|
||||
Version 1, 4 September 2020
|
||||
|
||||
PLEASE CAREFULLY READ THIS BITWARDEN LICENSE AGREEMENT ("AGREEMENT"). THIS
|
||||
AGREEMENT CONSTITUTES A LEGALLY BINDING AGREEMENT BETWEEN YOU AND BITWARDEN,
|
||||
INC. ("BITWARDEN") AND GOVERNS YOUR USE OF THE COMMERCIAL MODULES (DEFINED
|
||||
BELOW). BY COPYING OR USING THE COMMERCIAL MODULES, YOU AGREE TO THIS AGREEMENT.
|
||||
IF YOU DO NOT AGREE WITH THIS AGREEMENT, YOU MAY NOT COPY OR USE THE COMMERCIAL
|
||||
MODULES. IF YOU ARE COPYING OR USING THE COMMERCIAL MODULES ON BEHALF OF A LEGAL
|
||||
ENTITY, YOU REPRESENT AND WARRANT THAT YOU HAVE AUTHORITY TO AGREE TO THIS
|
||||
AGREEMENT ON BEHALF OF SUCH ENTITY. IF YOU DO NOT HAVE SUCH AUTHORITY, DO NOT
|
||||
COPY OR USE THE COMMERCIAL MODULES IN ANY MANNER.
|
||||
|
||||
This Agreement is entered into by and between Bitwarden and you, or the legal
|
||||
entity on behalf of whom you are acting (as applicable, "You" or "Your").
|
||||
|
||||
1. DEFINITIONS
|
||||
|
||||
"Bitwarden Software" means the Bitwarden server software, libraries, and
|
||||
Commercial Modules.
|
||||
|
||||
"Commercial Modules" means the modules designed to work with and enhance the
|
||||
Bitwarden Software to which this Agreement is linked, referenced, or appended.
|
||||
|
||||
2. LICENSES, RESTRICTIONS AND THIRD PARTY CODE
|
||||
|
||||
2.1 Commercial Module License. Subject to Your compliance with this Agreement,
|
||||
Bitwarden hereby grants to You a limited, non-exclusive, non-transferable,
|
||||
royalty-free license to use the Commercial Modules for the sole purposes of
|
||||
internal development and internal testing, and only in a non-production
|
||||
environment.
|
||||
|
||||
2.2 Reservation of Rights. As between Bitwarden and You, Bitwarden owns all
|
||||
right, title and interest in and to the Bitwarden Software, and except as
|
||||
expressly set forth in Sections 2.1, no other license to the Bitwarden Software
|
||||
is granted to You under this Agreement, by implication, estoppel, or otherwise.
|
||||
|
||||
2.3 Restrictions. You agree not to: (i) except as expressly permitted in
|
||||
Section 2.1, sell, rent, lease, distribute, sublicense, loan or otherwise
|
||||
transfer the Commercial Modules to any third party; (ii) alter or remove any
|
||||
trademarks, service mark, and logo included with the Commercial Modules, or
|
||||
(iii) use the Commercial Modules to create a competing product or service.
|
||||
Bitwarden is not obligated to provide maintenance and support services for the
|
||||
Bitwarden Software licensed under this Agreement.
|
||||
|
||||
2.4 Third Party Software. The Commercial Modules may contain or be provided
|
||||
with third party open source libraries, components, utilities and other open
|
||||
source software (collectively, "Open Source Software"). Notwithstanding anything
|
||||
to the contrary herein, use of the Open Source Software will be subject to the
|
||||
license terms and conditions applicable to such Open Source Software. To the
|
||||
extent any condition of this Agreement conflicts with any license to the Open
|
||||
Source Software, the Open Source Software license will govern with respect to
|
||||
such Open Source Software only.
|
||||
|
||||
2.5 This Agreement does not grant any rights in the trademarks, service marks, or
|
||||
logos of any Contributor (except as may be necessary to comply with the notice
|
||||
requirements in Section 2.3), and use of any Bitwarden trademarks must comply with
|
||||
Bitwarden Trademark Guidelines
|
||||
<https://github.com/bitwarden/server/blob/master/TRADEMARK_GUIDELINES.md>.
|
||||
|
||||
3. TERMINATION
|
||||
|
||||
3.1 Termination. This Agreement will automatically terminate upon notice from
|
||||
Bitwarden, which notice may be by email or posting in the location where the
|
||||
Commercial Modules are made available.
|
||||
|
||||
3.2 Effect of Termination. Upon any termination of this Agreement, for any
|
||||
reason, You will promptly cease use of the Commercial Modules and destroy any
|
||||
copies thereof. For the avoidance of doubt, termination of this Agreement will
|
||||
not affect Your right to Bitwarden Software, other than the Commercial Modules,
|
||||
made available pursuant to an Open Source Software license.
|
||||
|
||||
3.3 Survival. Sections 1, 2.2 -2.4, 3.2, 3.3, 4, and 5 will survive any
|
||||
termination of this Agreement.
|
||||
|
||||
4. DISCLAIMER AND LIMITATION OF LIABILITY
|
||||
|
||||
4.1 Disclaimer of Warranties. TO THE MAXIMUM EXTENT PERMITTED UNDER APPLICABLE
|
||||
LAW, THE BITWARDEN SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED REGARDING OR RELATING TO THE BITWARDEN SOFTWARE, INCLUDING
|
||||
ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
TITLE, AND NON-INFRINGEMENT. FURTHER, BITWARDEN DOES NOT WARRANT RESULTS OF USE
|
||||
OR THAT THE BITWARDEN SOFTWARE WILL BE ERROR FREE OR THAT THE USE OF THE
|
||||
BITWARDEN SOFTWARE WILL BE UNINTERRUPTED.
|
||||
|
||||
4.2 Limitation of Liability. IN NO EVENT WILL BITWARDEN OR ITS LICENSORS BE
|
||||
LIABLE TO YOU OR ANY THIRD PARTY UNDER THIS AGREEMENT FOR (I) ANY AMOUNTS IN
|
||||
EXCESS OF US $25 OR (II) FOR ANY SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OF
|
||||
ANY KIND, INCLUDING FOR ANY LOSS OF PROFITS, LOSS OF USE, BUSINESS INTERRUPTION,
|
||||
LOSS OF DATA, COST OF SUBSTITUTE GOODS OR SERVICES, WHETHER ALLEGED AS A BREACH
|
||||
OF CONTRACT OR TORTIOUS CONDUCT, INCLUDING NEGLIGENCE, EVEN IF BITWARDEN HAS
|
||||
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
5. MISCELLANEOUS
|
||||
|
||||
5.1 Assignment. You may not assign or otherwise transfer this Agreement or any
|
||||
rights or obligations hereunder, in whole or in part, whether by operation of
|
||||
law or otherwise, to any third party without Bitwarden's prior written consent.
|
||||
Any purported transfer, assignment or delegation without such prior written
|
||||
consent will be null and void and of no force or effect. Bitwarden may assign
|
||||
this Agreement to any successor to its business or assets to which this
|
||||
Agreement relates, whether by merger, sale of assets, sale of stock,
|
||||
reorganization or otherwise. Subject to this Section 5.1, this Agreement will be
|
||||
binding upon and inure to the benefit of the parties hereto, and their
|
||||
respective successors and permitted assigns.
|
||||
|
||||
5.2 Entire Agreement; Modification; Waiver. This Agreement represents the
|
||||
entire agreement between the parties, and supersedes all prior agreements and
|
||||
understandings, written or oral, with respect to the matters covered by this
|
||||
Agreement, and is not intended to confer upon any third party any rights or
|
||||
remedies hereunder. You acknowledge that You have not entered in this Agreement
|
||||
based on any representations other than those contained herein. No modification
|
||||
of or amendment to this Agreement, nor any waiver of any rights under this
|
||||
Agreement, will be effective unless in writing and signed by both parties. The
|
||||
waiver of one breach or default or any delay in exercising any rights will not
|
||||
constitute a waiver of any subsequent breach or default.
|
||||
|
||||
5.3 Governing Law. This Agreement will in all respects be governed by the laws
|
||||
of the State of California without reference to its principles of conflicts of
|
||||
laws. The parties hereby agree that all disputes arising out of this Agreement
|
||||
will be subject to the exclusive jurisdiction of and venue in the federal and
|
||||
state courts within Los Angeles County, California. You hereby consent to the
|
||||
personal and exclusive jurisdiction and venue of these courts. The parties
|
||||
hereby disclaim and exclude the application hereto of the United Nations
|
||||
Convention on Contracts for the International Sale of Goods.
|
||||
|
||||
5.4 Severability. If any provision of this Agreement is held invalid or
|
||||
unenforceable under applicable law by a court of competent jurisdiction, it will
|
||||
be replaced with the valid provision that most closely reflects the intent of
|
||||
the parties and the remaining provisions of the Agreement will remain in full
|
||||
force and effect.
|
||||
|
||||
5.5 Relationship of the Parties. Nothing in this Agreement is to be construed
|
||||
as creating an agency, partnership, or joint venture relationship between the
|
||||
parties hereto. Neither party will have any right or authority to assume or
|
||||
create any obligations or to make any representations or warranties on behalf of
|
||||
any other party, whether express or implied, or to bind the other party in any
|
||||
respect whatsoever.
|
||||
|
||||
5.6 Notices. All notices permitted or required under this Agreement will be in
|
||||
writing and will be deemed to have been given when delivered in person
|
||||
(including by overnight courier), or three (3) business days after being mailed
|
||||
by first class, registered or certified mail, postage prepaid, to the address of
|
||||
the party specified in this Agreement or such other address as either party may
|
||||
specify in writing.
|
||||
|
||||
5.7 U.S. Government Restricted Rights. If Commercial Modules is being licensed
|
||||
by the U.S. Government, the Commercial Modules is deemed to be "commercial
|
||||
computer software" and "commercial computer documentation" developed exclusively
|
||||
at private expense, and (a) if acquired by or on behalf of a civilian agency,
|
||||
will be subject solely to the terms of this computer software license as
|
||||
specified in 48 C.F.R. 12.212 of the Federal Acquisition Regulations and its
|
||||
successors; and (b) if acquired by or on behalf of units of the Department of
|
||||
Defense ("DOD") will be subject to the terms of this commercial computer
|
||||
software license as specified in 48 C.F.R. 227.7202-2, DOD FAR Supplement and
|
||||
its successors.
|
||||
|
||||
5.8 Injunctive Relief. A breach or threatened breach by You of Section 2 may
|
||||
cause irreparable harm for which damages at law may not provide adequate relief,
|
||||
and therefore Bitwarden will be entitled to seek injunctive relief in any
|
||||
applicable jurisdiction without being required to post a bond.
|
||||
|
||||
5.9 Export Law Assurances. You understand that the Commercial Modules is
|
||||
subject to export control laws and regulations. You may not download or
|
||||
otherwise export or re-export the Commercial Modules or any underlying
|
||||
information or technology except in full compliance with all applicable laws and
|
||||
regulations, in particular, but without limitation, United States export control
|
||||
laws. None of the Commercial Modules or any underlying information or technology
|
||||
may be downloaded or otherwise exported or re- exported: (a) into (or to a
|
||||
national or resident of) any country to which the United States has embargoed
|
||||
goods; or (b) to anyone on the U.S. Treasury Department's list of specially
|
||||
designated nationals or the U.S. Commerce Department's list of prohibited
|
||||
countries or debarred or denied persons or entities. You hereby agree to the
|
||||
foregoing and represents and warrants that You are not located in, under control
|
||||
of, or a national or resident of any such country or on any such list.
|
||||
|
||||
5.10 Construction. The titles and section headings used in this Agreement are
|
||||
for ease of reference only and will not be used in the interpretation or
|
||||
construction of this Agreement. No rule of construction resolving any ambiguity
|
||||
in favor of the non-drafting party will be applied hereto. The word "including",
|
||||
when used herein, is illustrative rather than exclusive and means "including,
|
||||
without limitation."
|
||||
98
apps/web/README.md
Normal file
98
apps/web/README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
> **Repository Reorganization in Progress**
|
||||
>
|
||||
> We are currently migrating some projects over to a mono repository. For existing PR's we will be providing documentation on how to move/migrate them. To minimize the overhead we are actively reviewing open PRs. If possible please ensure any pending comments are resolved as soon as possible.
|
||||
>
|
||||
> New pull requests created during this transition period may not get addressed —if needed, please create a new PR after the reorganization is complete.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/web-vault-macbook.png" alt="" width="600" height="358" />
|
||||
</p>
|
||||
<p align="center">
|
||||
The Bitwarden web project is an Angular application that powers the web vault (https://vault.bitwarden.com/).
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/bitwarden/web/actions?query=branch:master" target="_blank">
|
||||
<img src="https://github.com/bitwarden/web/actions/workflows/build.yml/badge.svg?branch=master" alt="Github Workflow build on master" />
|
||||
</a>
|
||||
<a href="https://crowdin.com/project/bitwarden-web" target="_blank">
|
||||
<img src="https://d322cqt584bo4o.cloudfront.net/bitwarden-web/localized.svg" alt="Crowdin" />
|
||||
</a>
|
||||
<a href="https://hub.docker.com/u/bitwarden/" target="_blank">
|
||||
<img src="https://img.shields.io/docker/pulls/bitwarden/web.svg" alt="DockerHub" />
|
||||
</a>
|
||||
<a href="https://gitter.im/bitwarden/Lobby" target="_blank">
|
||||
<img src="https://badges.gitter.im/bitwarden/Lobby.svg" alt="gitter chat" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Build/Run
|
||||
|
||||
### Requirements
|
||||
|
||||
- [Node.js](https://nodejs.org) v16.13.1 or greater
|
||||
- NPM v8
|
||||
|
||||
### Run the app
|
||||
|
||||
For local development, run the app with:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm run build:oss:watch
|
||||
```
|
||||
|
||||
You can now access the web vault in your browser at `https://localhost:8080`.
|
||||
|
||||
If you want to point the development web vault to the production APIs, you can run using:
|
||||
|
||||
```
|
||||
npm install
|
||||
ENV=cloud npm run build:oss:watch
|
||||
```
|
||||
|
||||
You can also manually adjusting your API endpoint settings by adding `config/local.json` overriding any of the following values:
|
||||
|
||||
```json
|
||||
{
|
||||
"dev": {
|
||||
"proxyApi": "http://your-api-url",
|
||||
"proxyIdentity": "http://your-identity-url",
|
||||
"proxyEvents": "http://your-events-url",
|
||||
"proxyNotifications": "http://your-notifications-url",
|
||||
"allowedHosts": ["hostnames-to-allow-in-webpack"]
|
||||
},
|
||||
"urls": {}
|
||||
}
|
||||
```
|
||||
|
||||
Where the `urls` object is defined by the [Urls type in jslib](https://github.com/bitwarden/jslib/blob/master/common/src/abstractions/environment.service.ts).
|
||||
|
||||
## We're Hiring!
|
||||
|
||||
Interested in contributing in a big way? Consider joining our team! We're hiring for many positions. Please take a look at our [Careers page](https://bitwarden.com/careers/) to see what opportunities are currently open as well as what it's like to work at Bitwarden.
|
||||
|
||||
## Contribute
|
||||
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
|
||||
|
||||
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
|
||||
|
||||
## Prettier
|
||||
|
||||
We recently migrated to using Prettier as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps:
|
||||
|
||||
1. Check out your local Branch
|
||||
2. Run `git merge 2b0a9d995e0147601ca8ae4778434a19354a60c2`
|
||||
3. Resolve any merge conflicts, commit.
|
||||
4. Run `npm run prettier`
|
||||
5. Commit
|
||||
6. Run `git merge -Xours 56477eb39cfd8a73c9920577d24d75fed36e2cf5`
|
||||
7. Push
|
||||
|
||||
### Git blame
|
||||
|
||||
We also recommend that you configure git to ignore the prettier revision using:
|
||||
|
||||
```bash
|
||||
git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
```
|
||||
21
apps/web/SECURITY.md
Normal file
21
apps/web/SECURITY.md
Normal file
@@ -0,0 +1,21 @@
|
||||
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 please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). 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).
|
||||
|
||||
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
|
||||
|
||||
# We want to help you!
|
||||
|
||||
If you have something that you feel is close to exploitation, or if you'd like some information regarding the internal API, or generally have any questions regarding the app that would help in your efforts, please email us at https://bitwarden.com/contact and ask for that information. As stated above, Bitwarden wants to help you find issues, and is more than willing to help.
|
||||
|
||||
Thank you for helping keep Bitwarden and our users safe!
|
||||
36
apps/web/config.js
Normal file
36
apps/web/config.js
Normal file
@@ -0,0 +1,36 @@
|
||||
function load(envName) {
|
||||
return {
|
||||
...require("./config/base.json"),
|
||||
...loadConfig(envName),
|
||||
...loadConfig("local"),
|
||||
dev: {
|
||||
...require("./config/base.json").dev,
|
||||
...loadConfig(envName).dev,
|
||||
...loadConfig("local").dev,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function log(configObj) {
|
||||
const repeatNum = 50;
|
||||
console.log(`${"=".repeat(repeatNum)}\nenvConfig`);
|
||||
console.log(JSON.stringify(configObj, null, 2));
|
||||
console.log(`${"=".repeat(repeatNum)}`);
|
||||
}
|
||||
|
||||
function loadConfig(configName) {
|
||||
try {
|
||||
return require(`./config/${configName}.json`);
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.code === "MODULE_NOT_FOUND") {
|
||||
return {};
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
load,
|
||||
log,
|
||||
};
|
||||
13
apps/web/config/base.json
Normal file
13
apps/web/config/base.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"urls": {},
|
||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
||||
"paypal": {
|
||||
"businessId": "AD3LAUZSNVPJY",
|
||||
"buttonAction": "https://www.sandbox.paypal.com/cgi-bin/webscr"
|
||||
},
|
||||
"dev": {
|
||||
"port": 8080,
|
||||
"allowedHosts": "auto"
|
||||
}
|
||||
}
|
||||
17
apps/web/config/cloud.json
Normal file
17
apps/web/config/cloud.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"urls": {
|
||||
"icons": "https://icons.bitwarden.net",
|
||||
"notifications": "https://notifications.bitwarden.com"
|
||||
},
|
||||
"stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk",
|
||||
"braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd",
|
||||
"paypal": {
|
||||
"businessId": "4ZDA7DLUUJGMN",
|
||||
"buttonAction": "https://www.paypal.com/cgi-bin/webscr"
|
||||
},
|
||||
"dev": {
|
||||
"proxyApi": "https://api.bitwarden.com",
|
||||
"proxyIdentity": "https://identity.bitwarden.com",
|
||||
"proxyEvents": "https://events.bitwarden.com"
|
||||
}
|
||||
}
|
||||
11
apps/web/config/development.json
Normal file
11
apps/web/config/development.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"urls": {
|
||||
"notifications": "http://localhost:61840"
|
||||
},
|
||||
"dev": {
|
||||
"proxyApi": "http://localhost:4000",
|
||||
"proxyIdentity": "http://localhost:33656",
|
||||
"proxyEvents": "http://localhost:46273",
|
||||
"proxyNotifications": "http://localhost:61840"
|
||||
}
|
||||
}
|
||||
11
apps/web/config/qa.json
Normal file
11
apps/web/config/qa.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"urls": {
|
||||
"icons": "https://icons.qa.bitwarden.pw",
|
||||
"notifications": "https://notifications.qa.bitwarden.pw"
|
||||
},
|
||||
"dev": {
|
||||
"proxyApi": "https://api.qa.bitwarden.pw",
|
||||
"proxyIdentity": "https://identity.qa.bitwarden.pw",
|
||||
"proxyEvents": "https://events.qa.bitwarden.pw"
|
||||
}
|
||||
}
|
||||
9
apps/web/config/selfhosted.json
Normal file
9
apps/web/config/selfhosted.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"dev": {
|
||||
"proxyApi": "http://localhost:4001",
|
||||
"proxyIdentity": "http://localhost:33657",
|
||||
"proxyEvents": "http://localhost:46274",
|
||||
"proxyNotifications": "http://localhost:61841",
|
||||
"port": 8081
|
||||
}
|
||||
}
|
||||
18
apps/web/crowdin.yml
Normal file
18
apps/web/crowdin.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
project_id_env: _CROWDIN_PROJECT_ID
|
||||
api_token_env: CROWDIN_API_TOKEN
|
||||
preserve_hierarchy: true
|
||||
files:
|
||||
- source: /src/locales/en/messages.json
|
||||
dest: /src/locales/en/%file_name%.%file_extension%
|
||||
translation: /src/locales/%two_letters_code%/%original_file_name%
|
||||
update_option: update_as_unapproved
|
||||
languages_mapping:
|
||||
two_letters_code:
|
||||
pt-PT: pt_PT
|
||||
pt-BR: pt_BR
|
||||
zh-CN: zh_CN
|
||||
zh-TW: zh_TW
|
||||
en-GB: en_GB
|
||||
en-IN: en_IN
|
||||
sr-CY: sr_CY
|
||||
sr-CS: sr_CS
|
||||
45
apps/web/dev-server.shared.pem
Normal file
45
apps/web/dev-server.shared.pem
Normal file
@@ -0,0 +1,45 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDaEy1cPw07irjg
|
||||
4wUgaxshW7oQrgVoNZYRROmdU20K22L+HyG2ahW6usiWw8+6fPgVve7Y1z+/GYsK
|
||||
DhaDdY1Ket3JvZHxoAJ/+6lYY+05PhtmYnyEZzZlnuYx/tu3vyGpsXqMpzL3ZrX2
|
||||
Mh2dWE7ZXKxsyig4wSDJhBPMrW8HKXLrLR/JPFhu/nz5MpRF5LfzWU13FEfmS43s
|
||||
PEkBCn5ZxhVX4eNclQhTl7oOo5LU+KCWn+C/GPQir7pdmMoYF6D5j3qtbsq9irPe
|
||||
qR7HsM9z6DnX+L0mF31P2S6OTgLT3kuWJn9vqLUwvQWGFvSSzpw7JgGBK3eX6zE9
|
||||
2koGWP9NAgMBAAECggEAIpwCie5TykxU1RQSfzegYaXuHLGRmB1RCMKYFOjlmGCD
|
||||
EHOeZRXnBvCX3x2KfT1SHhk7q9xVeJ20LE9aEVj5qIVhZ6AXZnKPkwI8uRN61afe
|
||||
r1wYCOdcgbo7LFoXQs0pqYXKPkJW217IqB8CBjO6p9KGZumago9cBb9ZaRVpVohZ
|
||||
c6YHeatrna2mPb/EUPHdT0RHHQ5Dz2ToPjCkDtxsNHLZLekR35WIMtCBlp0xY5hb
|
||||
5h54ZxnaMihvHTLa8L/pgxGEUsP+XFpdXkM1oREzh8tDRFcUL8mUVZq8bGyzALn5
|
||||
MxDhdXqxrnyD2cQ/cSqXLs1/2mh5eccU3g5IaNtrAQKBgQD0Q4K2UYXa8jWQu7jI
|
||||
b37zwr2EypLFjeluqF4fxs+oz3UYEXeBDK0Td19/tze6/XieKibKDtFrOZQwDDKC
|
||||
AVxD7Dm58T9Jf4LDHNYOfYL3X/E4H+JrVBh94s0B00jVJ6TnEQDMuLi+wMGtvTdW
|
||||
huxoNefIWKf73ozvxIF+nsyeDQKBgQDkjYoXkBtfNgQR42RVA0/UdLLDWWctMU4F
|
||||
sJYc1bLL3txbf+fK7QzbU/ggLMW0hv8/IdyirGJhW3G0K0yhpAOlPhe36qv4QyhD
|
||||
o2nFlRrzfzvGJAgH9b1s+VcL/cSIuv4aCkbv97DAoQGPzAWEKv5gY1iw1DsGgrz5
|
||||
svZUvd7WQQKBgQDPrp7yuTngQNP+bT3dXb9JLqjIwRwt0E1LgugUiIuDcnCSuDct
|
||||
iEOYK4UNKBDAckcd46T7Y8H3MwumFpjTJKj4L1+dk1tF+J6Lmnb99wVlozOLjsCK
|
||||
lQQF9NJt3OEuKvjwZeqSJfUeavHB8QGeFjXnHP4nwAmEA2M9cYzQxeAf+QKBgCbS
|
||||
U+6Er+GQT0iqk1RNZ7XyzJqaCQiII3Sb9iOXuPMgO9Xe+ARkF5b5wF/Wuw5bD+gt
|
||||
XEjVdzCKU9oCsNWUAnqC/Yxj9CoLXj9+9mx1U0qhBgo1/Jc9ipuEDuEejc+b06Wg
|
||||
sUP5krBlqNpAEX/Nvb+poFsI8a29b1QKrgTe64cBAoGALg92rZBG60N2n8fTokou
|
||||
f1fui8Ftb+vOVGv9CM6icmNuwXeMF40A33Hvx14XLFk6B5p5dtVyOR660rRv4HRV
|
||||
cBUm5wwCZjwR5Aj83XGR0PRbTNFNngHbawQiutSo6dw8cNNKCZMywVh2KX29dsLh
|
||||
0Yj++kb8+G1kzFonR8WWoC8=
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICwzCCAaugAwIBAgIJAN5sbMfEx05qMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
|
||||
BAMTCWxvY2FsaG9zdDAeFw0xODA2MDUwMzMxNDhaFw0yODA2MDIwMzMxNDhaMBQx
|
||||
EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||
ggEBANoTLVw/DTuKuODjBSBrGyFbuhCuBWg1lhFE6Z1TbQrbYv4fIbZqFbq6yJbD
|
||||
z7p8+BW97tjXP78ZiwoOFoN1jUp63cm9kfGgAn/7qVhj7Tk+G2ZifIRnNmWe5jH+
|
||||
27e/IamxeoynMvdmtfYyHZ1YTtlcrGzKKDjBIMmEE8ytbwcpcustH8k8WG7+fPky
|
||||
lEXkt/NZTXcUR+ZLjew8SQEKflnGFVfh41yVCFOXug6jktT4oJaf4L8Y9CKvul2Y
|
||||
yhgXoPmPeq1uyr2Ks96pHsewz3PoOdf4vSYXfU/ZLo5OAtPeS5Ymf2+otTC9BYYW
|
||||
9JLOnDsmAYErd5frMT3aSgZY/00CAwEAAaMYMBYwFAYDVR0RBA0wC4IJbG9jYWxo
|
||||
b3N0MA0GCSqGSIb3DQEBCwUAA4IBAQCBTn7szrcs+fSs1Q/a2O3ng35zcme6NRhp
|
||||
T65RP0ooj3tPT9QlTJyKjo9Yb2RW2RGVbQO86mkYe9N9wcZkzurZ6KDqsfBn3FkI
|
||||
eZA1G/za907Dt/25mOdrsav7NmFBwxo9iuZ/cozgneK1mAXOu4nDI5yYvAlvNA6E
|
||||
iXgls4WX1LtHL5b9YV7Jz27d5tTmGxEimakMBo+zr10vCtMCsTlDs/ChamnI7ljN
|
||||
7B4WIVUMI3xOZzqClLnSzFJNReAlapjtGtp1qH6Y+6aZ9OErIwZOjE9CYYvm6MbE
|
||||
CblXQ9Uifpwrc09TA5S2Y/9+VxUBF9xlxh8hkcGLTzlNFDzVWdmR
|
||||
-----END CERTIFICATE-----
|
||||
38
apps/web/entrypoint.sh
Normal file
38
apps/web/entrypoint.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Setup
|
||||
|
||||
GROUPNAME="bitwarden"
|
||||
USERNAME="bitwarden"
|
||||
|
||||
LUID=${LOCAL_UID:-0}
|
||||
LGID=${LOCAL_GID:-0}
|
||||
|
||||
# Step down from host root to well-known nobody/nogroup user
|
||||
|
||||
if [ $LUID -eq 0 ]
|
||||
then
|
||||
LUID=65534
|
||||
fi
|
||||
if [ $LGID -eq 0 ]
|
||||
then
|
||||
LGID=65534
|
||||
fi
|
||||
|
||||
# Create user and group
|
||||
|
||||
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
|
||||
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
|
||||
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
|
||||
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
|
||||
mkhomedir_helper $USERNAME
|
||||
|
||||
# The rest...
|
||||
|
||||
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
|
||||
cp /etc/bitwarden/web/app-id.json /app/app-id.json
|
||||
chown -R $USERNAME:$GROUPNAME /app
|
||||
chown -R $USERNAME:$GROUPNAME /bitwarden_server
|
||||
|
||||
exec gosu $USERNAME:$GROUPNAME dotnet /bitwarden_server/Server.dll \
|
||||
/contentRoot=/app /webRoot=. /serveUnknown=false /webVault=true
|
||||
13568
apps/web/package-lock.json
generated
Normal file
13568
apps/web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
119
apps/web/package.json
Normal file
119
apps/web/package.json
Normal file
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"name": "@bitwarden/web-vault",
|
||||
"version": "2022.05.0",
|
||||
"license": "GPL-3.0",
|
||||
"repository": "https://github.com/bitwarden/web",
|
||||
"scripts": {
|
||||
"sub:init": "git submodule update --init --recursive",
|
||||
"sub:update": "git submodule update --remote",
|
||||
"sub:pull": "git submodule foreach git pull origin master",
|
||||
"preinstall": "npm run sub:init",
|
||||
"symlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib",
|
||||
"symlink:mac": "npm run symlink:lin",
|
||||
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
|
||||
"build:oss": "webpack",
|
||||
"build:bit": "webpack -c bitwarden_license/webpack.config.js",
|
||||
"build:oss:watch": "webpack serve",
|
||||
"build:bit:watch": "webpack serve -c bitwarden_license/webpack.config.js",
|
||||
"build:bit:dev": "cross-env ENV=development npm run build:bit",
|
||||
"build:bit:dev:watch": "cross-env ENV=development npm run build:bit:watch",
|
||||
"build:bit:qa": "cross-env NODE_ENV=production ENV=qa npm run build:bit",
|
||||
"build:bit:cloud": "cross-env NODE_ENV=production ENV=cloud npm run build:bit",
|
||||
"build:oss:selfhost:watch": "cross-env ENV=selfhosted npm run build:oss:watch",
|
||||
"build:bit:selfhost:watch": "cross-env ENV=selfhosted npm run build:bit:watch",
|
||||
"build:oss:selfhost:prod": "cross-env ENV=selfhosted NODE_ENV=production npm run build:oss",
|
||||
"build:bit:selfhost:prod": "cross-env ENV=selfhosted NODE_ENV=production npm run build:bit",
|
||||
"clean:l10n": "git push origin --delete l10n_master",
|
||||
"dist:bit:cloud": "npm run build:bit:cloud",
|
||||
"dist:oss:selfhost": "npm run build:oss:selfhost:prod",
|
||||
"dist:bit:selfhost": "npm run build:bit:selfhost:prod",
|
||||
"deploy": "npm run dist:bit && gh-pages -d build",
|
||||
"deploy:dev": "npm run dist:bit && gh-pages -d build -r git@github.com:kspearrin/bitwarden-web-dev.git",
|
||||
"lint": "eslint . && prettier --check .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"prettier": "prettier --write .",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/compiler-cli": "^12.2.13",
|
||||
"@ngtools/webpack": "^12.2.13",
|
||||
"@types/jquery": "^3.5.5",
|
||||
"@types/node": "^16.11.12",
|
||||
"@types/webcrypto": "^0.0.28",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.1",
|
||||
"@typescript-eslint/parser": "^5.10.1",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"buffer": "^6.0.3",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^10.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.5.1",
|
||||
"eslint": "^8.7.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-typescript": "^2.5.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"gh-pages": "^3.1.0",
|
||||
"html-loader": "^3.0.1",
|
||||
"html-webpack-injector": "1.1.4",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.1.2",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"postcss": "^8.4.6",
|
||||
"postcss-loader": "^6.2.1",
|
||||
"prettier": "2.5.1",
|
||||
"process": "^0.11.10",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.32.10",
|
||||
"sass-loader": "^12.4.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.0.18",
|
||||
"terser-webpack-plugin": "^5.2.5",
|
||||
"ts-loader": "^9.2.5",
|
||||
"typescript": "4.3.5",
|
||||
"util": "^0.12.4",
|
||||
"webpack": "^5.64.4",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"webpack-dev-server": "^4.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^12.2.13",
|
||||
"@angular/cdk": "^12.2.13",
|
||||
"@angular/common": "^12.2.13",
|
||||
"@angular/compiler": "^12.2.13",
|
||||
"@angular/core": "^12.2.13",
|
||||
"@angular/forms": "^12.2.13",
|
||||
"@angular/platform-browser": "^12.2.13",
|
||||
"@angular/platform-browser-dynamic": "^12.2.13",
|
||||
"@angular/router": "^12.2.13",
|
||||
"@bitwarden/jslib-angular": "file:jslib/angular",
|
||||
"@bitwarden/jslib-common": "file:jslib/common",
|
||||
"bootstrap": "4.6.0",
|
||||
"braintree-web-drop-in": "1.33.1",
|
||||
"browser-hrtime": "^1.1.8",
|
||||
"core-js": "^3.11.0",
|
||||
"date-input-polyfill": "^2.14.0",
|
||||
"jquery": "3.6.0",
|
||||
"jszip": "^3.7.1",
|
||||
"ngx-infinite-scroll": "^10.0.1",
|
||||
"ngx-toastr": "14.1.4",
|
||||
"node-forge": "^1.3.1",
|
||||
"popper.js": "1.16.1",
|
||||
"qrious": "4.0.2",
|
||||
"rxjs": "^7.4.0",
|
||||
"sweetalert2": "^10.16.6",
|
||||
"webcrypto-shim": "0.1.7",
|
||||
"whatwg-fetch": "3.6.2",
|
||||
"zone.js": "0.11.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "~16",
|
||||
"npm": "~8"
|
||||
},
|
||||
"lint-staged": {
|
||||
"./!(jslib)**": "prettier --ignore-unknown --write",
|
||||
"*.ts": "eslint --fix",
|
||||
"*.png": "node scripts/optimize.js"
|
||||
}
|
||||
}
|
||||
4
apps/web/postcss.config.js
Normal file
4
apps/web/postcss.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
/* eslint-disable no-undef */
|
||||
module.exports = {
|
||||
plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")],
|
||||
};
|
||||
21
apps/web/scripts/optimize.js
Normal file
21
apps/web/scripts/optimize.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const child_process = require("child_process");
|
||||
const path = require("path");
|
||||
|
||||
const images = process.argv.slice(2);
|
||||
|
||||
images.forEach((img) => {
|
||||
switch (img.split(".").pop()) {
|
||||
case "png":
|
||||
child_process.execSync(
|
||||
`npx @squoosh/cli --oxipng {} --output-dir "${path.dirname(img)}" "${img}"`
|
||||
);
|
||||
break;
|
||||
case "jpg":
|
||||
child_process.execSync(
|
||||
`npx @squoosh/cli --mozjpeg {"quality":85,"baseline":false,"arithmetic":false,"progressive":true,"optimize_coding":true,"smoothing":0,"color_space":3,"quant_table":3,"trellis_multipass":false,"trellis_opt_zero":false,"trellis_opt_table":false,"trellis_loops":1,"auto_subsample":true,"chroma_subsample":2,"separate_chroma_quality":false,"chroma_quality":75} --output-dir "${path.dirname(
|
||||
img
|
||||
)}" "${img}"`
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
0
apps/web/src/.nojekyll
Normal file
0
apps/web/src/.nojekyll
Normal file
52
apps/web/src/404.html
Normal file
52
apps/web/src/404.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<link
|
||||
href="/404/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
integrity="sha384-hA/ESrxp2b05ywLtD9YwM6m+pNyLRY4+ruk6dWK00SM4k6SQs0bfrITJVSf6uZyH"
|
||||
/>
|
||||
<link href="/404/styles.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/images/icons/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/images/icons/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/images/icons/favicon-16x16.png" />
|
||||
<link rel="mask-icon" href="/images/icons/safari-pinned-tab.svg" color="#175DDC" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<title>Page not found!</title>
|
||||
<meta name="description" content="404 Page Not Found" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="banner">
|
||||
<div class="container inner banner">
|
||||
<div class="row align-items-center">
|
||||
<div class="col brand">
|
||||
<i class="bwi bwi-shield"></i> <strong>bit</strong>warden
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container inner content">
|
||||
<h2>Page not found!</h2>
|
||||
<p>Sorry, but the page you were looking for could not be found.</p>
|
||||
<p>
|
||||
<a href="/">
|
||||
<img src="/images/404.png" class="img-fluid" alt="404 image" width="80%" />
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
You can <a href="/">return to the web vault</a>, check our
|
||||
<a href="https://status.bitwarden.com/">status page</a> or
|
||||
<a href="https://bitwarden.com/contact/">contact us</a>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="container footer text-muted content">© Copyright 2022 Bitwarden, Inc.</div>
|
||||
</body>
|
||||
</html>
|
||||
7
apps/web/src/404/bootstrap.min.css
vendored
Normal file
7
apps/web/src/404/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
151
apps/web/src/404/styles.css
Normal file
151
apps/web/src/404/styles.css
Normal file
@@ -0,0 +1,151 @@
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: url(../fonts/Open_Sans-italic-300.woff) format("woff");
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url(../fonts/Open_Sans-italic-400.woff) format("woff");
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
src: url(../fonts/Open_Sans-italic-600.woff) format("woff");
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url(../fonts/Open_Sans-italic-700.woff) format("woff");
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
src: url(../fonts/Open_Sans-italic-800.woff) format("woff");
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(../fonts/Open_Sans-normal-300.woff) format("woff");
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(../fonts/Open_Sans-normal-400.woff) format("woff");
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url(../fonts/Open_Sans-normal-600.woff) format("woff");
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url(../fonts/Open_Sans-normal-700.woff) format("woff");
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url(../fonts/Open_Sans-normal-800.woff) format("woff");
|
||||
unicode-range: U+0-10FFFF;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Open Sans";
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
.row {
|
||||
height: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 25px;
|
||||
margin-bottom: 12.5px;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.brand {
|
||||
font-size: 23px;
|
||||
line-height: 25px;
|
||||
color: #fff;
|
||||
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.banner {
|
||||
background-color: #175ddc;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 40px 0 40px 0;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
/* Bitwarden icons, manually copied */
|
||||
|
||||
@font-face {
|
||||
font-family: "bwi-font";
|
||||
src: url(../images/bwi-font.svg) format("svg"), url(../fonts/bwi-font.ttf) format("truetype"),
|
||||
url(../fonts/bwi-font.woff) format("woff"), url(../fonts/bwi-font.woff2) format("woff2");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
.bwi {
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
font-family: "bwi-font" !important;
|
||||
speak: never;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
display: inline-block;
|
||||
/* Better Font Rendering */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.bwi-shield:before {
|
||||
content: "\e932";
|
||||
}
|
||||
9
apps/web/src/abstractions/state.service.ts
Normal file
9
apps/web/src/abstractions/state.service.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { StateService as BaseStateService } from "jslib-common/abstractions/state.service";
|
||||
import { StorageOptions } from "jslib-common/models/domain/storageOptions";
|
||||
|
||||
import { Account } from "src/models/account";
|
||||
|
||||
export abstract class StateService extends BaseStateService<Account> {
|
||||
getRememberEmail: (options?: StorageOptions) => Promise<boolean>;
|
||||
setRememberEmail: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
}
|
||||
15
apps/web/src/app-id.json
Normal file
15
apps/web/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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
45
apps/web/src/app/accounts/accept-emergency.component.html
Normal file
45
apps/web/src/app/accounts/accept-emergency.component.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
|
||||
<div>
|
||||
<img class="mb-4 logo logo-themed" alt="Bitwarden" />
|
||||
<p class="text-center">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" *ngIf="!loading && !authed">
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="lead text-center mb-4">{{ "emergencyAccess" | i18n }}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<p class="text-center">
|
||||
{{ name }}
|
||||
</p>
|
||||
<p>{{ "acceptEmergencyAccess" | i18n }}</p>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<a
|
||||
routerLink="/login"
|
||||
[queryParams]="{ email: email }"
|
||||
class="btn btn-primary btn-block"
|
||||
>
|
||||
{{ "logIn" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="/register"
|
||||
[queryParams]="{ email: email }"
|
||||
class="btn btn-primary btn-block ml-2 mt-0"
|
||||
>
|
||||
{{ "createAccount" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
55
apps/web/src/app/accounts/accept-emergency.component.ts
Normal file
55
apps/web/src/app/accounts/accept-emergency.component.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { EmergencyAccessAcceptRequest } from "jslib-common/models/request/emergencyAccessAcceptRequest";
|
||||
|
||||
import { BaseAcceptComponent } from "../common/base.accept.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-accept-emergency",
|
||||
templateUrl: "accept-emergency.component.html",
|
||||
})
|
||||
export class AcceptEmergencyComponent extends BaseAcceptComponent {
|
||||
name: string;
|
||||
|
||||
protected requiredParameters: string[] = ["id", "name", "email", "token"];
|
||||
protected failedShortMessage = "emergencyInviteAcceptFailedShort";
|
||||
protected failedMessage = "emergencyInviteAcceptFailed";
|
||||
|
||||
constructor(
|
||||
router: Router,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
route: ActivatedRoute,
|
||||
private apiService: ApiService,
|
||||
stateService: StateService
|
||||
) {
|
||||
super(router, platformUtilsService, i18nService, route, stateService);
|
||||
}
|
||||
|
||||
async authedHandler(qParams: any): Promise<void> {
|
||||
const request = new EmergencyAccessAcceptRequest();
|
||||
request.token = qParams.token;
|
||||
this.actionPromise = this.apiService.postEmergencyAccessAccept(qParams.id, request);
|
||||
await this.actionPromise;
|
||||
this.platformUtilService.showToast(
|
||||
"success",
|
||||
this.i18nService.t("inviteAccepted"),
|
||||
this.i18nService.t("emergencyInviteAcceptedDesc"),
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
this.router.navigate(["/vault"]);
|
||||
}
|
||||
|
||||
async unauthedHandler(qParams: any): Promise<void> {
|
||||
this.name = qParams.name;
|
||||
if (this.name != null) {
|
||||
// Fix URL encoding of space issue with Angular
|
||||
this.name = this.name.replace(/\+/g, " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
46
apps/web/src/app/accounts/accept-organization.component.html
Normal file
46
apps/web/src/app/accounts/accept-organization.component.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
|
||||
<div>
|
||||
<img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden" />
|
||||
<p class="text-center">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" *ngIf="!loading && !authed">
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="lead text-center mb-4">{{ "joinOrganization" | i18n }}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<p class="text-center">
|
||||
{{ orgName }}
|
||||
<strong class="d-block mt-2">{{ email }}</strong>
|
||||
</p>
|
||||
<p>{{ "joinOrganizationDesc" | i18n }}</p>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<a
|
||||
routerLink="/login"
|
||||
[queryParams]="{ email: email }"
|
||||
class="btn btn-primary btn-block"
|
||||
>
|
||||
{{ "logIn" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="/register"
|
||||
[queryParams]="{ email: email }"
|
||||
class="btn btn-primary btn-block ml-2 mt-0"
|
||||
>
|
||||
{{ "createAccount" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
126
apps/web/src/app/accounts/accept-organization.component.ts
Normal file
126
apps/web/src/app/accounts/accept-organization.component.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
import { Policy } from "jslib-common/models/domain/policy";
|
||||
import { OrganizationUserAcceptRequest } from "jslib-common/models/request/organizationUserAcceptRequest";
|
||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
|
||||
|
||||
import { BaseAcceptComponent } from "../common/base.accept.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-accept-organization",
|
||||
templateUrl: "accept-organization.component.html",
|
||||
})
|
||||
export class AcceptOrganizationComponent extends BaseAcceptComponent {
|
||||
orgName: string;
|
||||
|
||||
protected requiredParameters: string[] = ["organizationId", "organizationUserId", "token"];
|
||||
|
||||
constructor(
|
||||
router: Router,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
route: ActivatedRoute,
|
||||
private apiService: ApiService,
|
||||
stateService: StateService,
|
||||
private cryptoService: CryptoService,
|
||||
private policyService: PolicyService,
|
||||
private logService: LogService
|
||||
) {
|
||||
super(router, platformUtilsService, i18nService, route, stateService);
|
||||
}
|
||||
|
||||
async authedHandler(qParams: any): Promise<void> {
|
||||
const request = new OrganizationUserAcceptRequest();
|
||||
request.token = qParams.token;
|
||||
if (await this.performResetPasswordAutoEnroll(qParams)) {
|
||||
this.actionPromise = this.apiService
|
||||
.postOrganizationUserAccept(qParams.organizationId, qParams.organizationUserId, request)
|
||||
.then(() => {
|
||||
// Retrieve Public Key
|
||||
return this.apiService.getOrganizationKeys(qParams.organizationId);
|
||||
})
|
||||
.then(async (response) => {
|
||||
if (response == null) {
|
||||
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
|
||||
}
|
||||
|
||||
const publicKey = Utils.fromB64ToArray(response.publicKey);
|
||||
|
||||
// RSA Encrypt user's encKey.key with organization public key
|
||||
const encKey = await this.cryptoService.getEncKey();
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
|
||||
|
||||
// Create request and execute enrollment
|
||||
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
|
||||
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
|
||||
|
||||
return this.apiService.putOrganizationUserResetPasswordEnrollment(
|
||||
qParams.organizationId,
|
||||
await this.stateService.getUserId(),
|
||||
resetRequest
|
||||
);
|
||||
});
|
||||
} else {
|
||||
this.actionPromise = this.apiService.postOrganizationUserAccept(
|
||||
qParams.organizationId,
|
||||
qParams.organizationUserId,
|
||||
request
|
||||
);
|
||||
}
|
||||
|
||||
await this.actionPromise;
|
||||
this.platformUtilService.showToast(
|
||||
"success",
|
||||
this.i18nService.t("inviteAccepted"),
|
||||
this.i18nService.t("inviteAcceptedDesc"),
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
|
||||
await this.stateService.setOrganizationInvitation(null);
|
||||
this.router.navigate(["/vault"]);
|
||||
}
|
||||
|
||||
async unauthedHandler(qParams: any): Promise<void> {
|
||||
this.orgName = qParams.organizationName;
|
||||
if (this.orgName != null) {
|
||||
// Fix URL encoding of space issue with Angular
|
||||
this.orgName = this.orgName.replace(/\+/g, " ");
|
||||
}
|
||||
await this.stateService.setOrganizationInvitation(qParams);
|
||||
}
|
||||
|
||||
private async performResetPasswordAutoEnroll(qParams: any): Promise<boolean> {
|
||||
let policyList: Policy[] = null;
|
||||
try {
|
||||
const policies = await this.apiService.getPoliciesByToken(
|
||||
qParams.organizationId,
|
||||
qParams.token,
|
||||
qParams.email,
|
||||
qParams.organizationUserId
|
||||
);
|
||||
policyList = this.policyService.mapPoliciesFromToken(policies);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
|
||||
if (policyList != null) {
|
||||
const result = this.policyService.getResetPasswordPolicyOptions(
|
||||
policyList,
|
||||
qParams.organizationId
|
||||
);
|
||||
// Return true if policy enabled and auto-enroll enabled
|
||||
return result[1] && result[0].autoEnrollEnabled;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
44
apps/web/src/app/accounts/hint.component.html
Normal file
44
apps/web/src/app/accounts/hint.component.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="lead text-center mb-4">{{ "passwordHint" | i18n }}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="email">{{ "emailAddress" | i18n }}</label>
|
||||
<input
|
||||
id="email"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Email"
|
||||
[(ngModel)]="email"
|
||||
required
|
||||
appAutofocus
|
||||
inputmode="email"
|
||||
appInputVerbatim="false"
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "enterEmailToGetHint" | i18n }}</small>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<span [hidden]="form.loading">{{ "submit" | i18n }}</span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{ "cancel" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
24
apps/web/src/app/accounts/hint.component.ts
Normal file
24
apps/web/src/app/accounts/hint.component.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component";
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-hint",
|
||||
templateUrl: "hint.component.html",
|
||||
})
|
||||
export class HintComponent extends BaseHintComponent {
|
||||
constructor(
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
apiService: ApiService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
logService: LogService
|
||||
) {
|
||||
super(router, i18nService, apiService, platformUtilsService, logService);
|
||||
}
|
||||
}
|
||||
66
apps/web/src/app/accounts/lock.component.html
Normal file
66
apps/web/src/app/accounts/lock.component.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="text-center mb-4">
|
||||
<i class="bwi bwi-lock bwi-4x text-muted" aria-hidden="true"></i>
|
||||
</p>
|
||||
<p class="lead text-center mx-4 mb-4">{{ "yourVaultIsLocked" | i18n }}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
<div class="d-flex">
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="MasterPassword"
|
||||
class="text-monospace form-control"
|
||||
[(ngModel)]="masterPassword"
|
||||
required
|
||||
appAutofocus
|
||||
appInputVerbatim
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 btn btn-link"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword()"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted form-text">
|
||||
{{ "loggedInAsEmailOn" | i18n: email:webVaultHostname }}
|
||||
</small>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<span> <i class="bwi bwi-unlock" aria-hidden="true"></i> {{ "unlock" | i18n }} </span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-block ml-2 mt-0"
|
||||
(click)="logOut()"
|
||||
>
|
||||
{{ "logOut" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
64
apps/web/src/app/accounts/lock.component.ts
Normal file
64
apps/web/src/app/accounts/lock.component.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Component, NgZone } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||
|
||||
import { RouterService } from "../services/router.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-lock",
|
||||
templateUrl: "lock.component.html",
|
||||
})
|
||||
export class LockComponent extends BaseLockComponent {
|
||||
constructor(
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
messagingService: MessagingService,
|
||||
cryptoService: CryptoService,
|
||||
vaultTimeoutService: VaultTimeoutService,
|
||||
environmentService: EnvironmentService,
|
||||
private routerService: RouterService,
|
||||
stateService: StateService,
|
||||
apiService: ApiService,
|
||||
logService: LogService,
|
||||
keyConnectorService: KeyConnectorService,
|
||||
ngZone: NgZone
|
||||
) {
|
||||
super(
|
||||
router,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
messagingService,
|
||||
cryptoService,
|
||||
vaultTimeoutService,
|
||||
environmentService,
|
||||
stateService,
|
||||
apiService,
|
||||
logService,
|
||||
keyConnectorService,
|
||||
ngZone
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
this.onSuccessfulSubmit = async () => {
|
||||
const previousUrl = this.routerService.getPreviousUrl();
|
||||
if (previousUrl && previousUrl !== "/" && previousUrl.indexOf("lock") === -1) {
|
||||
this.successRoute = previousUrl;
|
||||
}
|
||||
this.router.navigateByUrl(this.successRoute);
|
||||
};
|
||||
}
|
||||
}
|
||||
102
apps/web/src/app/accounts/login.component.html
Normal file
102
apps/web/src/app/accounts/login.component.html
Normal file
@@ -0,0 +1,102 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<img class="mb-2 logo logo-themed" alt="Bitwarden" />
|
||||
<p class="lead text-center mx-4 mb-4">{{ "loginOrCreateNewAccount" | i18n }}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<app-callout
|
||||
type="warning"
|
||||
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
|
||||
*ngIf="showResetPasswordAutoEnrollWarning"
|
||||
>
|
||||
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
|
||||
</app-callout>
|
||||
<div class="form-group">
|
||||
<label for="email">{{ "emailAddress" | i18n }}</label>
|
||||
<input
|
||||
id="email"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Email"
|
||||
[(ngModel)]="email"
|
||||
required
|
||||
inputmode="email"
|
||||
appInputVerbatim="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
<div class="d-flex">
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="MasterPassword"
|
||||
class="text-monospace form-control"
|
||||
[(ngModel)]="masterPassword"
|
||||
required
|
||||
appInputVerbatim
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 btn btn-link"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword()"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
<small class="form-text">
|
||||
<a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="rememberEmail"
|
||||
name="RememberEmail"
|
||||
[(ngModel)]="rememberEmail"
|
||||
/>
|
||||
<label class="form-check-label" for="rememberEmail">{{ "rememberEmail" | i18n }}</label>
|
||||
</div>
|
||||
<div class="mb-n3" [hidden]="!showCaptcha()">
|
||||
<iframe id="hcaptcha_iframe" height="80"></iframe>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<span> <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }} </span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<a
|
||||
routerLink="/register"
|
||||
[queryParams]="{ email: email }"
|
||||
class="btn btn-outline-secondary btn-block ml-2 mt-0"
|
||||
>
|
||||
<i class="bwi bwi-pencil-square" aria-hidden="true"></i>
|
||||
{{ "createAccount" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<a routerLink="/sso" class="btn btn-outline-secondary btn-block mt-2">
|
||||
<i class="bwi bwi-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
179
apps/web/src/app/accounts/login.component.ts
Normal file
179
apps/web/src/app/accounts/login.component.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { Component, NgZone } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.component";
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
import { PolicyData } from "jslib-common/models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
|
||||
import { Policy } from "jslib-common/models/domain/policy";
|
||||
import { ListResponse } from "jslib-common/models/response/listResponse";
|
||||
import { PolicyResponse } from "jslib-common/models/response/policyResponse";
|
||||
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { RouterService } from "../services/router.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-login",
|
||||
templateUrl: "login.component.html",
|
||||
})
|
||||
export class LoginComponent extends BaseLoginComponent {
|
||||
showResetPasswordAutoEnrollWarning = false;
|
||||
enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions;
|
||||
policies: ListResponse<PolicyResponse>;
|
||||
|
||||
constructor(
|
||||
authService: AuthService,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
private route: ActivatedRoute,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService,
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
private apiService: ApiService,
|
||||
private policyService: PolicyService,
|
||||
logService: LogService,
|
||||
ngZone: NgZone,
|
||||
protected stateService: StateService,
|
||||
private messagingService: MessagingService,
|
||||
private routerService: RouterService
|
||||
) {
|
||||
super(
|
||||
authService,
|
||||
router,
|
||||
platformUtilsService,
|
||||
i18nService,
|
||||
stateService,
|
||||
environmentService,
|
||||
passwordGenerationService,
|
||||
cryptoFunctionService,
|
||||
logService,
|
||||
ngZone
|
||||
);
|
||||
this.onSuccessfulLogin = async () => {
|
||||
this.messagingService.send("setFullWidth");
|
||||
};
|
||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
if (qParams.email != null && qParams.email.indexOf("@") > -1) {
|
||||
this.email = qParams.email;
|
||||
}
|
||||
if (qParams.premium != null) {
|
||||
this.routerService.setPreviousUrl("/settings/premium");
|
||||
} else if (qParams.org != null) {
|
||||
const route = this.router.createUrlTree(["create-organization"], {
|
||||
queryParams: { plan: qParams.org },
|
||||
});
|
||||
this.routerService.setPreviousUrl(route.toString());
|
||||
}
|
||||
|
||||
// Are they coming from an email for sponsoring a families organization
|
||||
if (qParams.sponsorshipToken != null) {
|
||||
const route = this.router.createUrlTree(["setup/families-for-enterprise"], {
|
||||
queryParams: { token: qParams.sponsorshipToken },
|
||||
});
|
||||
this.routerService.setPreviousUrl(route.toString());
|
||||
}
|
||||
await super.ngOnInit();
|
||||
this.rememberEmail = await this.stateService.getRememberEmail();
|
||||
});
|
||||
|
||||
const invite = await this.stateService.getOrganizationInvitation();
|
||||
if (invite != null) {
|
||||
let policyList: Policy[] = null;
|
||||
try {
|
||||
this.policies = await this.apiService.getPoliciesByToken(
|
||||
invite.organizationId,
|
||||
invite.token,
|
||||
invite.email,
|
||||
invite.organizationUserId
|
||||
);
|
||||
policyList = this.policyService.mapPoliciesFromToken(this.policies);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
|
||||
if (policyList != null) {
|
||||
const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions(
|
||||
policyList,
|
||||
invite.organizationId
|
||||
);
|
||||
// Set to true if policy enabled and auto-enroll enabled
|
||||
this.showResetPasswordAutoEnrollWarning =
|
||||
resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled;
|
||||
|
||||
this.enforcedPasswordPolicyOptions =
|
||||
await this.policyService.getMasterPasswordPolicyOptions(policyList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async goAfterLogIn() {
|
||||
// Check master password against policy
|
||||
if (this.enforcedPasswordPolicyOptions != null) {
|
||||
const strengthResult = this.passwordGenerationService.passwordStrength(
|
||||
this.masterPassword,
|
||||
this.getPasswordStrengthUserInput()
|
||||
);
|
||||
const masterPasswordScore = strengthResult == null ? null : strengthResult.score;
|
||||
|
||||
// If invalid, save policies and require update
|
||||
if (
|
||||
!this.policyService.evaluateMasterPassword(
|
||||
masterPasswordScore,
|
||||
this.masterPassword,
|
||||
this.enforcedPasswordPolicyOptions
|
||||
)
|
||||
) {
|
||||
const policiesData: { [id: string]: PolicyData } = {};
|
||||
this.policies.data.map((p) => (policiesData[p.id] = new PolicyData(p)));
|
||||
await this.policyService.replace(policiesData);
|
||||
this.router.navigate(["update-password"]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const previousUrl = this.routerService.getPreviousUrl();
|
||||
if (previousUrl) {
|
||||
this.router.navigateByUrl(previousUrl);
|
||||
} else {
|
||||
this.router.navigate([this.successRoute]);
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
await this.stateService.setRememberEmail(this.rememberEmail);
|
||||
if (!this.rememberEmail) {
|
||||
await this.stateService.setRememberedEmail(null);
|
||||
}
|
||||
await super.submit();
|
||||
}
|
||||
|
||||
private getPasswordStrengthUserInput() {
|
||||
let userInput: string[] = [];
|
||||
const atPosition = this.email.indexOf("@");
|
||||
if (atPosition > -1) {
|
||||
userInput = userInput.concat(
|
||||
this.email
|
||||
.substr(0, atPosition)
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.split(/[^A-Za-z0-9]/)
|
||||
);
|
||||
}
|
||||
return userInput;
|
||||
}
|
||||
}
|
||||
44
apps/web/src/app/accounts/recover-delete.component.html
Normal file
44
apps/web/src/app/accounts/recover-delete.component.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="lead text-center mb-4">{{ "deleteAccount" | i18n }}</p>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>{{ "deleteRecoverDesc" | i18n }}</p>
|
||||
<div class="form-group">
|
||||
<label for="email">{{ "emailAddress" | i18n }}</label>
|
||||
<input
|
||||
id="email"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Email"
|
||||
[(ngModel)]="email"
|
||||
required
|
||||
appAutofocus
|
||||
inputmode="email"
|
||||
appInputVerbatim="false"
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<span>{{ "submit" | i18n }}</span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{ "cancel" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
42
apps/web/src/app/accounts/recover-delete.component.ts
Normal file
42
apps/web/src/app/accounts/recover-delete.component.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { DeleteRecoverRequest } from "jslib-common/models/request/deleteRecoverRequest";
|
||||
|
||||
@Component({
|
||||
selector: "app-recover-delete",
|
||||
templateUrl: "recover-delete.component.html",
|
||||
})
|
||||
export class RecoverDeleteComponent {
|
||||
email: string;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private apiService: ApiService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
const request = new DeleteRecoverRequest();
|
||||
request.email = this.email.trim().toLowerCase();
|
||||
this.formPromise = this.apiService.postAccountRecoverDelete(request);
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("deleteRecoverEmailSent")
|
||||
);
|
||||
this.router.navigate(["/"]);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
76
apps/web/src/app/accounts/recover-two-factor.component.html
Normal file
76
apps/web/src/app/accounts/recover-two-factor.component.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="lead text-center mb-4">{{ "recoverAccountTwoStep" | i18n }}</p>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>
|
||||
{{ "recoverAccountTwoStepDesc" | i18n }}
|
||||
<a
|
||||
href="https://bitwarden.com/help/lost-two-step-device/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>{{ "learnMore" | i18n }}</a
|
||||
>
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="email">{{ "emailAddress" | i18n }}</label>
|
||||
<input
|
||||
id="email"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Email"
|
||||
[(ngModel)]="email"
|
||||
required
|
||||
appAutofocus
|
||||
inputmode="email"
|
||||
appInputVerbatim="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="password"
|
||||
name="MasterPassword"
|
||||
class="form-control"
|
||||
[(ngModel)]="masterPassword"
|
||||
required
|
||||
appInputVerbatim
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="recoveryCode">{{ "recoveryCodeTitle" | i18n }}</label>
|
||||
<input
|
||||
id="recoveryCode"
|
||||
class="text-monospace form-control"
|
||||
type="text"
|
||||
name="RecoveryCode"
|
||||
[(ngModel)]="recoveryCode"
|
||||
required
|
||||
appInputVerbatim
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<span>{{ "submit" | i18n }}</span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{ "cancel" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
51
apps/web/src/app/accounts/recover-two-factor.component.ts
Normal file
51
apps/web/src/app/accounts/recover-two-factor.component.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { TwoFactorRecoveryRequest } from "jslib-common/models/request/twoFactorRecoveryRequest";
|
||||
|
||||
@Component({
|
||||
selector: "app-recover-two-factor",
|
||||
templateUrl: "recover-two-factor.component.html",
|
||||
})
|
||||
export class RecoverTwoFactorComponent {
|
||||
email: string;
|
||||
masterPassword: string;
|
||||
recoveryCode: string;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private apiService: ApiService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private cryptoService: CryptoService,
|
||||
private authService: AuthService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
const request = new TwoFactorRecoveryRequest();
|
||||
request.recoveryCode = this.recoveryCode.replace(/\s/g, "").toLowerCase();
|
||||
request.email = this.email.trim().toLowerCase();
|
||||
const key = await this.authService.makePreloginKey(this.masterPassword, request.email);
|
||||
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
|
||||
this.formPromise = this.apiService.postTwoFactorRecover(request);
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("twoStepRecoverDisabled")
|
||||
);
|
||||
this.router.navigate(["/"]);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
355
apps/web/src/app/accounts/register.component.html
Normal file
355
apps/web/src/app/accounts/register.component.html
Normal file
@@ -0,0 +1,355 @@
|
||||
<div class="layout" [ngClass]="['layout', layout]">
|
||||
<!-- TEAMS 1 Header -->
|
||||
<header
|
||||
class="header"
|
||||
*ngIf="
|
||||
layout === 'default' ||
|
||||
layout === 'teams' ||
|
||||
layout === 'teams1' ||
|
||||
layout === 'teams2' ||
|
||||
layout === 'enterprise' ||
|
||||
layout === 'enterprise1' ||
|
||||
layout === 'enterprise2' ||
|
||||
layout === 'cnetcmpgnent' ||
|
||||
layout === 'cnetcmpgnteams' ||
|
||||
layout === 'cnetcmpgnind'
|
||||
"
|
||||
>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-7">
|
||||
<img
|
||||
alt="Bitwarden"
|
||||
class="logo mb-2"
|
||||
src="../../images/register-layout/logo-horizontal-white.svg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||
<div class="row">
|
||||
<div class="col-7" *ngIf="layout">
|
||||
<div class="mt-5">
|
||||
<!-- Default Body -->
|
||||
<div
|
||||
*ngIf="
|
||||
layout === 'teams' ||
|
||||
layout === 'enterprise' ||
|
||||
layout === 'enterprise1' ||
|
||||
layout === 'default'
|
||||
"
|
||||
>
|
||||
<h1>The Bitwarden Password Manager</h1>
|
||||
<h2>
|
||||
Trusted by millions of individuals, teams, and organizations worldwide for secure
|
||||
password storage and sharing.
|
||||
</h2>
|
||||
<p>Store logins, secure notes, and more</p>
|
||||
<p>Collaborate and share securely</p>
|
||||
<p>Access anywhere on any device</p>
|
||||
<p>Create your account to get started</p>
|
||||
</div>
|
||||
|
||||
<!-- Teams & Enterprise Body -->
|
||||
<div *ngIf="layout === 'teams1' || layout === 'teams2' || layout === 'enterprise2'">
|
||||
<h1>
|
||||
Start Your <span *ngIf="layout === 'teams1' || layout === 'teams1'">Teams<br /></span
|
||||
><span *ngIf="layout === 'enterprise2'">Enterprise</span> Free Trial Now
|
||||
</h1>
|
||||
<h2>
|
||||
Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure
|
||||
password storage and sharing.
|
||||
</h2>
|
||||
<p>Collaborate and share securely</p>
|
||||
<p>Deploy and manage quickly and easily</p>
|
||||
<p>Access anywhere on any device</p>
|
||||
<p>Create your account to get started</p>
|
||||
</div>
|
||||
|
||||
<!-- CNET Campaign Teams & Enterprise Body -->
|
||||
<div *ngIf="layout === 'cnetcmpgnteams' || layout === 'cnetcmpgnent'">
|
||||
<h1>
|
||||
Start Your <span *ngIf="layout === 'cnetcmpgnteams'">Teams<br /></span
|
||||
><span *ngIf="layout === 'cnetcmpgnent'">Enterprise</span> Free Trial Now
|
||||
</h1>
|
||||
<h2>
|
||||
Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure
|
||||
password storage and sharing.
|
||||
</h2>
|
||||
<p>Collaborate and share securely</p>
|
||||
<p>Deploy and manage quickly and easily</p>
|
||||
<p>Access anywhere on any device</p>
|
||||
<p>Create your account to get started</p>
|
||||
</div>
|
||||
|
||||
<!-- CNET Campaign Premium Body -->
|
||||
<div *ngIf="layout === 'cnetcmpgnind'">
|
||||
<h1>Start Your Premium Account Now</h1>
|
||||
<h2>
|
||||
Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure
|
||||
password storage and sharing.
|
||||
</h2>
|
||||
<p>Store logins, secure notes, and more</p>
|
||||
<p>Secure your account with advanced two-step login</p>
|
||||
<p>Access anywhere on any device</p>
|
||||
<p>Create your account to get started</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div [ngClass]="{ 'col-5': layout, 'col-12': !layout }">
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div [ngClass]="{ 'col-5': !layout, 'col-12': layout }">
|
||||
<h1 class="lead text-center mb-4" *ngIf="!layout">{{ "createAccount" | i18n }}</h1>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<app-callout
|
||||
title="{{ 'createOrganizationStep1' | i18n }}"
|
||||
type="info"
|
||||
icon="bwi bwi-thumb-tack"
|
||||
*ngIf="showCreateOrgMessage"
|
||||
>
|
||||
{{ "createOrganizationCreatePersonalAccount" | i18n }}
|
||||
</app-callout>
|
||||
<div class="form-group">
|
||||
<label for="email">{{ "emailAddress" | i18n }}</label>
|
||||
<input
|
||||
id="email"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Email"
|
||||
[(ngModel)]="email"
|
||||
required
|
||||
[appAutofocus]="email === ''"
|
||||
inputmode="email"
|
||||
appInputVerbatim="false"
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "emailAddressDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">{{ "yourName" | i18n }}</label>
|
||||
<input
|
||||
id="name"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Name"
|
||||
[(ngModel)]="name"
|
||||
[appAutofocus]="email !== ''"
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "yourNameDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<app-callout
|
||||
type="info"
|
||||
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||
*ngIf="enforcedPolicyOptions"
|
||||
>
|
||||
</app-callout>
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
<div class="d-flex">
|
||||
<div class="w-100">
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="MasterPassword"
|
||||
class="text-monospace form-control mb-1"
|
||||
[(ngModel)]="masterPassword"
|
||||
(input)="updatePasswordStrength()"
|
||||
required
|
||||
appInputVerbatim
|
||||
/>
|
||||
<app-password-strength [score]="masterPasswordScore" [showText]="true">
|
||||
</app-password-strength>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 btn btn-link"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword(false)"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-eye': !showPassword,
|
||||
'bwi-eye-slash': showPassword
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
<div class="progress-bar invisible"></div>
|
||||
</div>
|
||||
</div>
|
||||
<small class="form-text text-muted">{{ "masterPassDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
|
||||
<div class="d-flex">
|
||||
<input
|
||||
id="masterPasswordRetype"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="MasterPasswordRetype"
|
||||
class="text-monospace form-control"
|
||||
[(ngModel)]="confirmMasterPassword"
|
||||
required
|
||||
appInputVerbatim
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 btn btn-link"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword(true)"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="hint">{{ "masterPassHint" | i18n }}</label>
|
||||
<input
|
||||
id="hint"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Hint"
|
||||
[(ngModel)]="hint"
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div [hidden]="!showCaptcha()">
|
||||
<iframe id="hcaptcha_iframe" height="80"></iframe>
|
||||
</div>
|
||||
<div class="form-group" *ngIf="showTerms">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="acceptPolicies"
|
||||
[(ngModel)]="acceptPolicies"
|
||||
name="AcceptPolicies"
|
||||
/>
|
||||
<label class="form-check-label small text-muted" for="acceptPolicies">
|
||||
{{ "acceptPolicies" | i18n }}<br />
|
||||
<a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{
|
||||
"termsOfService" | i18n
|
||||
}}</a
|
||||
>,
|
||||
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{
|
||||
"privacyPolicy" | i18n
|
||||
}}</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex mb-2">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<span>{{ "submit" | i18n }}</span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{ "cancel" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-7 d-flex align-items-center">
|
||||
<div
|
||||
*ngIf="
|
||||
layout === 'cnetcmpgnent' || layout === 'cnetcmpgnteams' || layout === 'cnetcmpgnind'
|
||||
"
|
||||
>
|
||||
<figure>
|
||||
<figcaption>
|
||||
<cite>
|
||||
<img
|
||||
src="../../images/register-layout/cnet-logo.svg"
|
||||
class="w-25 d-block mx-auto"
|
||||
alt="cnet logo"
|
||||
/>
|
||||
</cite>
|
||||
</figcaption>
|
||||
<blockquote class="mx-auto text-center px-4">
|
||||
"No more excuses; start using Bitwarden today. The identity you save could be your
|
||||
own. The money definitely will be."
|
||||
</blockquote>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="
|
||||
layout === 'teams' ||
|
||||
layout === 'teams1' ||
|
||||
layout === 'teams2' ||
|
||||
layout === 'enterprise' ||
|
||||
layout === 'enterprise1' ||
|
||||
layout === 'enterprise2' ||
|
||||
layout === 'default'
|
||||
"
|
||||
>
|
||||
<figure>
|
||||
<figcaption>
|
||||
<cite>
|
||||
<img
|
||||
src="../../images/register-layout/forbes-logo.svg"
|
||||
class="w-25 d-block mx-auto"
|
||||
alt="Forbes Logo"
|
||||
/>
|
||||
</cite>
|
||||
</figcaption>
|
||||
<blockquote class="mx-auto text-center px-4">
|
||||
“Bitwarden boasts the backing of some of the world's best security experts and an
|
||||
attractive, easy-to-use interface”
|
||||
</blockquote>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="
|
||||
layout === 'cnetcmpgnent' || layout === 'cnetcmpgnteams' || layout === 'cnetcmpgnind'
|
||||
"
|
||||
class="col-5 d-flex align-items-center justify-content-center"
|
||||
>
|
||||
<img
|
||||
src="../../images/register-layout/usnews-360-badge.svg"
|
||||
class="w-50 d-block"
|
||||
alt="US News 360 Reviews Best Password Manager"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="
|
||||
layout === 'teams' ||
|
||||
layout === 'teams1' ||
|
||||
layout === 'teams2' ||
|
||||
layout === 'enterprise' ||
|
||||
layout === 'enterprise1' ||
|
||||
layout === 'enterprise2' ||
|
||||
layout === 'default'
|
||||
"
|
||||
class="col-5 d-flex align-items-center justify-content-center"
|
||||
>
|
||||
<img
|
||||
src="../../images/register-layout/usnews-360-badge.svg"
|
||||
class="w-50 d-block"
|
||||
alt="US News 360 Reviews Best Password Manager"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
149
apps/web/src/app/accounts/register.component.ts
Normal file
149
apps/web/src/app/accounts/register.component.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component";
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { PolicyData } from "jslib-common/models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
|
||||
import { Policy } from "jslib-common/models/domain/policy";
|
||||
import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest";
|
||||
|
||||
import { RouterService } from "../services/router.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-register",
|
||||
templateUrl: "register.component.html",
|
||||
})
|
||||
export class RegisterComponent extends BaseRegisterComponent {
|
||||
showCreateOrgMessage = false;
|
||||
layout = "";
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
|
||||
private policies: Policy[];
|
||||
|
||||
constructor(
|
||||
authService: AuthService,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
private route: ActivatedRoute,
|
||||
stateService: StateService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
private policyService: PolicyService,
|
||||
environmentService: EnvironmentService,
|
||||
logService: LogService,
|
||||
private routerService: RouterService
|
||||
) {
|
||||
super(
|
||||
authService,
|
||||
router,
|
||||
i18nService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
stateService,
|
||||
platformUtilsService,
|
||||
passwordGenerationService,
|
||||
environmentService,
|
||||
logService
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.queryParams.pipe(first()).subscribe((qParams) => {
|
||||
this.referenceData = new ReferenceEventRequest();
|
||||
if (qParams.email != null && qParams.email.indexOf("@") > -1) {
|
||||
this.email = qParams.email;
|
||||
}
|
||||
if (qParams.premium != null) {
|
||||
this.routerService.setPreviousUrl("/settings/premium");
|
||||
} else if (qParams.org != null) {
|
||||
this.showCreateOrgMessage = true;
|
||||
this.referenceData.flow = qParams.org;
|
||||
const route = this.router.createUrlTree(["create-organization"], {
|
||||
queryParams: { plan: qParams.org },
|
||||
});
|
||||
this.routerService.setPreviousUrl(route.toString());
|
||||
}
|
||||
if (qParams.layout != null) {
|
||||
this.layout = this.referenceData.layout = qParams.layout;
|
||||
}
|
||||
if (qParams.reference != null) {
|
||||
this.referenceData.id = qParams.reference;
|
||||
} else {
|
||||
this.referenceData.id = ("; " + document.cookie)
|
||||
.split("; reference=")
|
||||
.pop()
|
||||
.split(";")
|
||||
.shift();
|
||||
}
|
||||
// Are they coming from an email for sponsoring a families organization
|
||||
if (qParams.sponsorshipToken != null) {
|
||||
// After logging in redirect them to setup the families sponsorship
|
||||
const route = this.router.createUrlTree(["setup/families-for-enterprise"], {
|
||||
queryParams: { plan: qParams.sponsorshipToken },
|
||||
});
|
||||
this.routerService.setPreviousUrl(route.toString());
|
||||
}
|
||||
if (this.referenceData.id === "") {
|
||||
this.referenceData.id = null;
|
||||
}
|
||||
});
|
||||
const invite = await this.stateService.getOrganizationInvitation();
|
||||
if (invite != null) {
|
||||
try {
|
||||
const policies = await this.apiService.getPoliciesByToken(
|
||||
invite.organizationId,
|
||||
invite.token,
|
||||
invite.email,
|
||||
invite.organizationUserId
|
||||
);
|
||||
if (policies.data != null) {
|
||||
const policiesData = policies.data.map((p) => new PolicyData(p));
|
||||
this.policies = policiesData.map((p) => new Policy(p));
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.policies != null) {
|
||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(
|
||||
this.policies
|
||||
);
|
||||
}
|
||||
|
||||
await super.ngOnInit();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (
|
||||
this.enforcedPolicyOptions != null &&
|
||||
!this.policyService.evaluateMasterPassword(
|
||||
this.masterPasswordScore,
|
||||
this.masterPassword,
|
||||
this.enforcedPolicyOptions
|
||||
)
|
||||
) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("masterPasswordPolicyRequirementsNotMet")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await super.submit();
|
||||
}
|
||||
}
|
||||
55
apps/web/src/app/accounts/remove-password.component.html
Normal file
55
apps/web/src/app/accounts/remove-password.component.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
|
||||
<div>
|
||||
<img class="mb-4 logo logo-themed" alt="Bitwarden" />
|
||||
<p class="text-center">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" *ngIf="!loading">
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="lead text-center mb-4">{{ "removeMasterPassword" | i18n }}</p>
|
||||
<hr />
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-block"
|
||||
(click)="convert()"
|
||||
[disabled]="actionPromise"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
*ngIf="continuing"
|
||||
></i>
|
||||
{{ "removeMasterPassword" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-block"
|
||||
(click)="leave()"
|
||||
[disabled]="actionPromise"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
*ngIf="leaving"
|
||||
></i>
|
||||
{{ "leaveOrganization" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
9
apps/web/src/app/accounts/remove-password.component.ts
Normal file
9
apps/web/src/app/accounts/remove-password.component.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { RemovePasswordComponent as BaseRemovePasswordComponent } from "jslib-angular/components/remove-password.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-remove-password",
|
||||
templateUrl: "remove-password.component.html",
|
||||
})
|
||||
export class RemovePasswordComponent extends BaseRemovePasswordComponent {}
|
||||
117
apps/web/src/app/accounts/set-password.component.html
Normal file
117
apps/web/src/app/accounts/set-password.component.html
Normal file
@@ -0,0 +1,117 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="lead text-center mb-4">{{ "setMasterPassword" | i18n }}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body text-center" *ngIf="syncLoading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
{{ "loading" | i18n }}
|
||||
</div>
|
||||
<div class="card-body" *ngIf="!syncLoading">
|
||||
<app-callout type="info">{{ "ssoCompleteRegistration" | i18n }}</app-callout>
|
||||
<app-callout
|
||||
type="warning"
|
||||
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
|
||||
*ngIf="resetPasswordAutoEnroll"
|
||||
>
|
||||
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
|
||||
</app-callout>
|
||||
<div class="form-group">
|
||||
<app-callout
|
||||
type="info"
|
||||
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||
*ngIf="enforcedPolicyOptions"
|
||||
>
|
||||
</app-callout>
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
<div class="d-flex">
|
||||
<div class="w-100">
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="MasterPasswordHash"
|
||||
class="text-monospace form-control mb-1"
|
||||
[(ngModel)]="masterPassword"
|
||||
(input)="updatePasswordStrength()"
|
||||
required
|
||||
appInputVerbatim
|
||||
/>
|
||||
<app-password-strength [score]="masterPasswordScore" [showText]="true">
|
||||
</app-password-strength>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 btn btn-link"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword(false)"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
<div class="progress-bar invisible"></div>
|
||||
</div>
|
||||
</div>
|
||||
<small class="form-text text-muted">{{ "masterPassDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
|
||||
<div class="d-flex">
|
||||
<input
|
||||
id="masterPasswordRetype"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="MasterPasswordRetype"
|
||||
class="text-monospace form-control"
|
||||
[(ngModel)]="masterPasswordRetype"
|
||||
required
|
||||
appInputVerbatim
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 btn btn-link"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword(true)"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="hint">{{ "masterPassHint" | i18n }}</label>
|
||||
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint" />
|
||||
<small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span>{{ "submit" | i18n }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-block ml-2 mt-0"
|
||||
(click)="logOut()"
|
||||
>
|
||||
{{ "logOut" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
47
apps/web/src/app/accounts/set-password.component.ts
Normal file
47
apps/web/src/app/accounts/set-password.component.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component";
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-set-password",
|
||||
templateUrl: "set-password.component.html",
|
||||
})
|
||||
export class SetPasswordComponent extends BaseSetPasswordComponent {
|
||||
constructor(
|
||||
apiService: ApiService,
|
||||
i18nService: I18nService,
|
||||
cryptoService: CryptoService,
|
||||
messagingService: MessagingService,
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
policyService: PolicyService,
|
||||
router: Router,
|
||||
syncService: SyncService,
|
||||
route: ActivatedRoute,
|
||||
stateService: StateService
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
cryptoService,
|
||||
messagingService,
|
||||
passwordGenerationService,
|
||||
platformUtilsService,
|
||||
policyService,
|
||||
router,
|
||||
apiService,
|
||||
syncService,
|
||||
route,
|
||||
stateService
|
||||
);
|
||||
}
|
||||
}
|
||||
52
apps/web/src/app/accounts/sso.component.html
Normal file
52
apps/web/src/app/accounts/sso.component.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
class="container"
|
||||
[appApiAction]="initiateSsoFormPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<img class="logo mb-2 logo-themed" alt="Bitwarden" />
|
||||
<div class="card d-block mt-4">
|
||||
<div class="card-body" *ngIf="loggingIn">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
{{ "loading" | i18n }}
|
||||
</div>
|
||||
<div class="card-body" *ngIf="!loggingIn">
|
||||
<p>{{ "ssoLogInWithOrgIdentifier" | i18n }}</p>
|
||||
<div class="form-group">
|
||||
<label for="identifier">{{ "organizationIdentifier" | i18n }}</label>
|
||||
<input
|
||||
id="identifier"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Identifier"
|
||||
[(ngModel)]="identifier"
|
||||
required
|
||||
appAutofocus
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<span> <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }} </span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{ "cancel" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
72
apps/web/src/app/accounts/sso.component.ts
Normal file
72
apps/web/src/app/accounts/sso.component.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-sso",
|
||||
templateUrl: "sso.component.html",
|
||||
})
|
||||
export class SsoComponent extends BaseSsoComponent {
|
||||
constructor(
|
||||
authService: AuthService,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
route: ActivatedRoute,
|
||||
stateService: StateService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
apiService: ApiService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
environmentService: EnvironmentService,
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
logService: LogService
|
||||
) {
|
||||
super(
|
||||
authService,
|
||||
router,
|
||||
i18nService,
|
||||
route,
|
||||
stateService,
|
||||
platformUtilsService,
|
||||
apiService,
|
||||
cryptoFunctionService,
|
||||
environmentService,
|
||||
passwordGenerationService,
|
||||
logService
|
||||
);
|
||||
this.redirectUri = window.location.origin + "/sso-connector.html";
|
||||
this.clientId = "web";
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
super.ngOnInit();
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
if (qParams.identifier != null) {
|
||||
this.identifier = qParams.identifier;
|
||||
} else {
|
||||
const storedIdentifier = await this.stateService.getSsoOrgIdentifier();
|
||||
if (storedIdentifier != null) {
|
||||
this.identifier = storedIdentifier;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async submit() {
|
||||
await this.stateService.setSsoOrganizationIdentifier(this.identifier);
|
||||
if (this.clientId === "browser") {
|
||||
document.cookie = `ssoHandOffMessage=${this.i18nService.t("ssoHandOff")};SameSite=strict`;
|
||||
}
|
||||
super.submit();
|
||||
}
|
||||
}
|
||||
68
apps/web/src/app/accounts/two-factor-options.component.html
Normal file
68
apps/web/src/app/accounts/two-factor-options.component.html
Normal file
@@ -0,0 +1,68 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="twoStepOptionsTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="twoStepOptionsTitle">{{ "twoStepOptions" | i18n }}</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="list-group list-group-flush-2fa">
|
||||
<div *ngFor="let p of providers" class="list-group-item list-group-item-action">
|
||||
<div class="two-factor-content">
|
||||
<div class="logo-col">
|
||||
<img [class]="'mfaType' + p.type" [alt]="p.name + ' logo'" />
|
||||
</div>
|
||||
<div class="text-col">
|
||||
<h3>{{ p.name }}</h3>
|
||||
{{ p.description }}
|
||||
</div>
|
||||
<div class="btn-col">
|
||||
<button
|
||||
[attr.aria-describedby]="p.name"
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-sm"
|
||||
(click)="choose(p)"
|
||||
>
|
||||
{{ "select" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group-item list-group-item-action" (click)="recover()">
|
||||
<div class="two-factor-content">
|
||||
<div class="logo-col">
|
||||
<img class="recovery-code-img" alt="rc logo" />
|
||||
</div>
|
||||
<div class="text-col">
|
||||
<h3>{{ "recoveryCodeTitle" | i18n }}</h3>
|
||||
{{ "recoveryCodeDesc" | i18n }}
|
||||
</div>
|
||||
<div class="btn-col">
|
||||
<button
|
||||
[attr.aria-descibedby]="'recoveryCodeTitle' | i18n"
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-sm"
|
||||
(click)="recover()"
|
||||
>
|
||||
{{ "select" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "close" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
22
apps/web/src/app/accounts/two-factor-options.component.ts
Normal file
22
apps/web/src/app/accounts/two-factor-options.component.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "jslib-angular/components/two-factor-options.component";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-two-factor-options",
|
||||
templateUrl: "two-factor-options.component.html",
|
||||
})
|
||||
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
|
||||
constructor(
|
||||
twoFactorService: TwoFactorService,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService
|
||||
) {
|
||||
super(twoFactorService, router, i18nService, platformUtilsService, window);
|
||||
}
|
||||
}
|
||||
155
apps/web/src/app/accounts/two-factor.component.html
Normal file
155
apps/web/src/app/accounts/two-factor.component.html
Normal file
@@ -0,0 +1,155 @@
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
class="container"
|
||||
ngNativeValidate
|
||||
autocomplete="off"
|
||||
>
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div
|
||||
class="col-5"
|
||||
[ngClass]="{
|
||||
'col-9':
|
||||
selectedProviderType === providerType.Duo ||
|
||||
selectedProviderType === providerType.OrganizationDuo
|
||||
}"
|
||||
>
|
||||
<p class="lead text-center mb-4">{{ title }}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
selectedProviderType === providerType.Email ||
|
||||
selectedProviderType === providerType.Authenticator
|
||||
"
|
||||
>
|
||||
<p *ngIf="selectedProviderType === providerType.Authenticator">
|
||||
{{ "enterVerificationCodeApp" | i18n }}
|
||||
</p>
|
||||
<p *ngIf="selectedProviderType === providerType.Email">
|
||||
{{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }}
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
|
||||
<input
|
||||
id="code"
|
||||
type="text"
|
||||
name="Code"
|
||||
class="form-control"
|
||||
[(ngModel)]="token"
|
||||
required
|
||||
appAutofocus
|
||||
inputmode="tel"
|
||||
appInputVerbatim
|
||||
/>
|
||||
<small class="form-text" *ngIf="selectedProviderType === providerType.Email">
|
||||
<a
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="sendEmail(true)"
|
||||
[appApiAction]="emailPromise"
|
||||
*ngIf="selectedProviderType === providerType.Email"
|
||||
>
|
||||
{{ "sendVerificationCodeEmailAgain" | i18n }}
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
|
||||
<p class="text-center">{{ "insertYubiKey" | i18n }}</p>
|
||||
<picture>
|
||||
<source srcset="../../images/yubikey.avif" type="image/avif" />
|
||||
<source srcset="../../images/yubikey.webp" type="image/webp" />
|
||||
<img src="../../images/yubikey.jpg" class="rounded img-fluid mb-3" alt="" />
|
||||
</picture>
|
||||
<div class="form-group">
|
||||
<label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
|
||||
<input
|
||||
id="code"
|
||||
type="password"
|
||||
name="Code"
|
||||
class="form-control"
|
||||
[(ngModel)]="token"
|
||||
required
|
||||
appAutofocus
|
||||
appInputVerbatim
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
|
||||
<div id="web-authn-frame" class="mb-3">
|
||||
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
selectedProviderType === providerType.Duo ||
|
||||
selectedProviderType === providerType.OrganizationDuo
|
||||
"
|
||||
>
|
||||
<div id="duo-frame" class="mb-3">
|
||||
<iframe id="duo_iframe"></iframe>
|
||||
</div>
|
||||
</ng-container>
|
||||
<i
|
||||
class="bwi bwi-spinner text-muted bwi-spin pull-right"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
*ngIf="form.loading && selectedProviderType === providerType.WebAuthn"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<div class="form-check" *ngIf="selectedProviderType != null">
|
||||
<input
|
||||
id="remember"
|
||||
type="checkbox"
|
||||
name="Remember"
|
||||
class="form-check-input"
|
||||
[(ngModel)]="remember"
|
||||
/>
|
||||
<label for="remember" class="form-check-label">{{ "rememberMe" | i18n }}</label>
|
||||
</div>
|
||||
<ng-container *ngIf="selectedProviderType == null">
|
||||
<p>{{ "noTwoStepProviders" | i18n }}</p>
|
||||
<p>{{ "noTwoStepProviders2" | i18n }}</p>
|
||||
</ng-container>
|
||||
<hr />
|
||||
<div [hidden]="!showCaptcha()">
|
||||
<iframe id="hcaptcha_iframe" height="80"></iframe>
|
||||
</div>
|
||||
<div class="d-flex mb-3">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
*ngIf="
|
||||
selectedProviderType != null &&
|
||||
selectedProviderType !== providerType.Duo &&
|
||||
selectedProviderType !== providerType.OrganizationDuo &&
|
||||
selectedProviderType !== providerType.WebAuthn
|
||||
"
|
||||
>
|
||||
<span>
|
||||
<i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "continue" | i18n }}
|
||||
</span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{ "cancel" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<a href="#" appStopClick (click)="anotherMethod()">{{
|
||||
"useAnotherTwoStepMethod" | i18n
|
||||
}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<ng-template #twoFactorOptions></ng-template>
|
||||
90
apps/web/src/app/accounts/two-factor.component.ts
Normal file
90
apps/web/src/app/accounts/two-factor.component.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { AppIdService } from "jslib-common/abstractions/appId.service";
|
||||
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
|
||||
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
|
||||
|
||||
import { RouterService } from "../services/router.service";
|
||||
|
||||
import { TwoFactorOptionsComponent } from "./two-factor-options.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-two-factor",
|
||||
templateUrl: "two-factor.component.html",
|
||||
})
|
||||
export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||
@ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true })
|
||||
twoFactorOptionsModal: ViewContainerRef;
|
||||
|
||||
constructor(
|
||||
authService: AuthService,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
apiService: ApiService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
stateService: StateService,
|
||||
environmentService: EnvironmentService,
|
||||
private modalService: ModalService,
|
||||
route: ActivatedRoute,
|
||||
logService: LogService,
|
||||
twoFactorService: TwoFactorService,
|
||||
appIdService: AppIdService,
|
||||
private routerService: RouterService
|
||||
) {
|
||||
super(
|
||||
authService,
|
||||
router,
|
||||
i18nService,
|
||||
apiService,
|
||||
platformUtilsService,
|
||||
window,
|
||||
environmentService,
|
||||
stateService,
|
||||
route,
|
||||
logService,
|
||||
twoFactorService,
|
||||
appIdService
|
||||
);
|
||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
||||
}
|
||||
|
||||
async anotherMethod() {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
TwoFactorOptionsComponent,
|
||||
this.twoFactorOptionsModal,
|
||||
(comp) => {
|
||||
comp.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
|
||||
modal.close();
|
||||
this.selectedProviderType = provider;
|
||||
await this.init();
|
||||
});
|
||||
comp.onRecoverSelected.subscribe(() => {
|
||||
modal.close();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async goAfterLogIn() {
|
||||
const previousUrl = this.routerService.getPreviousUrl();
|
||||
if (previousUrl) {
|
||||
this.router.navigateByUrl(previousUrl);
|
||||
} else {
|
||||
this.router.navigate([this.successRoute], {
|
||||
queryParams: {
|
||||
identifier: this.identifier,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
90
apps/web/src/app/accounts/update-password.component.html
Normal file
90
apps/web/src/app/accounts/update-password.component.html
Normal file
@@ -0,0 +1,90 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-4">
|
||||
<p class="lead text-center mb-4">{{ "updateMasterPassword" | i18n }}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<app-callout type="warning">{{ "masterPasswordInvalidWarning" | i18n }} </app-callout>
|
||||
<app-callout
|
||||
type="info"
|
||||
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||
*ngIf="enforcedPolicyOptions"
|
||||
></app-callout>
|
||||
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
autocomplete="off"
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="currentMasterPassword">{{ "currentMasterPass" | i18n }}</label>
|
||||
<input
|
||||
id="currentMasterPassword"
|
||||
type="password"
|
||||
name="MasterPasswordHash"
|
||||
class="form-control"
|
||||
[(ngModel)]="currentMasterPassword"
|
||||
required
|
||||
appInputVerbatim
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="newMasterPassword">{{ "newMasterPass" | i18n }}</label>
|
||||
<input
|
||||
id="newMasterPassword"
|
||||
type="password"
|
||||
name="NewMasterPasswordHash"
|
||||
class="form-control mb-1"
|
||||
[(ngModel)]="masterPassword"
|
||||
(input)="updatePasswordStrength()"
|
||||
required
|
||||
appInputVerbatim
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
<app-password-strength
|
||||
[score]="masterPasswordScore"
|
||||
[showText]="true"
|
||||
></app-password-strength>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="masterPasswordRetype">{{ "confirmNewMasterPass" | i18n }}</label>
|
||||
<input
|
||||
id="masterPasswordRetype"
|
||||
type="password"
|
||||
name="MasterPasswordRetype"
|
||||
class="form-control"
|
||||
[(ngModel)]="masterPasswordRetype"
|
||||
required
|
||||
appInputVerbatim
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i
|
||||
class="fa fa-spinner fa-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span>{{ "changeMasterPassword" | i18n }}</span>
|
||||
</button>
|
||||
<button (click)="cancel()" type="button" class="btn btn-outline-secondary">
|
||||
<span>{{ "cancel" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
48
apps/web/src/app/accounts/update-password.component.ts
Normal file
48
apps/web/src/app/accounts/update-password.component.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "jslib-angular/components/update-password.component";
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-update-password",
|
||||
templateUrl: "update-password.component.html",
|
||||
})
|
||||
export class UpdatePasswordComponent extends BaseUpdatePasswordComponent {
|
||||
constructor(
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
policyService: PolicyService,
|
||||
cryptoService: CryptoService,
|
||||
messagingService: MessagingService,
|
||||
apiService: ApiService,
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
userVerificationService: UserVerificationService
|
||||
) {
|
||||
super(
|
||||
router,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
passwordGenerationService,
|
||||
policyService,
|
||||
cryptoService,
|
||||
messagingService,
|
||||
apiService,
|
||||
stateService,
|
||||
userVerificationService,
|
||||
logService
|
||||
);
|
||||
}
|
||||
}
|
||||
105
apps/web/src/app/accounts/update-temp-password.component.html
Normal file
105
apps/web/src/app/accounts/update-temp-password.component.html
Normal file
@@ -0,0 +1,105 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-4">
|
||||
<p class="lead text-center mb-4">{{ "updateMasterPassword" | i18n }}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<app-callout type="warning">{{ "updateMasterPasswordWarning" | i18n }} </app-callout>
|
||||
<div class="form-group">
|
||||
<app-callout
|
||||
type="info"
|
||||
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||
*ngIf="enforcedPolicyOptions"
|
||||
>
|
||||
</app-callout>
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
<div class="d-flex">
|
||||
<div class="w-100">
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="MasterPasswordHash"
|
||||
class="text-monospace form-control mb-1"
|
||||
[(ngModel)]="masterPassword"
|
||||
(input)="updatePasswordStrength()"
|
||||
required
|
||||
appInputVerbatim
|
||||
/>
|
||||
<app-password-strength [score]="masterPasswordScore" [showText]="true">
|
||||
</app-password-strength>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 btn btn-link"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword(false)"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
<div class="progress-bar invisible"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
|
||||
<div class="d-flex">
|
||||
<input
|
||||
id="masterPasswordRetype"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="MasterPasswordRetype"
|
||||
class="text-monospace form-control"
|
||||
[(ngModel)]="masterPasswordRetype"
|
||||
required
|
||||
appInputVerbatim
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 btn btn-link"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword(true)"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="hint">{{ "masterPassHint" | i18n }}</label>
|
||||
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint" />
|
||||
<small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span>{{ "submit" | i18n }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-block ml-2 mt-0"
|
||||
(click)="logOut()"
|
||||
>
|
||||
{{ "logOut" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
45
apps/web/src/app/accounts/update-temp-password.component.ts
Normal file
45
apps/web/src/app/accounts/update-temp-password.component.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "jslib-angular/components/update-temp-password.component";
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-update-temp-password",
|
||||
templateUrl: "update-temp-password.component.html",
|
||||
})
|
||||
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
policyService: PolicyService,
|
||||
cryptoService: CryptoService,
|
||||
messagingService: MessagingService,
|
||||
apiService: ApiService,
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
syncService: SyncService
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
passwordGenerationService,
|
||||
policyService,
|
||||
cryptoService,
|
||||
messagingService,
|
||||
apiService,
|
||||
stateService,
|
||||
syncService,
|
||||
logService
|
||||
);
|
||||
}
|
||||
}
|
||||
13
apps/web/src/app/accounts/verify-email-token.component.html
Normal file
13
apps/web/src/app/accounts/verify-email-token.component.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<div class="mt-5 d-flex justify-content-center">
|
||||
<div>
|
||||
<img class="mb-4 logo logo-themed" alt="Bitwarden" />
|
||||
<p class="text-center">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
48
apps/web/src/app/accounts/verify-email-token.component.ts
Normal file
48
apps/web/src/app/accounts/verify-email-token.component.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { VerifyEmailRequest } from "jslib-common/models/request/verifyEmailRequest";
|
||||
|
||||
@Component({
|
||||
selector: "app-verify-email-token",
|
||||
templateUrl: "verify-email-token.component.html",
|
||||
})
|
||||
export class VerifyEmailTokenComponent implements OnInit {
|
||||
constructor(
|
||||
private router: Router,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private route: ActivatedRoute,
|
||||
private apiService: ApiService,
|
||||
private logService: LogService,
|
||||
private stateService: StateService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
if (qParams.userId != null && qParams.token != null) {
|
||||
try {
|
||||
await this.apiService.postAccountVerifyEmailToken(
|
||||
new VerifyEmailRequest(qParams.userId, qParams.token)
|
||||
);
|
||||
if (await this.stateService.getIsAuthenticated()) {
|
||||
await this.apiService.refreshIdentityToken();
|
||||
}
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("emailVerified"));
|
||||
this.router.navigate(["/"]);
|
||||
return;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("emailVerifiedFailed"));
|
||||
this.router.navigate(["/"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="lead text-center mb-4">{{ "deleteAccount" | i18n }}</p>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<app-callout type="warning">{{ "deleteAccountWarning" | i18n }}</app-callout>
|
||||
<p class="text-center">
|
||||
<strong>{{ email }}</strong>
|
||||
</p>
|
||||
<p>{{ "deleteRecoverConfirmDesc" | i18n }}</p>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-danger btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<span>{{ "deleteAccount" | i18n }}</span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{ "cancel" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
58
apps/web/src/app/accounts/verify-recover-delete.component.ts
Normal file
58
apps/web/src/app/accounts/verify-recover-delete.component.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { VerifyDeleteRecoverRequest } from "jslib-common/models/request/verifyDeleteRecoverRequest";
|
||||
|
||||
@Component({
|
||||
selector: "app-verify-recover-delete",
|
||||
templateUrl: "verify-recover-delete.component.html",
|
||||
})
|
||||
export class VerifyRecoverDeleteComponent implements OnInit {
|
||||
email: string;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
private userId: string;
|
||||
private token: string;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private apiService: ApiService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private route: ActivatedRoute,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
if (qParams.userId != null && qParams.token != null && qParams.email != null) {
|
||||
this.userId = qParams.userId;
|
||||
this.token = qParams.token;
|
||||
this.email = qParams.email;
|
||||
} else {
|
||||
this.router.navigate(["/"]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
const request = new VerifyDeleteRecoverRequest(this.userId, this.token);
|
||||
this.formPromise = this.apiService.postAccountRecoverDeleteToken(request);
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
this.i18nService.t("accountDeleted"),
|
||||
this.i18nService.t("accountDeletedDesc")
|
||||
);
|
||||
this.router.navigate(["/"]);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
apps/web/src/app/app.component.html
Normal file
1
apps/web/src/app/app.component.html
Normal file
@@ -0,0 +1 @@
|
||||
<router-outlet></router-outlet>
|
||||
315
apps/web/src/app/app.component.ts
Normal file
315
apps/web/src/app/app.component.ts
Normal file
@@ -0,0 +1,315 @@
|
||||
import { Component, NgZone, OnDestroy, OnInit, SecurityContext } from "@angular/core";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { NavigationEnd, Router } from "@angular/router";
|
||||
import * as jq from "jquery";
|
||||
import { IndividualConfig, ToastrService } from "ngx-toastr";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { EventService } from "jslib-common/abstractions/event.service";
|
||||
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||
import { NotificationsService } from "jslib-common/abstractions/notifications.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||
import { SettingsService } from "jslib-common/abstractions/settings.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||
|
||||
import { DisableSendPolicy } from "./organizations/policies/disable-send.component";
|
||||
import { MasterPasswordPolicy } from "./organizations/policies/master-password.component";
|
||||
import { PasswordGeneratorPolicy } from "./organizations/policies/password-generator.component";
|
||||
import { PersonalOwnershipPolicy } from "./organizations/policies/personal-ownership.component";
|
||||
import { RequireSsoPolicy } from "./organizations/policies/require-sso.component";
|
||||
import { ResetPasswordPolicy } from "./organizations/policies/reset-password.component";
|
||||
import { SendOptionsPolicy } from "./organizations/policies/send-options.component";
|
||||
import { SingleOrgPolicy } from "./organizations/policies/single-org.component";
|
||||
import { TwoFactorAuthenticationPolicy } from "./organizations/policies/two-factor-authentication.component";
|
||||
import { PolicyListService } from "./services/policy-list.service";
|
||||
import { RouterService } from "./services/router.service";
|
||||
|
||||
const BroadcasterSubscriptionId = "AppComponent";
|
||||
const IdleTimeout = 60000 * 10; // 10 minutes
|
||||
|
||||
@Component({
|
||||
selector: "app-root",
|
||||
templateUrl: "app.component.html",
|
||||
})
|
||||
export class AppComponent implements OnDestroy, OnInit {
|
||||
private lastActivity: number = null;
|
||||
private idleTimer: number = null;
|
||||
private isIdle = false;
|
||||
|
||||
constructor(
|
||||
private broadcasterService: BroadcasterService,
|
||||
private tokenService: TokenService,
|
||||
private folderService: FolderService,
|
||||
private settingsService: SettingsService,
|
||||
private syncService: SyncService,
|
||||
private passwordGenerationService: PasswordGenerationService,
|
||||
private cipherService: CipherService,
|
||||
private authService: AuthService,
|
||||
private router: Router,
|
||||
private toastrService: ToastrService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private ngZone: NgZone,
|
||||
private vaultTimeoutService: VaultTimeoutService,
|
||||
private cryptoService: CryptoService,
|
||||
private collectionService: CollectionService,
|
||||
private sanitizer: DomSanitizer,
|
||||
private searchService: SearchService,
|
||||
private notificationsService: NotificationsService,
|
||||
private routerService: RouterService,
|
||||
private stateService: StateService,
|
||||
private eventService: EventService,
|
||||
private policyService: PolicyService,
|
||||
protected policyListService: PolicyListService,
|
||||
private keyConnectorService: KeyConnectorService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
window.onmousemove = () => this.recordActivity();
|
||||
window.onmousedown = () => this.recordActivity();
|
||||
window.ontouchstart = () => this.recordActivity();
|
||||
window.onclick = () => this.recordActivity();
|
||||
window.onscroll = () => this.recordActivity();
|
||||
window.onkeypress = () => this.recordActivity();
|
||||
});
|
||||
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||
this.ngZone.run(async () => {
|
||||
switch (message.command) {
|
||||
case "loggedIn":
|
||||
this.notificationsService.updateConnection(false);
|
||||
break;
|
||||
case "loggedOut":
|
||||
this.routerService.setPreviousUrl(null);
|
||||
this.notificationsService.updateConnection(false);
|
||||
break;
|
||||
case "unlocked":
|
||||
this.notificationsService.updateConnection(false);
|
||||
break;
|
||||
case "authBlocked":
|
||||
this.routerService.setPreviousUrl(message.url);
|
||||
this.router.navigate(["/"]);
|
||||
break;
|
||||
case "logout":
|
||||
this.logOut(!!message.expired);
|
||||
break;
|
||||
case "lockVault":
|
||||
await this.vaultTimeoutService.lock();
|
||||
break;
|
||||
case "locked":
|
||||
this.notificationsService.updateConnection(false);
|
||||
this.router.navigate(["lock"]);
|
||||
break;
|
||||
case "lockedUrl":
|
||||
this.routerService.setPreviousUrl(message.url);
|
||||
break;
|
||||
case "syncStarted":
|
||||
break;
|
||||
case "syncCompleted":
|
||||
break;
|
||||
case "upgradeOrganization": {
|
||||
const upgradeConfirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("upgradeOrganizationDesc"),
|
||||
this.i18nService.t("upgradeOrganization"),
|
||||
this.i18nService.t("upgradeOrganization"),
|
||||
this.i18nService.t("cancel")
|
||||
);
|
||||
if (upgradeConfirmed) {
|
||||
this.router.navigate([
|
||||
"organizations",
|
||||
message.organizationId,
|
||||
"settings",
|
||||
"billing",
|
||||
]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "premiumRequired": {
|
||||
const premiumConfirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("premiumRequiredDesc"),
|
||||
this.i18nService.t("premiumRequired"),
|
||||
this.i18nService.t("learnMore"),
|
||||
this.i18nService.t("cancel")
|
||||
);
|
||||
if (premiumConfirmed) {
|
||||
this.router.navigate(["settings/premium"]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "emailVerificationRequired": {
|
||||
const emailVerificationConfirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("emailVerificationRequiredDesc"),
|
||||
this.i18nService.t("emailVerificationRequired"),
|
||||
this.i18nService.t("learnMore"),
|
||||
this.i18nService.t("cancel")
|
||||
);
|
||||
if (emailVerificationConfirmed) {
|
||||
this.platformUtilsService.launchUri(
|
||||
"https://bitwarden.com/help/create-bitwarden-account/"
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "showToast":
|
||||
this.showToast(message);
|
||||
break;
|
||||
case "setFullWidth":
|
||||
this.setFullWidth();
|
||||
break;
|
||||
case "convertAccountToKeyConnector":
|
||||
this.router.navigate(["/remove-password"]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.router.events.subscribe((event) => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
const modals = Array.from(document.querySelectorAll(".modal"));
|
||||
for (const modal of modals) {
|
||||
(jq(modal) as any).modal("hide");
|
||||
}
|
||||
|
||||
if (document.querySelector(".swal-modal") != null) {
|
||||
Swal.close(undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.policyListService.addPolicies([
|
||||
new TwoFactorAuthenticationPolicy(),
|
||||
new MasterPasswordPolicy(),
|
||||
new PasswordGeneratorPolicy(),
|
||||
new SingleOrgPolicy(),
|
||||
new RequireSsoPolicy(),
|
||||
new PersonalOwnershipPolicy(),
|
||||
new DisableSendPolicy(),
|
||||
new SendOptionsPolicy(),
|
||||
new ResetPasswordPolicy(),
|
||||
]);
|
||||
|
||||
this.setFullWidth();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
|
||||
private async logOut(expired: boolean) {
|
||||
await this.eventService.uploadEvents();
|
||||
const userId = await this.stateService.getUserId();
|
||||
await Promise.all([
|
||||
this.eventService.clearEvents(),
|
||||
this.syncService.setLastSync(new Date(0)),
|
||||
this.cryptoService.clearKeys(),
|
||||
this.settingsService.clear(userId),
|
||||
this.cipherService.clear(userId),
|
||||
this.folderService.clear(userId),
|
||||
this.collectionService.clear(userId),
|
||||
this.policyService.clear(userId),
|
||||
this.passwordGenerationService.clear(),
|
||||
this.keyConnectorService.clear(),
|
||||
]);
|
||||
|
||||
this.searchService.clearIndex();
|
||||
this.authService.logOut(async () => {
|
||||
if (expired) {
|
||||
this.platformUtilsService.showToast(
|
||||
"warning",
|
||||
this.i18nService.t("loggedOut"),
|
||||
this.i18nService.t("loginExpired")
|
||||
);
|
||||
}
|
||||
|
||||
await this.stateService.clean({ userId: userId });
|
||||
Swal.close();
|
||||
this.router.navigate(["/"]);
|
||||
});
|
||||
}
|
||||
|
||||
private async recordActivity() {
|
||||
const now = new Date().getTime();
|
||||
if (this.lastActivity != null && now - this.lastActivity < 250) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastActivity = now;
|
||||
this.stateService.setLastActive(now);
|
||||
// Idle states
|
||||
if (this.isIdle) {
|
||||
this.isIdle = false;
|
||||
this.idleStateChanged();
|
||||
}
|
||||
if (this.idleTimer != null) {
|
||||
window.clearTimeout(this.idleTimer);
|
||||
this.idleTimer = null;
|
||||
}
|
||||
this.idleTimer = window.setTimeout(() => {
|
||||
if (!this.isIdle) {
|
||||
this.isIdle = true;
|
||||
this.idleStateChanged();
|
||||
}
|
||||
}, IdleTimeout);
|
||||
}
|
||||
|
||||
private showToast(msg: any) {
|
||||
let message = "";
|
||||
|
||||
const options: Partial<IndividualConfig> = {};
|
||||
|
||||
if (typeof msg.text === "string") {
|
||||
message = msg.text;
|
||||
} else if (msg.text.length === 1) {
|
||||
message = msg.text[0];
|
||||
} else {
|
||||
msg.text.forEach(
|
||||
(t: string) =>
|
||||
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>")
|
||||
);
|
||||
options.enableHtml = true;
|
||||
}
|
||||
if (msg.options != null) {
|
||||
if (msg.options.trustedHtml === true) {
|
||||
options.enableHtml = true;
|
||||
}
|
||||
if (msg.options.timeout != null && msg.options.timeout > 0) {
|
||||
options.timeOut = msg.options.timeout;
|
||||
}
|
||||
}
|
||||
|
||||
this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
|
||||
}
|
||||
|
||||
private idleStateChanged() {
|
||||
if (this.isIdle) {
|
||||
this.notificationsService.disconnectFromInactivity();
|
||||
} else {
|
||||
this.notificationsService.reconnectFromActivity();
|
||||
}
|
||||
}
|
||||
|
||||
private async setFullWidth() {
|
||||
const enableFullWidth = await this.stateService.getEnableFullWidth();
|
||||
if (enableFullWidth) {
|
||||
document.body.classList.add("full-width");
|
||||
} else {
|
||||
document.body.classList.remove("full-width");
|
||||
}
|
||||
}
|
||||
}
|
||||
27
apps/web/src/app/app.module.ts
Normal file
27
apps/web/src/app/app.module.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { DragDropModule } from "@angular/cdk/drag-drop";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { InfiniteScrollModule } from "ngx-infinite-scroll";
|
||||
|
||||
import { AppComponent } from "./app.component";
|
||||
import { OssRoutingModule } from "./oss-routing.module";
|
||||
import { OssModule } from "./oss.module";
|
||||
import { ServicesModule } from "./services/services.module";
|
||||
import { WildcardRoutingModule } from "./wildcard-routing.module";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
OssModule,
|
||||
BrowserAnimationsModule,
|
||||
FormsModule,
|
||||
ServicesModule,
|
||||
InfiniteScrollModule,
|
||||
DragDropModule,
|
||||
OssRoutingModule,
|
||||
WildcardRoutingModule, // Needs to be last to catch all non-existing routes
|
||||
],
|
||||
declarations: [AppComponent],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
63
apps/web/src/app/common/base.accept.component.ts
Normal file
63
apps/web/src/app/common/base.accept.component.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
@Directive()
|
||||
export abstract class BaseAcceptComponent implements OnInit {
|
||||
loading = true;
|
||||
authed = false;
|
||||
email: string;
|
||||
actionPromise: Promise<any>;
|
||||
|
||||
protected requiredParameters: string[] = [];
|
||||
protected failedShortMessage = "inviteAcceptFailedShort";
|
||||
protected failedMessage = "inviteAcceptFailed";
|
||||
|
||||
constructor(
|
||||
protected router: Router,
|
||||
protected platformUtilService: PlatformUtilsService,
|
||||
protected i18nService: I18nService,
|
||||
protected route: ActivatedRoute,
|
||||
protected stateService: StateService
|
||||
) {}
|
||||
|
||||
abstract authedHandler(qParams: any): Promise<void>;
|
||||
abstract unauthedHandler(qParams: any): Promise<void>;
|
||||
|
||||
ngOnInit() {
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
let error = this.requiredParameters.some((e) => qParams?.[e] == null || qParams[e] === "");
|
||||
let errorMessage: string = null;
|
||||
if (!error) {
|
||||
this.authed = await this.stateService.getIsAuthenticated();
|
||||
|
||||
if (this.authed) {
|
||||
try {
|
||||
await this.authedHandler(qParams);
|
||||
} catch (e) {
|
||||
error = true;
|
||||
errorMessage = e.message;
|
||||
}
|
||||
} else {
|
||||
this.email = qParams.email;
|
||||
await this.unauthedHandler(qParams);
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
const message =
|
||||
errorMessage != null
|
||||
? this.i18nService.t(this.failedShortMessage, errorMessage)
|
||||
: this.i18nService.t(this.failedMessage);
|
||||
this.platformUtilService.showToast("error", null, message, { timeout: 10000 });
|
||||
this.router.navigate(["/"]);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
178
apps/web/src/app/common/base.events.component.ts
Normal file
178
apps/web/src/app/common/base.events.component.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import { Directive } from "@angular/core";
|
||||
|
||||
import { ExportService } from "jslib-common/abstractions/export.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { EventResponse } from "jslib-common/models/response/eventResponse";
|
||||
import { ListResponse } from "jslib-common/models/response/listResponse";
|
||||
import { EventView } from "jslib-common/models/view/eventView";
|
||||
|
||||
import { EventService } from "src/app/services/event.service";
|
||||
|
||||
@Directive()
|
||||
export abstract class BaseEventsComponent {
|
||||
loading = true;
|
||||
loaded = false;
|
||||
events: EventView[];
|
||||
start: string;
|
||||
end: string;
|
||||
dirtyDates = true;
|
||||
continuationToken: string;
|
||||
refreshPromise: Promise<any>;
|
||||
exportPromise: Promise<any>;
|
||||
morePromise: Promise<any>;
|
||||
|
||||
abstract readonly exportFileName: string;
|
||||
|
||||
constructor(
|
||||
protected eventService: EventService,
|
||||
protected i18nService: I18nService,
|
||||
protected exportService: ExportService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected logService: LogService
|
||||
) {
|
||||
const defaultDates = this.eventService.getDefaultDateFilters();
|
||||
this.start = defaultDates[0];
|
||||
this.end = defaultDates[1];
|
||||
}
|
||||
|
||||
async exportEvents() {
|
||||
if (this.appApiPromiseUnfulfilled() || this.dirtyDates) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
const dates = this.parseDates();
|
||||
if (dates == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.exportPromise = this.export(dates[0], dates[1]);
|
||||
|
||||
await this.exportPromise;
|
||||
} catch (e) {
|
||||
this.logService.error(`Handled exception: ${e}`);
|
||||
}
|
||||
|
||||
this.exportPromise = null;
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async loadEvents(clearExisting: boolean) {
|
||||
if (this.appApiPromiseUnfulfilled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dates = this.parseDates();
|
||||
if (dates == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
let events: EventView[] = [];
|
||||
try {
|
||||
const promise = this.loadAndParseEvents(
|
||||
dates[0],
|
||||
dates[1],
|
||||
clearExisting ? null : this.continuationToken
|
||||
);
|
||||
if (clearExisting) {
|
||||
this.refreshPromise = promise;
|
||||
} else {
|
||||
this.morePromise = promise;
|
||||
}
|
||||
const result = await promise;
|
||||
this.continuationToken = result.continuationToken;
|
||||
events = result.events;
|
||||
} catch (e) {
|
||||
this.logService.error(`Handled exception: ${e}`);
|
||||
}
|
||||
|
||||
if (!clearExisting && this.events != null && this.events.length > 0) {
|
||||
this.events = this.events.concat(events);
|
||||
} else {
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
this.dirtyDates = false;
|
||||
this.loading = false;
|
||||
this.morePromise = null;
|
||||
this.refreshPromise = null;
|
||||
}
|
||||
|
||||
protected abstract requestEvents(
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
continuationToken: string
|
||||
): Promise<ListResponse<EventResponse>>;
|
||||
protected abstract getUserName(r: EventResponse, userId: string): { name: string; email: string };
|
||||
|
||||
protected async loadAndParseEvents(
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
continuationToken: string
|
||||
) {
|
||||
const response = await this.requestEvents(startDate, endDate, continuationToken);
|
||||
|
||||
const events = await Promise.all(
|
||||
response.data.map(async (r) => {
|
||||
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
|
||||
const eventInfo = await this.eventService.getEventInfo(r);
|
||||
const user = this.getUserName(r, userId);
|
||||
const userName = user != null ? user.name : this.i18nService.t("unknown");
|
||||
|
||||
return new EventView({
|
||||
message: eventInfo.message,
|
||||
humanReadableMessage: eventInfo.humanReadableMessage,
|
||||
appIcon: eventInfo.appIcon,
|
||||
appName: eventInfo.appName,
|
||||
userId: userId,
|
||||
userName: r.installationId != null ? `Installation: ${r.installationId}` : userName,
|
||||
userEmail: user != null ? user.email : "",
|
||||
date: r.date,
|
||||
ip: r.ipAddress,
|
||||
type: r.type,
|
||||
installationId: r.installationId,
|
||||
});
|
||||
})
|
||||
);
|
||||
return { continuationToken: response.continuationToken, events: events };
|
||||
}
|
||||
|
||||
protected parseDates() {
|
||||
let dates: string[] = null;
|
||||
try {
|
||||
dates = this.eventService.formatDateFilters(this.start, this.end);
|
||||
} catch (e) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("invalidDateRange")
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
|
||||
protected appApiPromiseUnfulfilled() {
|
||||
return this.refreshPromise != null || this.morePromise != null || this.exportPromise != null;
|
||||
}
|
||||
|
||||
private async export(start: string, end: string) {
|
||||
let continuationToken = this.continuationToken;
|
||||
let events = [].concat(this.events);
|
||||
|
||||
while (continuationToken != null) {
|
||||
const result = await this.loadAndParseEvents(start, end, continuationToken);
|
||||
continuationToken = result.continuationToken;
|
||||
events = events.concat(result.events);
|
||||
}
|
||||
|
||||
const data = await this.exportService.getEventExport(events);
|
||||
const fileName = this.exportService.getFileName(this.exportFileName, "csv");
|
||||
this.platformUtilsService.saveFile(window, data, { type: "text/plain" }, fileName);
|
||||
}
|
||||
}
|
||||
345
apps/web/src/app/common/base.people.component.ts
Normal file
345
apps/web/src/app/common/base.people.component.ts
Normal file
@@ -0,0 +1,345 @@
|
||||
import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
|
||||
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
|
||||
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
import { ValidationService } from "jslib-angular/services/validation.service";
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
|
||||
import { OrganizationUserType } from "jslib-common/enums/organizationUserType";
|
||||
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
|
||||
import { ProviderUserType } from "jslib-common/enums/providerUserType";
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
import { ListResponse } from "jslib-common/models/response/listResponse";
|
||||
import { OrganizationUserUserDetailsResponse } from "jslib-common/models/response/organizationUserResponse";
|
||||
import { ProviderUserUserDetailsResponse } from "jslib-common/models/response/provider/providerUserResponse";
|
||||
|
||||
import { UserConfirmComponent } from "../organizations/manage/user-confirm.component";
|
||||
|
||||
type StatusType = OrganizationUserStatusType | ProviderUserStatusType;
|
||||
|
||||
const MaxCheckedCount = 500;
|
||||
|
||||
@Directive()
|
||||
export abstract class BasePeopleComponent<
|
||||
UserType extends ProviderUserUserDetailsResponse | OrganizationUserUserDetailsResponse
|
||||
> {
|
||||
@ViewChild("confirmTemplate", { read: ViewContainerRef, static: true })
|
||||
confirmModalRef: ViewContainerRef;
|
||||
|
||||
get allCount() {
|
||||
return this.allUsers != null ? this.allUsers.length : 0;
|
||||
}
|
||||
|
||||
get invitedCount() {
|
||||
return this.statusMap.has(this.userStatusType.Invited)
|
||||
? this.statusMap.get(this.userStatusType.Invited).length
|
||||
: 0;
|
||||
}
|
||||
|
||||
get acceptedCount() {
|
||||
return this.statusMap.has(this.userStatusType.Accepted)
|
||||
? this.statusMap.get(this.userStatusType.Accepted).length
|
||||
: 0;
|
||||
}
|
||||
|
||||
get confirmedCount() {
|
||||
return this.statusMap.has(this.userStatusType.Confirmed)
|
||||
? this.statusMap.get(this.userStatusType.Confirmed).length
|
||||
: 0;
|
||||
}
|
||||
|
||||
get showConfirmUsers(): boolean {
|
||||
return (
|
||||
this.allUsers != null &&
|
||||
this.statusMap != null &&
|
||||
this.allUsers.length > 1 &&
|
||||
this.confirmedCount > 0 &&
|
||||
this.confirmedCount < 3 &&
|
||||
this.acceptedCount > 0
|
||||
);
|
||||
}
|
||||
|
||||
get showBulkConfirmUsers(): boolean {
|
||||
return this.acceptedCount > 0;
|
||||
}
|
||||
|
||||
abstract userType: typeof OrganizationUserType | typeof ProviderUserType;
|
||||
abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType;
|
||||
|
||||
loading = true;
|
||||
statusMap = new Map<StatusType, UserType[]>();
|
||||
status: StatusType;
|
||||
users: UserType[] = [];
|
||||
pagedUsers: UserType[] = [];
|
||||
searchText: string;
|
||||
actionPromise: Promise<any>;
|
||||
|
||||
protected allUsers: UserType[] = [];
|
||||
|
||||
protected didScroll = false;
|
||||
protected pageSize = 100;
|
||||
|
||||
private pagedUsersCount = 0;
|
||||
|
||||
constructor(
|
||||
protected apiService: ApiService,
|
||||
private searchService: SearchService,
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected cryptoService: CryptoService,
|
||||
protected validationService: ValidationService,
|
||||
protected modalService: ModalService,
|
||||
private logService: LogService,
|
||||
private searchPipe: SearchPipe,
|
||||
protected userNamePipe: UserNamePipe,
|
||||
protected stateService: StateService
|
||||
) {}
|
||||
|
||||
abstract edit(user: UserType): void;
|
||||
abstract getUsers(): Promise<ListResponse<UserType>>;
|
||||
abstract deleteUser(id: string): Promise<any>;
|
||||
abstract reinviteUser(id: string): Promise<any>;
|
||||
abstract confirmUser(user: UserType, publicKey: Uint8Array): Promise<any>;
|
||||
|
||||
async load() {
|
||||
const response = await this.getUsers();
|
||||
this.statusMap.clear();
|
||||
for (const status of Utils.iterateEnum(this.userStatusType)) {
|
||||
this.statusMap.set(status, []);
|
||||
}
|
||||
|
||||
this.allUsers = response.data != null && response.data.length > 0 ? response.data : [];
|
||||
this.allUsers.sort(Utils.getSortFunction(this.i18nService, "email"));
|
||||
this.allUsers.forEach((u) => {
|
||||
if (!this.statusMap.has(u.status)) {
|
||||
this.statusMap.set(u.status, [u]);
|
||||
} else {
|
||||
this.statusMap.get(u.status).push(u);
|
||||
}
|
||||
});
|
||||
this.filter(this.status);
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
filter(status: StatusType) {
|
||||
this.status = status;
|
||||
if (this.status != null) {
|
||||
this.users = this.statusMap.get(this.status);
|
||||
} else {
|
||||
this.users = this.allUsers;
|
||||
}
|
||||
// Reset checkbox selecton
|
||||
this.selectAll(false);
|
||||
this.resetPaging();
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
if (!this.users || this.users.length <= this.pageSize) {
|
||||
return;
|
||||
}
|
||||
const pagedLength = this.pagedUsers.length;
|
||||
let pagedSize = this.pageSize;
|
||||
if (pagedLength === 0 && this.pagedUsersCount > this.pageSize) {
|
||||
pagedSize = this.pagedUsersCount;
|
||||
}
|
||||
if (this.users.length > pagedLength) {
|
||||
this.pagedUsers = this.pagedUsers.concat(
|
||||
this.users.slice(pagedLength, pagedLength + pagedSize)
|
||||
);
|
||||
}
|
||||
this.pagedUsersCount = this.pagedUsers.length;
|
||||
this.didScroll = this.pagedUsers.length > this.pageSize;
|
||||
}
|
||||
|
||||
checkUser(user: OrganizationUserUserDetailsResponse, select?: boolean) {
|
||||
(user as any).checked = select == null ? !(user as any).checked : select;
|
||||
}
|
||||
|
||||
selectAll(select: boolean) {
|
||||
if (select) {
|
||||
this.selectAll(false);
|
||||
}
|
||||
|
||||
const filteredUsers = this.searchPipe.transform(
|
||||
this.users,
|
||||
this.searchText,
|
||||
"name",
|
||||
"email",
|
||||
"id"
|
||||
);
|
||||
|
||||
const selectCount =
|
||||
select && filteredUsers.length > MaxCheckedCount ? MaxCheckedCount : filteredUsers.length;
|
||||
for (let i = 0; i < selectCount; i++) {
|
||||
this.checkUser(filteredUsers[i], select);
|
||||
}
|
||||
}
|
||||
|
||||
async resetPaging() {
|
||||
this.pagedUsers = [];
|
||||
this.loadMore();
|
||||
}
|
||||
|
||||
invite() {
|
||||
this.edit(null);
|
||||
}
|
||||
|
||||
async remove(user: UserType) {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.deleteWarningMessage(user),
|
||||
this.userNamePipe.transform(user),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.actionPromise = this.deleteUser(user.id);
|
||||
try {
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("removedUserId", this.userNamePipe.transform(user))
|
||||
);
|
||||
this.removeUser(user);
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
}
|
||||
this.actionPromise = null;
|
||||
}
|
||||
|
||||
async reinvite(user: UserType) {
|
||||
if (this.actionPromise != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.actionPromise = this.reinviteUser(user.id);
|
||||
try {
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("hasBeenReinvited", this.userNamePipe.transform(user))
|
||||
);
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
}
|
||||
this.actionPromise = null;
|
||||
}
|
||||
|
||||
async confirm(user: UserType) {
|
||||
function updateUser(self: BasePeopleComponent<UserType>) {
|
||||
user.status = self.userStatusType.Confirmed;
|
||||
const mapIndex = self.statusMap.get(self.userStatusType.Accepted).indexOf(user);
|
||||
if (mapIndex > -1) {
|
||||
self.statusMap.get(self.userStatusType.Accepted).splice(mapIndex, 1);
|
||||
self.statusMap.get(self.userStatusType.Confirmed).push(user);
|
||||
}
|
||||
}
|
||||
|
||||
const confirmUser = async (publicKey: Uint8Array) => {
|
||||
try {
|
||||
this.actionPromise = this.confirmUser(user, publicKey);
|
||||
await this.actionPromise;
|
||||
updateUser(this);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(user))
|
||||
);
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
throw e;
|
||||
} finally {
|
||||
this.actionPromise = null;
|
||||
}
|
||||
};
|
||||
|
||||
if (this.actionPromise != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
|
||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||
|
||||
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
|
||||
if (autoConfirm == null || !autoConfirm) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
UserConfirmComponent,
|
||||
this.confirmModalRef,
|
||||
(comp) => {
|
||||
comp.name = this.userNamePipe.transform(user);
|
||||
comp.userId = user != null ? user.userId : null;
|
||||
comp.publicKey = publicKey;
|
||||
comp.onConfirmedUser.subscribe(async () => {
|
||||
try {
|
||||
comp.formPromise = confirmUser(publicKey);
|
||||
await comp.formPromise;
|
||||
modal.close();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey.buffer);
|
||||
this.logService.info(`User's fingerprint: ${fingerprint.join("-")}`);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
await confirmUser(publicKey);
|
||||
} catch (e) {
|
||||
this.logService.error(`Handled exception: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
isSearching() {
|
||||
return this.searchService.isSearchable(this.searchText);
|
||||
}
|
||||
|
||||
isPaging() {
|
||||
const searching = this.isSearching();
|
||||
if (searching && this.didScroll) {
|
||||
this.resetPaging();
|
||||
}
|
||||
return !searching && this.users && this.users.length > this.pageSize;
|
||||
}
|
||||
|
||||
protected deleteWarningMessage(user: UserType): string {
|
||||
return this.i18nService.t("removeUserConfirmation");
|
||||
}
|
||||
|
||||
protected getCheckedUsers() {
|
||||
return this.users.filter((u) => (u as any).checked);
|
||||
}
|
||||
|
||||
protected removeUser(user: UserType) {
|
||||
let index = this.users.indexOf(user);
|
||||
if (index > -1) {
|
||||
this.users.splice(index, 1);
|
||||
this.resetPaging();
|
||||
}
|
||||
if (this.statusMap.has(user.status)) {
|
||||
index = this.statusMap.get(user.status).indexOf(user);
|
||||
if (index > -1) {
|
||||
this.statusMap.get(user.status).splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
apps/web/src/app/components/nested-checkbox.component.html
Normal file
30
apps/web/src/app/components/nested-checkbox.component.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[name]="pascalize(parentId)"
|
||||
[id]="parentId"
|
||||
[(ngModel)]="parentChecked"
|
||||
[indeterminate]="parentIndeterminate"
|
||||
/>
|
||||
<label class="form-check-label font-weight-normal" [for]="parentId">
|
||||
{{ parentId | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group form-group-child-check mb-0">
|
||||
<div class="form-check mt-1" *ngFor="let c of checkboxes">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[name]="pascalize(c.id)"
|
||||
[id]="c.id"
|
||||
[ngModel]="c.get()"
|
||||
(ngModelChange)="c.set($event)"
|
||||
/>
|
||||
<label class="form-check-label font-weight-normal" [for]="c.id">
|
||||
{{ c.id | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
32
apps/web/src/app/components/nested-checkbox.component.ts
Normal file
32
apps/web/src/app/components/nested-checkbox.component.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
|
||||
@Component({
|
||||
selector: "app-nested-checkbox",
|
||||
templateUrl: "nested-checkbox.component.html",
|
||||
})
|
||||
export class NestedCheckboxComponent {
|
||||
@Input() parentId: string;
|
||||
@Input() checkboxes: { id: string; get: () => boolean; set: (v: boolean) => void }[];
|
||||
@Output() onSavedUser = new EventEmitter();
|
||||
@Output() onDeletedUser = new EventEmitter();
|
||||
|
||||
get parentIndeterminate() {
|
||||
return !this.parentChecked && this.checkboxes.some((c) => c.get());
|
||||
}
|
||||
|
||||
get parentChecked() {
|
||||
return this.checkboxes.every((c) => c.get());
|
||||
}
|
||||
|
||||
set parentChecked(value: boolean) {
|
||||
this.checkboxes.forEach((c) => {
|
||||
c.set(value);
|
||||
});
|
||||
}
|
||||
|
||||
pascalize(s: string) {
|
||||
return Utils.camelToPascalCase(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<div *ngIf="loaded && activeOrganization != null" class="tw-flex">
|
||||
<button
|
||||
class="tw-flex tw-items-center tw-bg-background-alt tw-border-none"
|
||||
type="button"
|
||||
id="pickerButton"
|
||||
[appA11yTitle]="'organizationPicker' | i18n"
|
||||
[bitMenuTriggerFor]="orgPickerMenu"
|
||||
>
|
||||
<app-avatar
|
||||
[data]="activeOrganization.name"
|
||||
size="45"
|
||||
[circle]="true"
|
||||
[dynamic]="true"
|
||||
></app-avatar>
|
||||
<div class="tw-flex">
|
||||
<div class="org-name tw-ml-3">
|
||||
<span>{{ activeOrganization.name }}</span>
|
||||
<small class="tw-text-muted">{{ "organization" | i18n }}</small>
|
||||
</div>
|
||||
<div class="tw-ml-3">
|
||||
<i class="bwi bwi-angle-down tw-text-main" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div>
|
||||
<div
|
||||
class="tw-ml-3 tw-border tw-border-solid tw-rounded tw-border-danger-500 tw-text-danger"
|
||||
*ngIf="!activeOrganization.enabled"
|
||||
>
|
||||
<div class="tw-py-2 tw-px-5">
|
||||
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{ "organizationIsDisabled" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tw-ml-3 tw-border tw-border-solid tw-rounded tw-border-info-500 tw-text-info"
|
||||
*ngIf="activeOrganization.isProviderUser"
|
||||
>
|
||||
<div class="tw-py-2 tw-px-5">
|
||||
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{ "accessingUsingProvider" | i18n: activeOrganization.providerName }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<bit-menu #orgPickerMenu>
|
||||
<ul aria-labelledby="pickerButton" class="tw-p-0 tw-m-0">
|
||||
<li *ngFor="let org of organizations" class="tw-list-none tw-flex tw-flex-col" role="none">
|
||||
<a bitMenuItem [routerLink]="['/organizations', org.id]">
|
||||
<i
|
||||
class="bwi bwi-check mr-2"
|
||||
[ngClass]="org.id === activeOrganization.id ? 'visible' : 'invisible'"
|
||||
>
|
||||
<span class="tw-sr-only">{{ "currentOrganization" | i18n }}</span>
|
||||
</i>
|
||||
{{ org.name }}
|
||||
</a>
|
||||
</li>
|
||||
<bit-menu-divider></bit-menu-divider>
|
||||
<li class="tw-list-none" role="none">
|
||||
<a bitMenuItem routerLink="/create-organization">
|
||||
<i class="bwi bwi-plus mr-2"></i>
|
||||
{{ "newOrganization" | i18n }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</bit-menu>
|
||||
</div>
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
|
||||
import { NavigationPermissionsService } from "../organizations/services/navigation-permissions.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-organization-switcher",
|
||||
templateUrl: "organization-switcher.component.html",
|
||||
})
|
||||
export class OrganizationSwitcherComponent implements OnInit {
|
||||
constructor(private organizationService: OrganizationService, private i18nService: I18nService) {}
|
||||
|
||||
@Input() activeOrganization: Organization = null;
|
||||
organizations: Organization[] = [];
|
||||
|
||||
loaded = false;
|
||||
|
||||
async ngOnInit() {
|
||||
await this.load();
|
||||
}
|
||||
|
||||
async load() {
|
||||
const orgs = await this.organizationService.getAll();
|
||||
this.organizations = orgs
|
||||
.filter((org) => NavigationPermissionsService.canAccessAdmin(org))
|
||||
.sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||
|
||||
this.loaded = true;
|
||||
}
|
||||
}
|
||||
53
apps/web/src/app/components/password-reprompt.component.html
Normal file
53
apps/web/src/app/components/password-reprompt.component.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="confirmUserTitle">
|
||||
{{ "passwordConfirmation" | i18n }}
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ "passwordConfirmationDesc" | i18n }}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
<div class="d-flex">
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="MasterPassword"
|
||||
class="text-monospace form-control"
|
||||
[(ngModel)]="masterPassword"
|
||||
required
|
||||
appAutofocus
|
||||
appInputVerbatim
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 btn btn-link"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword()"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
|
||||
<span>{{ "ok" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { PasswordRepromptComponent as BasePasswordRepromptComponent } from "jslib-angular/components/password-reprompt.component";
|
||||
|
||||
@Component({
|
||||
templateUrl: "password-reprompt.component.html",
|
||||
})
|
||||
export class PasswordRepromptComponent extends BasePasswordRepromptComponent {}
|
||||
14
apps/web/src/app/components/password-strength.component.html
Normal file
14
apps/web/src/app/components/password-strength.component.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<div class="progress">
|
||||
<div
|
||||
class="progress-bar {{ color }}"
|
||||
role="progressbar"
|
||||
[ngStyle]="{ width: scoreWidth + '%' }"
|
||||
attr.aria-valuenow="{{ scoreWidth }}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
<ng-container *ngIf="showText && text">
|
||||
{{ text }}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
40
apps/web/src/app/components/password-strength.component.ts
Normal file
40
apps/web/src/app/components/password-strength.component.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Component, Input, OnChanges } from "@angular/core";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-password-strength",
|
||||
templateUrl: "password-strength.component.html",
|
||||
})
|
||||
export class PasswordStrengthComponent implements OnChanges {
|
||||
@Input() score?: number;
|
||||
@Input() showText = false;
|
||||
|
||||
scoreWidth = 0;
|
||||
color = "bg-danger";
|
||||
text: string;
|
||||
|
||||
constructor(private i18nService: I18nService) {}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.scoreWidth = this.score == null ? 0 : (this.score + 1) * 20;
|
||||
switch (this.score) {
|
||||
case 4:
|
||||
this.color = "bg-success";
|
||||
this.text = this.i18nService.t("strong");
|
||||
break;
|
||||
case 3:
|
||||
this.color = "bg-primary";
|
||||
this.text = this.i18nService.t("good");
|
||||
break;
|
||||
case 2:
|
||||
this.color = "bg-warning";
|
||||
this.text = this.i18nService.t("weak");
|
||||
break;
|
||||
default:
|
||||
this.color = "bg-danger";
|
||||
this.text = this.score != null ? this.i18nService.t("weak") : null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
apps/web/src/app/components/premium-badge.component.ts
Normal file
19
apps/web/src/app/components/premium-badge.component.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-premium-badge",
|
||||
template: `
|
||||
<button *appNotPremium bitBadge badgeType="success" (click)="premiumRequired()">
|
||||
{{ "premium" | i18n }}
|
||||
</button>
|
||||
`,
|
||||
})
|
||||
export class PremiumBadgeComponent {
|
||||
constructor(private messagingService: MessagingService) {}
|
||||
|
||||
premiumRequired() {
|
||||
this.messagingService.send("premiumRequired");
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user