mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Compare commits
646 Commits
v2.14.0
...
beeep/deco
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f244f4ffde | ||
|
|
9d393224da | ||
|
|
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 | ||
|
|
5bc01ea13e | ||
|
|
fba2102518 |
@@ -1,3 +1,4 @@
|
||||
*
|
||||
!docker/*
|
||||
!build/*
|
||||
!entrypoint.sh
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
@@ -12,4 +12,7 @@ insert_final_newline = true
|
||||
[*.{js,ts,scss,html}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
indent_size = 2
|
||||
|
||||
[*.{ts}]
|
||||
quote_type = single
|
||||
|
||||
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
||||
# Apply Prettier https://github.com/bitwarden/web/pull/1347
|
||||
56477eb39cfd8a73c9920577d24d75fed36e2cf5
|
||||
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1,3 +1 @@
|
||||
*.sh eol=lf
|
||||
.dockerignore eol=lf
|
||||
dockerfile eol=lf
|
||||
* text=auto eol=lf
|
||||
|
||||
93
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
93
.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
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
14
.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.
|
||||
32
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
32
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
## 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-->
|
||||
|
||||
## Testing requirements
|
||||
|
||||
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
|
||||
|
||||
## 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)
|
||||
493
.github/workflows/build.yml
vendored
Normal file
493
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,493 @@
|
||||
---
|
||||
name: Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
custom_tag_extension:
|
||||
description: "Custom image tag extension"
|
||||
required: false
|
||||
push:
|
||||
branches-ignore:
|
||||
- "l10n_master"
|
||||
- "gh-pages"
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
env:
|
||||
_VERSION: ${{ needs.setup.outputs.version }}
|
||||
steps:
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||
with:
|
||||
node-version: "16"
|
||||
|
||||
- name: Cache npm
|
||||
id: npm-cache
|
||||
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
|
||||
with:
|
||||
path: "~/.npm"
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
whoami
|
||||
node --version
|
||||
npm --version
|
||||
gulp --version
|
||||
docker --version
|
||||
echo "GitHub ref: $GITHUB_REF"
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- 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
|
||||
env:
|
||||
_VERSION: ${{ needs.setup.outputs.version }}
|
||||
steps:
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||
with:
|
||||
node-version: "16"
|
||||
|
||||
- name: Cache npm
|
||||
id: npm-cache
|
||||
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
|
||||
with:
|
||||
path: "~/.npm"
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
whoami
|
||||
node --version
|
||||
npm --version
|
||||
gulp --version
|
||||
docker --version
|
||||
echo "GitHub ref: $GITHUB_REF"
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- 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
|
||||
env:
|
||||
_VERSION: ${{ needs.setup.outputs.version }}
|
||||
steps:
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||
with:
|
||||
node-version: "16"
|
||||
|
||||
- name: Cache npm
|
||||
id: npm-cache
|
||||
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
|
||||
with:
|
||||
path: "~/.npm"
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- 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'
|
||||
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: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- name: Restore
|
||||
run: dotnet tool restore
|
||||
|
||||
- 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'
|
||||
run: docker tag bitwarden/web bitwarden/web:hotfix
|
||||
|
||||
- name: List Docker images
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix'
|
||||
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'
|
||||
run: docker push bitwarden/web:hotfix
|
||||
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'
|
||||
run: docker logout
|
||||
|
||||
build-qa:
|
||||
name: Build Docker images for QA environment
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||
with:
|
||||
node-version: "16"
|
||||
|
||||
- name: Cache npm
|
||||
id: npm-cache
|
||||
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
|
||||
with:
|
||||
path: "~/.npm"
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- 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: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- name: Restore
|
||||
run: dotnet tool restore
|
||||
|
||||
- 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: Set up NuGet
|
||||
uses: nuget/setup-nuget@04b0c2b8d1b97922f67eca497d7cf0bf17b8ffe1
|
||||
with:
|
||||
nuget-version: "latest"
|
||||
|
||||
- name: Set up MSBuild
|
||||
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d
|
||||
|
||||
- name: Cache npm
|
||||
id: npm-cache
|
||||
uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6
|
||||
with:
|
||||
path: "~/.npm"
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
||||
with:
|
||||
node-version: "16"
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
nuget help | grep Version
|
||||
msbuild -version
|
||||
dotnet --info
|
||||
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: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run linter
|
||||
run: npm run lint
|
||||
|
||||
- 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
|
||||
- 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 }}
|
||||
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 [ "$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
.github/workflows/crowdin-pull.yml
vendored
Normal file
49
.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"
|
||||
71
.github/workflows/qa-deploy.yml
vendored
Normal file
71
.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
|
||||
201
.github/workflows/release.yml
vendored
Normal file
201
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
---
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_type:
|
||||
description: 'Release Options'
|
||||
required: true
|
||||
default: 'Initial Release'
|
||||
type: choice
|
||||
options:
|
||||
- Initial Release
|
||||
- Redeploy
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
name: Setup
|
||||
runs-on: ubuntu-20.04
|
||||
outputs:
|
||||
release_version: ${{ steps.version.outputs.package }}
|
||||
tag_version: ${{ steps.version.outputs.tag }}
|
||||
branch-name: ${{ steps.branch.outputs.branch-name }}
|
||||
steps:
|
||||
- name: Branch check
|
||||
run: |
|
||||
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix" ]]; then
|
||||
echo "==================================="
|
||||
echo "[!] Can only release from the 'rc' or 'hotfix' branches"
|
||||
echo "==================================="
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # 2.3.4
|
||||
|
||||
- name: Check Release Version
|
||||
id: version
|
||||
run: |
|
||||
version=$( jq -r ".version" package.json)
|
||||
previous_release_tag_version=$(
|
||||
curl -sL https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest | jq -r ".tag_name"
|
||||
)
|
||||
|
||||
if [ "v$version" == "$previous_release_tag_version" ] && \
|
||||
[ "${{ github.event.inputs.release_type }}" == "Initial Release" ]; then
|
||||
echo "[!] Already released v$version. Please bump version to continue"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "::set-output name=package::$version"
|
||||
echo "::set-output name=tag::v$version"
|
||||
|
||||
- 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 }}
|
||||
steps:
|
||||
- name: Print environment
|
||||
run: |
|
||||
whoami
|
||||
docker --version
|
||||
echo "GitHub ref: $GITHUB_REF"
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
|
||||
- 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: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- name: Pull latest selfhost image
|
||||
run: docker pull bitwarden/web:$_BRANCH_NAME
|
||||
|
||||
- name: Tag version and latest
|
||||
run: |
|
||||
docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:$_RELEASE_VERSION
|
||||
docker tag bitwarden/web:$_BRANCH_NAME bitwarden/web:latest
|
||||
|
||||
- name: List Docker images
|
||||
run: docker images
|
||||
|
||||
- name: Push version and latest image
|
||||
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
|
||||
run: docker logout
|
||||
|
||||
ghpages-deploy:
|
||||
name: Deploy Web Vault
|
||||
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@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||
with:
|
||||
ref: gh-pages
|
||||
|
||||
- name: Create deploy branch
|
||||
run: |
|
||||
git switch -c deploy-$_TAG_VERSION
|
||||
git push -u origin deploy-$_TAG_VERSION
|
||||
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||
|
||||
- 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@23433be15ed6fd046ce12b6889c5184a8d9c8783
|
||||
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@db4476a01402e1a7ce05f41832040eef16d14925 # v2.5.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
target_branch: deploy-${{ needs.setup.outputs.tag_version }}
|
||||
build_dir: build
|
||||
keep_history: true
|
||||
commit_message: "Staging deploy ${{ needs.setup.outputs.release_version }}"
|
||||
|
||||
- name: Create Deploy PR
|
||||
env:
|
||||
PR_BRANCH: deploy-${{ env._TAG_VERSION }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh pr create --title "Deploy $_RELEASE_VERSION" \
|
||||
--body "Deploying $_RELEASE_VERSION" \
|
||||
--base gh-pages \
|
||||
--head "$PR_BRANCH"
|
||||
|
||||
release:
|
||||
name: Create GitHub Release
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- setup
|
||||
- self-host
|
||||
- ghpages-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
|
||||
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09
|
||||
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
|
||||
71
.github/workflows/version-bump.yml
vendored
Normal file
71
.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 }}"
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,5 +9,7 @@ dist/
|
||||
*.pem
|
||||
*.crx
|
||||
*.zip
|
||||
*.swp
|
||||
build/
|
||||
!dev-server.shared.pem
|
||||
config/local.json
|
||||
|
||||
4
.husky/pre-commit
Normal file
4
.husky/pre-commit
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
12
.prettierignore
Normal file
12
.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
.prettierrc.json
Normal file
3
.prettierrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"printWidth": 100
|
||||
}
|
||||
@@ -1,4 +1,27 @@
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
||||
# 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 [User-to-User Support 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)
|
||||
|
||||
|
||||
28
Dockerfile
28
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM bitwarden/server
|
||||
FROM nginx:stable
|
||||
|
||||
LABEL com.bitwarden.product="bitwarden"
|
||||
|
||||
@@ -8,13 +8,29 @@ RUN apt-get update \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV ASPNETCORE_URLS http://+:5000
|
||||
COPY docker/nginx.conf /etc/nginx
|
||||
COPY docker/nginx-web.conf /etc/nginx
|
||||
COPY docker/mime.types /etc/nginx
|
||||
COPY docker/security-headers.conf /etc/nginx
|
||||
|
||||
WORKDIR /app
|
||||
EXPOSE 5000
|
||||
COPY ./build .
|
||||
COPY entrypoint.sh /
|
||||
COPY docker/entrypoint.sh /
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
HEALTHCHECK CMD curl -f http://localhost:5000 || exit 1
|
||||
RUN bash /entrypoint.sh
|
||||
RUN chown -R bitwarden:bitwarden /app && chmod -R 755 /app && \
|
||||
chown -R bitwarden:bitwarden /var/cache/nginx && \
|
||||
chown -R bitwarden:bitwarden /var/log/nginx && \
|
||||
chown -R bitwarden:bitwarden /etc/nginx/conf.d
|
||||
RUN touch /var/run/nginx.pid && \
|
||||
chown -R bitwarden:bitwarden /var/run/nginx.pid
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
USER bitwarden
|
||||
|
||||
EXPOSE 8080
|
||||
HEALTHCHECK CMD curl -f http://localhost:8080 || exit 1
|
||||
|
||||
#ENTRYPOINT ["/entrypoint.sh"]
|
||||
#CMD ["tail", "-f", "/dev/null"]
|
||||
CMD nginx -g 'daemon off;'
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<!--
|
||||
Please do not submit feature requests. The [Community Forums][1] has a
|
||||
section for submitting, voting for, and discussing product feature requests.
|
||||
[1]: https://community.bitwarden.com
|
||||
-->
|
||||
691
LICENSE.txt
691
LICENSE.txt
@@ -1,674 +1,17 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
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
LICENSE_BITWARDEN.txt
Normal file
182
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."
|
||||
674
LICENSE_GPL.txt
Normal file
674
LICENSE_GPL.txt
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
66
README.md
66
README.md
@@ -5,8 +5,8 @@
|
||||
The Bitwarden web project is an Angular application that powers the web vault (https://vault.bitwarden.com/).
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://ci.appveyor.com/project/bitwarden/web/branch/master" target="_blank">
|
||||
<img src="https://ci.appveyor.com/api/projects/status/github/bitwarden/web?branch=master&svg=true" alt="appveyor build" />
|
||||
<a 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" />
|
||||
@@ -23,38 +23,66 @@
|
||||
|
||||
### Requirements
|
||||
|
||||
- [Node.js](https://nodejs.org) v8.11 or greater
|
||||
- [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:watch
|
||||
npm run build:oss:watch
|
||||
```
|
||||
|
||||
You can now access the web vault in your browser at `https://localhost:8080`. You can adjust your API endpoint settings in `src/app/services/services.module.ts` by altering the `apiService.setUrls` call. For example:
|
||||
You can now access the web vault in your browser at `https://localhost:8080`.
|
||||
|
||||
```typescript
|
||||
await apiService.setUrls({
|
||||
base: isDev ? null : window.location.origin,
|
||||
api: isDev ? 'http://mylocalapi' : null,
|
||||
identity: isDev ? 'http://mylocalidentity' : null,
|
||||
});
|
||||
If you want to point the development web vault to the production APIs, you can run using:
|
||||
|
||||
```
|
||||
npm install
|
||||
ENV=cloud npm run build:oss:watch
|
||||
```
|
||||
|
||||
If you want to point the development web vault to the production APIs, you can set:
|
||||
You can also manually adjusting your API endpoint settings by adding `config/local.json` overriding any of the following values:
|
||||
|
||||
```typescript
|
||||
await apiService.setUrls({
|
||||
base: null,
|
||||
api: 'https://api.bitwarden.com',
|
||||
identity: 'https://identity.bitwarden.com',
|
||||
});
|
||||
```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).
|
||||
|
||||
## Contribute
|
||||
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch. 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
|
||||
```
|
||||
|
||||
@@ -7,7 +7,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
|
||||
- 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.
|
||||
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.
|
||||
|
||||
83
appveyor.yml
83
appveyor.yml
@@ -1,83 +0,0 @@
|
||||
image:
|
||||
- Visual Studio 2017
|
||||
- Ubuntu1804
|
||||
|
||||
branches:
|
||||
except:
|
||||
- l10n_master
|
||||
- gh-pages
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
stack: node 10
|
||||
|
||||
init:
|
||||
- ps: |
|
||||
if($isWindows) {
|
||||
Install-Product node 10
|
||||
}
|
||||
|
||||
install:
|
||||
- ps: |
|
||||
$env:PACKAGE_VERSION = (Get-Content -Raw -Path .\package.json | ConvertFrom-Json).version
|
||||
$env:PUSH_DOCKER = "false"
|
||||
$env:PROD_DEPLOY = "false"
|
||||
$env:TAG_NAME = ""
|
||||
if($env:APPVEYOR_REPO_TAG -eq "true" -and $env:APPVEYOR_RE_BUILD -eq "True") {
|
||||
$env:PROD_DEPLOY = "true"
|
||||
$env:TAG_NAME = $env:APPVEYOR_REPO_TAG_NAME.TrimStart("v")
|
||||
echo "This is a production deployment for ${env:TAG_NAME}."
|
||||
}
|
||||
if("${env:DOCKER_USERNAME}" -ne "" -and "${env:DOCKER_PASSWORD}" -ne "") {
|
||||
$env:PUSH_DOCKER = "true"
|
||||
}
|
||||
if($isWindows) {
|
||||
choco install cloc --no-progress
|
||||
cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
|
||||
}
|
||||
|
||||
before_build:
|
||||
- node --version
|
||||
- npm --version
|
||||
- sh: |
|
||||
if [ "${PUSH_DOCKER}" == "true" ]
|
||||
then
|
||||
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
fi
|
||||
- cmd: set "GIT_PATH=C:\Program Files\Git\mingw64\libexec\git-core"
|
||||
- cmd: set "PATH=%GIT_PATH%;%PATH%"
|
||||
|
||||
build_script:
|
||||
- sh: chmod +x ./build.sh
|
||||
- ps: |
|
||||
if($isLinux) {
|
||||
./build.sh
|
||||
./build.sh tag dev
|
||||
|
||||
if($env:PROD_DEPLOY -eq "true") {
|
||||
./build.sh tag beta
|
||||
./build.sh tag $env:TAG_NAME
|
||||
}
|
||||
|
||||
docker images
|
||||
|
||||
if($env:PUSH_DOCKER -eq "true") {
|
||||
./build.sh push dev
|
||||
|
||||
if($env:PROD_DEPLOY -eq "true") {
|
||||
./build.sh push beta
|
||||
./build.sh push latest
|
||||
./build.sh push $env:TAG_NAME
|
||||
}
|
||||
}
|
||||
}
|
||||
- cmd: npm install
|
||||
- cmd: npm run build:prod
|
||||
|
||||
after_build:
|
||||
- sh: |
|
||||
if [ "${PUSH_DOCKER}" == "true" ]
|
||||
then
|
||||
docker logout
|
||||
fi
|
||||
3
bitwarden_license/README.md
Normal file
3
bitwarden_license/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Bitwarden Licensed Code
|
||||
|
||||
All source code under this directory is licensed under the [Bitwarden License Agreement](https://github.com/bitwarden/web/blob/master/LICENSE_BITWARDEN.txt).
|
||||
15
bitwarden_license/src/app/app-routing.module.ts
Normal file
15
bitwarden_license/src/app/app-routing.module.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "providers",
|
||||
loadChildren: async () => (await import("./providers/providers.module")).ProvidersModule,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
20
bitwarden_license/src/app/app.component.ts
Normal file
20
bitwarden_license/src/app/app.component.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { AppComponent as BaseAppComponent } from "src/app/app.component";
|
||||
import { DisablePersonalVaultExportPolicy } from "./policies/disable-personal-vault-export.component";
|
||||
import { MaximumVaultTimeoutPolicy } from "./policies/maximum-vault-timeout.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-root",
|
||||
templateUrl: "../../../src/app/app.component.html",
|
||||
})
|
||||
export class AppComponent extends BaseAppComponent {
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.policyListService.addPolicies([
|
||||
new MaximumVaultTimeoutPolicy(),
|
||||
new DisablePersonalVaultExportPolicy(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
48
bitwarden_license/src/app/app.module.ts
Normal file
48
bitwarden_license/src/app/app.module.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { DragDropModule } from "@angular/cdk/drag-drop";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { InfiniteScrollModule } from "ngx-infinite-scroll";
|
||||
|
||||
import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
|
||||
|
||||
import { AppRoutingModule } from "./app-routing.module";
|
||||
import { AppComponent } from "./app.component";
|
||||
import { OrganizationsModule } from "./organizations/organizations.module";
|
||||
import { DisablePersonalVaultExportPolicyComponent } from "./policies/disable-personal-vault-export.component";
|
||||
import { MaximumVaultTimeoutPolicyComponent } from "./policies/maximum-vault-timeout.component";
|
||||
|
||||
import { OssRoutingModule } from "src/app/oss-routing.module";
|
||||
import { OssModule } from "src/app/oss.module";
|
||||
import { ServicesModule } from "src/app/services/services.module";
|
||||
import { WildcardRoutingModule } from "src/app/wildcard-routing.module";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
OssModule,
|
||||
BrowserAnimationsModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
ServicesModule,
|
||||
BitwardenToastModule.forRoot({
|
||||
maxOpened: 5,
|
||||
autoDismiss: true,
|
||||
closeButton: true,
|
||||
}),
|
||||
InfiniteScrollModule,
|
||||
DragDropModule,
|
||||
AppRoutingModule,
|
||||
OssRoutingModule,
|
||||
OrganizationsModule,
|
||||
RouterModule,
|
||||
WildcardRoutingModule, // Needs to be last to catch all non-existing routes
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
MaximumVaultTimeoutPolicyComponent,
|
||||
DisablePersonalVaultExportPolicyComponent,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
17
bitwarden_license/src/app/main.ts
Normal file
17
bitwarden_license/src/app/main.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { enableProdMode } from "@angular/core";
|
||||
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
|
||||
|
||||
import "bootstrap";
|
||||
import "jquery";
|
||||
import "popper.js";
|
||||
|
||||
// tslint:disable-next-line
|
||||
require("src/scss/styles.scss");
|
||||
|
||||
import { AppModule } from "./app.module";
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });
|
||||
@@ -0,0 +1,492 @@
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{ "singleSignOn" | i18n }}</h1>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[formGroup]="data"
|
||||
[appApiAction]="formPromise"
|
||||
*ngIf="!loading"
|
||||
ngNativeValidate
|
||||
>
|
||||
<p>
|
||||
{{ "ssoPolicyHelpStart" | i18n }}
|
||||
<a routerLink="../policies">{{ "ssoPolicyHelpLink" | i18n }}</a>
|
||||
{{ "ssoPolicyHelpEnd" | i18n }}
|
||||
<br />
|
||||
{{ "ssoPolicyHelpKeyConnector" | i18n }}
|
||||
</p>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enabled"
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{ "allowSso" | i18n }}</label>
|
||||
</div>
|
||||
<small class="form-text text-muted">{{ "allowSsoDesc" | i18n }}</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ "memberDecryptionOption" | i18n }}</label>
|
||||
<div class="form-check form-check-block">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
id="memberDecryptionPass"
|
||||
[value]="false"
|
||||
formControlName="keyConnectorEnabled"
|
||||
/>
|
||||
<label class="form-check-label" for="memberDecryptionPass">
|
||||
{{ "masterPass" | i18n }}
|
||||
<small>{{ "memberDecryptionPassDesc" | i18n }}</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mt-2 form-check-block">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
id="memberDecryptionKey"
|
||||
[value]="true"
|
||||
formControlName="keyConnectorEnabled"
|
||||
[attr.disabled]="!organization.useKeyConnector || null"
|
||||
/>
|
||||
<label class="form-check-label" for="memberDecryptionKey">
|
||||
{{ "keyConnector" | i18n }}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
href="https://bitwarden.com/help/article/about-key-connector/"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
<small>{{ "memberDecryptionKeyConnectorDesc" | i18n }}</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="data.value.keyConnectorEnabled">
|
||||
<app-callout type="warning" [useAlertRole]="true">
|
||||
{{ "keyConnectorWarning" | i18n }}
|
||||
</app-callout>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="keyConnectorUrl">{{ "keyConnectorUrl" | i18n }}</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
class="form-control"
|
||||
formControlName="keyConnectorUrl"
|
||||
id="keyConnectorUrl"
|
||||
required
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
(click)="validateKeyConnectorUrl()"
|
||||
[disabled]="!enableTestKeyConnector"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
*ngIf="keyConnectorUrl.pending"
|
||||
></i>
|
||||
<span *ngIf="!keyConnectorUrl.pending">
|
||||
{{ "keyConnectorTest" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="keyConnectorUrl.pristine && !keyConnectorUrl.pending">
|
||||
<div class="text-danger" *ngIf="keyConnectorUrl.hasError('invalidUrl')" role="alert">
|
||||
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
|
||||
{{ "keyConnectorTestFail" | i18n }}
|
||||
</div>
|
||||
<div class="text-success" *ngIf="!keyConnectorUrl.hasError('invalidUrl')" role="alert">
|
||||
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
|
||||
{{ "keyConnectorTestSuccess" | i18n }}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="type">{{ "type" | i18n }}</label>
|
||||
<select class="form-control" id="type" formControlName="configType">
|
||||
<option [ngValue]="0" disabled>{{ "selectType" | i18n }}</option>
|
||||
<option [ngValue]="1">OpenID Connect</option>
|
||||
<option [ngValue]="2">SAML 2.0</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- OIDC -->
|
||||
<div *ngIf="data.value.configType == 1">
|
||||
<div class="config-section">
|
||||
<h2>{{ "openIdConnectConfig" | i18n }}</h2>
|
||||
<div class="form-group">
|
||||
<label>{{ "callbackPath" | i18n }}</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" readonly [value]="callbackPath" />
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||
(click)="copy(callbackPath)"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{{ "signedOutCallbackPath" | i18n }}</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" readonly [value]="signedOutCallbackPath" />
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||
(click)="copy(signedOutCallbackPath)"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="authority">{{ "authority" | i18n }}</label>
|
||||
<input class="form-control" formControlName="authority" id="authority" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="clientId">{{ "clientId" | i18n }}</label>
|
||||
<input class="form-control" formControlName="clientId" id="clientId" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="clientSecret">{{ "clientSecret" | i18n }}</label>
|
||||
<input class="form-control" formControlName="clientSecret" id="clientSecret" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="metadataAddress">{{ "metadataAddress" | i18n }}</label>
|
||||
<input class="form-control" formControlName="metadataAddress" id="metadataAddress" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="redirectBehavior">{{ "oidcRedirectBehavior" | i18n }}</label>
|
||||
<select class="form-control" formControlName="redirectBehavior" id="redirectBehavior">
|
||||
<option [ngValue]="0">Redirect GET</option>
|
||||
<option [ngValue]="1">Form POST</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="getClaimsFromUserInfoEndpoint"
|
||||
formControlName="getClaimsFromUserInfoEndpoint"
|
||||
/>
|
||||
<label class="form-check-label" for="getClaimsFromUserInfoEndpoint">
|
||||
{{ "getClaimsFromUserInfoEndpoint" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="additionalScopes">{{ "additionalScopes" | i18n }}</label>
|
||||
<input class="form-control" formControlName="additionalScopes" id="additionalScopes" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="additionalUserIdClaimTypes">{{ "additionalUserIdClaimTypes" | i18n }}</label>
|
||||
<input
|
||||
class="form-control"
|
||||
formControlName="additionalUserIdClaimTypes"
|
||||
id="additionalUserIdClaimTypes"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="additionalEmailClaimTypes">{{ "additionalEmailClaimTypes" | i18n }}</label>
|
||||
<input
|
||||
class="form-control"
|
||||
formControlName="additionalEmailClaimTypes"
|
||||
id="additionalEmailClaimTypes"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="additionalNameClaimTypes">{{ "additionalNameClaimTypes" | i18n }}</label>
|
||||
<input
|
||||
class="form-control"
|
||||
formControlName="additionalNameClaimTypes"
|
||||
id="additionalNameClaimTypes"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="acrValues">{{ "acrValues" | i18n }}</label>
|
||||
<input class="form-control" formControlName="acrValues" id="acrValues" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="expectedReturnAcrValue">{{ "expectedReturnAcrValue" | i18n }}</label>
|
||||
<input
|
||||
class="form-control"
|
||||
formControlName="expectedReturnAcrValue"
|
||||
id="expectedReturnAcrValue"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="data.value.configType == 2">
|
||||
<!-- SAML2 SP -->
|
||||
<div class="config-section">
|
||||
<h2>{{ "samlSpConfig" | i18n }}</h2>
|
||||
<div class="form-group">
|
||||
<label>{{ "spEntityId" | i18n }}</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" readonly [value]="spEntityId" />
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||
(click)="copy(spEntityId)"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{{ "spMetadataUrl" | i18n }}</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" readonly [value]="spMetadataUrl" />
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'launch' | i18n }}"
|
||||
(click)="launchUri(spMetadataUrl)"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-external-link" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||
(click)="copy(spMetadataUrl)"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{{ "spAcsUrl" | i18n }}</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" readonly [value]="spAcsUrl" />
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||
(click)="copy(spAcsUrl)"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="spNameIdFormat">{{ "spNameIdFormat" | i18n }}</label>
|
||||
<select class="form-control" formControlName="spNameIdFormat" id="spNameIdFormat">
|
||||
<option value="0">Not Configured</option>
|
||||
<option value="1">Unspecified</option>
|
||||
<option value="2">Email Address</option>
|
||||
<option value="3">X.509 Subject Name</option>
|
||||
<option value="4">Windows Domain Qualified Name</option>
|
||||
<option value="5">Kerberos Principal Name</option>
|
||||
<option value="6">Entity Identifier</option>
|
||||
<option value="7">Persistent</option>
|
||||
<option value="8">Transient</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="spOutboundSigningAlgorithm">{{ "spOutboundSigningAlgorithm" | i18n }}</label>
|
||||
<select
|
||||
class="form-control"
|
||||
formControlName="spOutboundSigningAlgorithm"
|
||||
id="spOutboundSigningAlgorithm"
|
||||
>
|
||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="spSigningBehavior">{{ "spSigningBehavior" | i18n }}</label>
|
||||
<select class="form-control" formControlName="spSigningBehavior" id="spSigningBehavior">
|
||||
<option value="0">If IdP Wants Authn Requests Signed</option>
|
||||
<option value="1">Always</option>
|
||||
<option value="3">Never</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="spMinIncomingSigningAlgorithm">{{
|
||||
"spMinIncomingSigningAlgorithm" | i18n
|
||||
}}</label>
|
||||
<select
|
||||
class="form-control"
|
||||
formControlName="spMinIncomingSigningAlgorithm"
|
||||
id="spMinIncomingSigningAlgorithm"
|
||||
>
|
||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="spWantAssertionsSigned"
|
||||
formControlName="spWantAssertionsSigned"
|
||||
/>
|
||||
<label class="form-check-label" for="spWantAssertionsSigned">
|
||||
{{ "spWantAssertionsSigned" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="spValidateCertificates"
|
||||
formControlName="spValidateCertificates"
|
||||
/>
|
||||
<label class="form-check-label" for="spValidateCertificates">
|
||||
{{ "spValidateCertificates" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SAML2 IDP -->
|
||||
<div class="config-section">
|
||||
<h2>{{ "samlIdpConfig" | i18n }}</h2>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="idpEntityId">{{ "idpEntityId" | i18n }}</label>
|
||||
<input class="form-control" formControlName="idpEntityId" id="idpEntityId" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="idpBindingType">{{ "idpBindingType" | i18n }}</label>
|
||||
<select class="form-control" formControlName="idpBindingType" id="idpBindingType">
|
||||
<option value="1">Redirect</option>
|
||||
<option value="2">HTTP POST</option>
|
||||
<option value="4">Artifact</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="idpSingleSignOnServiceUrl">{{ "idpSingleSignOnServiceUrl" | i18n }}</label>
|
||||
<input
|
||||
class="form-control"
|
||||
formControlName="idpSingleSignOnServiceUrl"
|
||||
id="idpSingleSignOnServiceUrl"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="idpSingleLogoutServiceUrl">{{ "idpSingleLogoutServiceUrl" | i18n }}</label>
|
||||
<input
|
||||
class="form-control"
|
||||
formControlName="idpSingleLogoutServiceUrl"
|
||||
id="idpSingleLogoutServiceUrl"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="idpArtifactResolutionServiceUrl">{{
|
||||
"idpArtifactResolutionServiceUrl" | i18n
|
||||
}}</label>
|
||||
<input
|
||||
class="form-control"
|
||||
formControlName="idpArtifactResolutionServiceUrl"
|
||||
id="idpArtifactResolutionServiceUrl"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="idpX509PublicCert">{{ "idpX509PublicCert" | i18n }}</label>
|
||||
<textarea
|
||||
formControlName="idpX509PublicCert"
|
||||
class="form-control form-control-sm text-monospace"
|
||||
rows="6"
|
||||
id="idpX509PublicCert"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="idpOutboundSigningAlgorithm">{{ "idpOutboundSigningAlgorithm" | i18n }}</label>
|
||||
<select
|
||||
class="form-control"
|
||||
formControlName="idpOutboundSigningAlgorithm"
|
||||
id="idpOutboundSigningAlgorithm"
|
||||
>
|
||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="idpAllowUnsolicitedAuthnResponse"
|
||||
formControlName="idpAllowUnsolicitedAuthnResponse"
|
||||
/>
|
||||
<label class="form-check-label" for="idpAllowUnsolicitedAuthnResponse">
|
||||
{{ "idpAllowUnsolicitedAuthnResponse" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="idpDisableOutboundLogoutRequests"
|
||||
formControlName="idpDisableOutboundLogoutRequests"
|
||||
/>
|
||||
<label class="form-check-label" for="idpDisableOutboundLogoutRequests">
|
||||
{{ "idpDisableOutboundLogoutRequests" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="idpWantAuthnRequestsSigned"
|
||||
formControlName="idpWantAuthnRequestsSigned"
|
||||
/>
|
||||
<label class="form-check-label" for="idpWantAuthnRequestsSigned">
|
||||
{{ "idpWantAuthnRequestsSigned" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
183
bitwarden_license/src/app/organizations/manage/sso.component.ts
Normal file
183
bitwarden_license/src/app/organizations/manage/sso.component.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
|
||||
import { OrganizationSsoRequest } from "jslib-common/models/request/organization/organizationSsoRequest";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-manage-sso",
|
||||
templateUrl: "sso.component.html",
|
||||
})
|
||||
export class SsoComponent implements OnInit {
|
||||
samlSigningAlgorithms = [
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||
"http://www.w3.org/2000/09/xmldsig#rsa-sha384",
|
||||
"http://www.w3.org/2000/09/xmldsig#rsa-sha512",
|
||||
"http://www.w3.org/2000/09/xmldsig#rsa-sha1",
|
||||
];
|
||||
|
||||
loading = true;
|
||||
organizationId: string;
|
||||
organization: Organization;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
callbackPath: string;
|
||||
signedOutCallbackPath: string;
|
||||
spEntityId: string;
|
||||
spMetadataUrl: string;
|
||||
spAcsUrl: string;
|
||||
|
||||
enabled = this.formBuilder.control(false);
|
||||
data = this.formBuilder.group({
|
||||
configType: [],
|
||||
|
||||
keyConnectorEnabled: [],
|
||||
keyConnectorUrl: [],
|
||||
|
||||
// OpenId
|
||||
authority: [],
|
||||
clientId: [],
|
||||
clientSecret: [],
|
||||
metadataAddress: [],
|
||||
redirectBehavior: [],
|
||||
getClaimsFromUserInfoEndpoint: [],
|
||||
additionalScopes: [],
|
||||
additionalUserIdClaimTypes: [],
|
||||
additionalEmailClaimTypes: [],
|
||||
additionalNameClaimTypes: [],
|
||||
acrValues: [],
|
||||
expectedReturnAcrValue: [],
|
||||
|
||||
// SAML
|
||||
spNameIdFormat: [],
|
||||
spOutboundSigningAlgorithm: [],
|
||||
spSigningBehavior: [],
|
||||
spMinIncomingSigningAlgorithm: [],
|
||||
spWantAssertionsSigned: [],
|
||||
spValidateCertificates: [],
|
||||
|
||||
idpEntityId: [],
|
||||
idpBindingType: [],
|
||||
idpSingleSignOnServiceUrl: [],
|
||||
idpSingleLogoutServiceUrl: [],
|
||||
idpArtifactResolutionServiceUrl: [],
|
||||
idpX509PublicCert: [],
|
||||
idpOutboundSigningAlgorithm: [],
|
||||
idpAllowUnsolicitedAuthnResponse: [],
|
||||
idpDisableOutboundLogoutRequests: [],
|
||||
idpWantAuthnRequestsSigned: [],
|
||||
});
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private route: ActivatedRoute,
|
||||
private apiService: ApiService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private organizationService: OrganizationService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
await this.load();
|
||||
});
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.organization = await this.organizationService.get(this.organizationId);
|
||||
const ssoSettings = await this.apiService.getOrganizationSso(this.organizationId);
|
||||
|
||||
this.data.patchValue(ssoSettings.data);
|
||||
this.enabled.setValue(ssoSettings.enabled);
|
||||
|
||||
this.callbackPath = ssoSettings.urls.callbackPath;
|
||||
this.signedOutCallbackPath = ssoSettings.urls.signedOutCallbackPath;
|
||||
this.spEntityId = ssoSettings.urls.spEntityId;
|
||||
this.spMetadataUrl = ssoSettings.urls.spMetadataUrl;
|
||||
this.spAcsUrl = ssoSettings.urls.spAcsUrl;
|
||||
|
||||
this.keyConnectorUrl.markAsDirty();
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
copy(value: string) {
|
||||
this.platformUtilsService.copyToClipboard(value);
|
||||
}
|
||||
|
||||
launchUri(url: string) {
|
||||
this.platformUtilsService.launchUri(url);
|
||||
}
|
||||
|
||||
async submit() {
|
||||
this.formPromise = this.postData();
|
||||
|
||||
try {
|
||||
const response = await this.formPromise;
|
||||
|
||||
this.data.patchValue(response.data);
|
||||
this.enabled.setValue(response.enabled);
|
||||
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("ssoSettingsSaved"));
|
||||
} catch {
|
||||
// Logged by appApiAction, do nothing
|
||||
}
|
||||
|
||||
this.formPromise = null;
|
||||
}
|
||||
|
||||
async postData() {
|
||||
if (this.data.get("keyConnectorEnabled").value) {
|
||||
await this.validateKeyConnectorUrl();
|
||||
|
||||
if (this.keyConnectorUrl.hasError("invalidUrl")) {
|
||||
throw new Error(this.i18nService.t("keyConnectorTestFail"));
|
||||
}
|
||||
}
|
||||
|
||||
const request = new OrganizationSsoRequest();
|
||||
request.enabled = this.enabled.value;
|
||||
request.data = this.data.value;
|
||||
|
||||
return this.apiService.postOrganizationSso(this.organizationId, request);
|
||||
}
|
||||
|
||||
async validateKeyConnectorUrl() {
|
||||
if (this.keyConnectorUrl.pristine) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.keyConnectorUrl.markAsPending();
|
||||
|
||||
try {
|
||||
await this.apiService.getKeyConnectorAlive(this.keyConnectorUrl.value);
|
||||
this.keyConnectorUrl.updateValueAndValidity();
|
||||
} catch {
|
||||
this.keyConnectorUrl.setErrors({
|
||||
invalidUrl: true,
|
||||
});
|
||||
}
|
||||
|
||||
this.keyConnectorUrl.markAsPristine();
|
||||
}
|
||||
|
||||
get enableTestKeyConnector() {
|
||||
return (
|
||||
this.data.get("keyConnectorEnabled").value &&
|
||||
this.keyConnectorUrl != null &&
|
||||
this.keyConnectorUrl.value !== ""
|
||||
);
|
||||
}
|
||||
|
||||
get keyConnectorUrl() {
|
||||
return this.data.get("keyConnectorUrl");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
|
||||
|
||||
import { Permissions } from "jslib-common/enums/permissions";
|
||||
|
||||
import { OrganizationLayoutComponent } from "src/app/layouts/organization-layout.component";
|
||||
import { ManageComponent } from "src/app/organizations/manage/manage.component";
|
||||
import { OrganizationGuardService } from "src/app/services/organization-guard.service";
|
||||
import { OrganizationTypeGuardService } from "src/app/services/organization-type-guard.service";
|
||||
|
||||
import { SsoComponent } from "./manage/sso.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "organizations/:organizationId",
|
||||
component: OrganizationLayoutComponent,
|
||||
canActivate: [AuthGuardService, OrganizationGuardService],
|
||||
children: [
|
||||
{
|
||||
path: "manage",
|
||||
component: ManageComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
permissions: [
|
||||
Permissions.CreateNewCollections,
|
||||
Permissions.EditAnyCollection,
|
||||
Permissions.DeleteAnyCollection,
|
||||
Permissions.EditAssignedCollections,
|
||||
Permissions.DeleteAssignedCollections,
|
||||
Permissions.AccessEventLogs,
|
||||
Permissions.ManageGroups,
|
||||
Permissions.ManageUsers,
|
||||
Permissions.ManagePolicies,
|
||||
Permissions.ManageSso,
|
||||
],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "sso",
|
||||
component: SsoComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class OrganizationsRoutingModule {}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
|
||||
import { OssModule } from "src/app/oss.module";
|
||||
|
||||
import { SsoComponent } from "./manage/sso.component";
|
||||
import { OrganizationsRoutingModule } from "./organizations-routing.module";
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, FormsModule, ReactiveFormsModule, OssModule, OrganizationsRoutingModule],
|
||||
declarations: [SsoComponent],
|
||||
})
|
||||
export class OrganizationsModule {}
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enabled"
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
|
||||
import { PolicyType } from "jslib-common/enums/policyType";
|
||||
|
||||
import { PolicyRequest } from "jslib-common/models/request/policyRequest";
|
||||
|
||||
import {
|
||||
BasePolicy,
|
||||
BasePolicyComponent,
|
||||
} from "src/app/organizations/policies/base-policy.component";
|
||||
|
||||
export class DisablePersonalVaultExportPolicy extends BasePolicy {
|
||||
name = "disablePersonalVaultExport";
|
||||
description = "disablePersonalVaultExportDesc";
|
||||
type = PolicyType.DisablePersonalVaultExport;
|
||||
component = DisablePersonalVaultExportPolicyComponent;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "policy-disable-personal-vault-export",
|
||||
templateUrl: "disable-personal-vault-export.component.html",
|
||||
})
|
||||
export class DisablePersonalVaultExportPolicyComponent extends BasePolicyComponent {}
|
||||
@@ -0,0 +1,47 @@
|
||||
<app-callout type="tip" title="{{ 'prerequisite' | i18n }}">
|
||||
{{ "requireSsoPolicyReq" | i18n }}
|
||||
</app-callout>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enabled"
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div [formGroup]="data">
|
||||
<div class="form-group">
|
||||
<label for="hours">{{ "maximumVaultTimeoutLabel" | i18n }}</label>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<input
|
||||
id="hours"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
name="hours"
|
||||
formControlName="hours"
|
||||
/>
|
||||
<small>{{ "hours" | i18n }}</small>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<input
|
||||
id="minutes"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
max="59"
|
||||
name="minutes"
|
||||
formControlName="minutes"
|
||||
/>
|
||||
<small>{{ "minutes" | i18n }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
|
||||
import { PolicyType } from "jslib-common/enums/policyType";
|
||||
|
||||
import { PolicyRequest } from "jslib-common/models/request/policyRequest";
|
||||
|
||||
import {
|
||||
BasePolicy,
|
||||
BasePolicyComponent,
|
||||
} from "src/app/organizations/policies/base-policy.component";
|
||||
|
||||
export class MaximumVaultTimeoutPolicy extends BasePolicy {
|
||||
name = "maximumVaultTimeout";
|
||||
description = "maximumVaultTimeoutDesc";
|
||||
type = PolicyType.MaximumVaultTimeout;
|
||||
component = MaximumVaultTimeoutPolicyComponent;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "policy-maximum-timeout",
|
||||
templateUrl: "maximum-vault-timeout.component.html",
|
||||
})
|
||||
export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent {
|
||||
data = this.formBuilder.group({
|
||||
hours: [null],
|
||||
minutes: [null],
|
||||
});
|
||||
|
||||
constructor(private formBuilder: FormBuilder, private i18nService: I18nService) {
|
||||
super();
|
||||
}
|
||||
|
||||
loadData() {
|
||||
const minutes = this.policyResponse.data?.minutes;
|
||||
|
||||
if (minutes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.data.patchValue({
|
||||
hours: Math.floor(minutes / 60),
|
||||
minutes: minutes % 60,
|
||||
});
|
||||
}
|
||||
|
||||
buildRequestData() {
|
||||
if (this.data.value.hours == null && this.data.value.minutes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
minutes: this.data.value.hours * 60 + this.data.value.minutes,
|
||||
};
|
||||
}
|
||||
|
||||
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
|
||||
const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false;
|
||||
if (this.enabled.value && !singleOrgEnabled) {
|
||||
throw new Error(this.i18nService.t("requireSsoPolicyReqError"));
|
||||
}
|
||||
|
||||
const data = this.buildRequestData();
|
||||
if (data?.minutes == null || data?.minutes <= 0) {
|
||||
throw new Error(this.i18nService.t("invalidMaximumVaultTimeout"));
|
||||
}
|
||||
|
||||
return super.buildRequest(policiesEnabledMap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="addTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="addTitle">
|
||||
{{ "addExistingOrganization" | 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="card-body text-center" *ngIf="loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
{{ "loading" | i18n }}
|
||||
</div>
|
||||
<ng-container *ngIf="!loading">
|
||||
<table class="table table-hover table-list">
|
||||
<tr *ngFor="let o of organizations">
|
||||
<td width="30">
|
||||
<app-avatar [data]="o.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
|
||||
</td>
|
||||
<td>
|
||||
{{ o.name }}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
class="btn btn-outline-secondary pull-right"
|
||||
(click)="add(o)"
|
||||
[disabled]="formPromise"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,86 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
|
||||
import { ValidationService } from "jslib-angular/services/validation.service";
|
||||
|
||||
import { WebProviderService } from "../services/webProvider.service";
|
||||
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
import { Provider } from "jslib-common/models/domain/provider";
|
||||
|
||||
@Component({
|
||||
selector: "provider-add-organization",
|
||||
templateUrl: "add-organization.component.html",
|
||||
})
|
||||
export class AddOrganizationComponent implements OnInit {
|
||||
@Input() providerId: string;
|
||||
@Input() organizations: Organization[];
|
||||
@Output() onAddedOrganization = new EventEmitter();
|
||||
|
||||
provider: Provider;
|
||||
formPromise: Promise<any>;
|
||||
loading = true;
|
||||
|
||||
constructor(
|
||||
private providerService: ProviderService,
|
||||
private webProviderService: WebProviderService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private validationService: ValidationService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.load();
|
||||
}
|
||||
|
||||
async load() {
|
||||
if (this.providerId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.provider = await this.providerService.get(this.providerId);
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async add(organization: Organization) {
|
||||
if (this.formPromise) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("addOrganizationConfirmation", organization.name, this.provider.name),
|
||||
organization.name,
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.formPromise = this.webProviderService.addOrganizationToProvider(
|
||||
this.providerId,
|
||||
organization.id
|
||||
);
|
||||
await this.formPromise;
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
return;
|
||||
} finally {
|
||||
this.formPromise = null;
|
||||
}
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("organizationJoinedProvider")
|
||||
);
|
||||
this.onAddedOrganization.emit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{ "clients" | i18n }}</h1>
|
||||
|
||||
<div class="ml-auto d-flex">
|
||||
<div>
|
||||
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
|
||||
<input
|
||||
type="search"
|
||||
class="form-control form-control-sm"
|
||||
id="search"
|
||||
placeholder="{{ 'search' | i18n }}"
|
||||
[(ngModel)]="searchText"
|
||||
/>
|
||||
</div>
|
||||
<a class="btn btn-sm btn-outline-primary ml-3" routerLink="create" *ngIf="manageOrganizations">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newClientOrganization" | i18n }}
|
||||
</a>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-primary ml-3"
|
||||
(click)="addExistingOrganization()"
|
||||
*ngIf="manageOrganizations && showAddExisting"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "addExistingOrganization" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
|
||||
<ng-container
|
||||
*ngIf="!loading && (clients | search: searchText:'organizationName':'id') as searchedClients"
|
||||
>
|
||||
<p *ngIf="!searchedClients.length">{{ "noClientsInList" | i18n }}</p>
|
||||
<ng-container *ngIf="searchedClients.length">
|
||||
<table
|
||||
class="table table-hover table-list"
|
||||
infiniteScroll
|
||||
[infiniteScrollDistance]="1"
|
||||
[infiniteScrollDisabled]="!isPaging()"
|
||||
(scrolled)="loadMore()"
|
||||
>
|
||||
<tbody>
|
||||
<tr *ngFor="let o of searchedClients">
|
||||
<td width="30">
|
||||
<app-avatar
|
||||
[data]="o.organizationName"
|
||||
size="25"
|
||||
[circle]="true"
|
||||
[fontSize]="14"
|
||||
></app-avatar>
|
||||
</td>
|
||||
<td>
|
||||
<a [routerLink]="['/organizations', o.organizationId]">{{ o.organizationName }}</a>
|
||||
</td>
|
||||
<td class="table-list-options" *ngIf="manageOrganizations">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button
|
||||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(o)">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #add></ng-template>
|
||||
183
bitwarden_license/src/app/providers/clients/clients.component.ts
Normal file
183
bitwarden_license/src/app/providers/clients/clients.component.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { ActivatedRoute } 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 { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
import { ValidationService } from "jslib-angular/services/validation.service";
|
||||
|
||||
import { PlanType } from "jslib-common/enums/planType";
|
||||
import { ProviderUserType } from "jslib-common/enums/providerUserType";
|
||||
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
import { ProviderOrganizationOrganizationDetailsResponse } from "jslib-common/models/response/provider/providerOrganizationResponse";
|
||||
|
||||
import { WebProviderService } from "../services/webProvider.service";
|
||||
|
||||
import { AddOrganizationComponent } from "./add-organization.component";
|
||||
|
||||
const DisallowedPlanTypes = [
|
||||
PlanType.Free,
|
||||
PlanType.FamiliesAnnually2019,
|
||||
PlanType.FamiliesAnnually,
|
||||
];
|
||||
|
||||
@Component({
|
||||
templateUrl: "clients.component.html",
|
||||
})
|
||||
export class ClientsComponent implements OnInit {
|
||||
@ViewChild("add", { read: ViewContainerRef, static: true }) addModalRef: ViewContainerRef;
|
||||
|
||||
providerId: any;
|
||||
searchText: string;
|
||||
addableOrganizations: Organization[];
|
||||
loading = true;
|
||||
manageOrganizations = false;
|
||||
showAddExisting = false;
|
||||
|
||||
clients: ProviderOrganizationOrganizationDetailsResponse[];
|
||||
pagedClients: ProviderOrganizationOrganizationDetailsResponse[];
|
||||
|
||||
protected didScroll = false;
|
||||
protected pageSize = 100;
|
||||
protected actionPromise: Promise<any>;
|
||||
private pagedClientsCount = 0;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private providerService: ProviderService,
|
||||
private apiService: ApiService,
|
||||
private searchService: SearchService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private validationService: ValidationService,
|
||||
private webProviderService: WebProviderService,
|
||||
private logService: LogService,
|
||||
private modalService: ModalService,
|
||||
private organizationService: OrganizationService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.params.subscribe(async (params) => {
|
||||
this.providerId = params.providerId;
|
||||
|
||||
await this.load();
|
||||
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
this.searchText = qParams.search;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async load() {
|
||||
const response = await this.apiService.getProviderClients(this.providerId);
|
||||
this.clients = response.data != null && response.data.length > 0 ? response.data : [];
|
||||
this.manageOrganizations =
|
||||
(await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin;
|
||||
const candidateOrgs = (await this.organizationService.getAll()).filter(
|
||||
(o) => o.isOwner && o.providerId == null
|
||||
);
|
||||
const allowedOrgsIds = await Promise.all(
|
||||
candidateOrgs.map((o) => this.apiService.getOrganization(o.id))
|
||||
).then((orgs) =>
|
||||
orgs.filter((o) => !DisallowedPlanTypes.includes(o.planType)).map((o) => o.id)
|
||||
);
|
||||
this.addableOrganizations = candidateOrgs.filter((o) => allowedOrgsIds.includes(o.id));
|
||||
|
||||
this.showAddExisting = this.addableOrganizations.length !== 0;
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
isPaging() {
|
||||
const searching = this.isSearching();
|
||||
if (searching && this.didScroll) {
|
||||
this.resetPaging();
|
||||
}
|
||||
return !searching && this.clients && this.clients.length > this.pageSize;
|
||||
}
|
||||
|
||||
isSearching() {
|
||||
return this.searchService.isSearchable(this.searchText);
|
||||
}
|
||||
|
||||
async resetPaging() {
|
||||
this.pagedClients = [];
|
||||
this.loadMore();
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
if (!this.clients || this.clients.length <= this.pageSize) {
|
||||
return;
|
||||
}
|
||||
const pagedLength = this.pagedClients.length;
|
||||
let pagedSize = this.pageSize;
|
||||
if (pagedLength === 0 && this.pagedClientsCount > this.pageSize) {
|
||||
pagedSize = this.pagedClientsCount;
|
||||
}
|
||||
if (this.clients.length > pagedLength) {
|
||||
this.pagedClients = this.pagedClients.concat(
|
||||
this.clients.slice(pagedLength, pagedLength + pagedSize)
|
||||
);
|
||||
}
|
||||
this.pagedClientsCount = this.pagedClients.length;
|
||||
this.didScroll = this.pagedClients.length > this.pageSize;
|
||||
}
|
||||
|
||||
async addExistingOrganization() {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
AddOrganizationComponent,
|
||||
this.addModalRef,
|
||||
(comp) => {
|
||||
comp.providerId = this.providerId;
|
||||
comp.organizations = this.addableOrganizations;
|
||||
comp.onAddedOrganization.subscribe(async () => {
|
||||
try {
|
||||
await this.load();
|
||||
modal.close();
|
||||
} catch (e) {
|
||||
this.logService.error(`Handled exception: ${e}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async remove(organization: ProviderOrganizationOrganizationDetailsResponse) {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("detachOrganizationConfirmation"),
|
||||
organization.organizationName,
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.actionPromise = this.webProviderService.detachOrganizastion(
|
||||
this.providerId,
|
||||
organization.id
|
||||
);
|
||||
try {
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("detachedOrganization", organization.organizationName)
|
||||
);
|
||||
await this.load();
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
}
|
||||
this.actionPromise = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "newClientOrganization" | i18n }}</h1>
|
||||
</div>
|
||||
<p>{{ "newClientOrganizationDesc" | i18n }}</p>
|
||||
<app-organization-plans [providerId]="providerId"></app-organization-plans>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Component, OnInit, ViewChild } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { OrganizationPlansComponent } from "src/app/settings/organization-plans.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-create-organization",
|
||||
templateUrl: "create-organization.component.html",
|
||||
})
|
||||
export class CreateOrganizationComponent implements OnInit {
|
||||
@ViewChild(OrganizationPlansComponent, { static: true })
|
||||
orgPlansComponent: OrganizationPlansComponent;
|
||||
|
||||
providerId: string;
|
||||
|
||||
constructor(private route: ActivatedRoute) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.parent.params.subscribe(async (params) => {
|
||||
this.providerId = params.providerId;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<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">{{ "joinProvider" | i18n }}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<p class="text-center">
|
||||
{{ providerName }}
|
||||
<strong class="d-block mt-2">{{ email }}</strong>
|
||||
</p>
|
||||
<p>{{ "joinProviderDesc" | i18n }}</p>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
|
||||
{{ "logIn" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="/register"
|
||||
[queryParams]="{ email: email }"
|
||||
class="btn btn-primary btn-block ml-2 mt-0"
|
||||
>
|
||||
{{ "createAccount" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { BaseAcceptComponent } from "src/app/common/base.accept.component";
|
||||
|
||||
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 { ProviderUserAcceptRequest } from "jslib-common/models/request/provider/providerUserAcceptRequest";
|
||||
|
||||
@Component({
|
||||
selector: "app-accept-provider",
|
||||
templateUrl: "accept-provider.component.html",
|
||||
})
|
||||
export class AcceptProviderComponent extends BaseAcceptComponent {
|
||||
providerName: string;
|
||||
|
||||
failedMessage = "providerInviteAcceptFailed";
|
||||
|
||||
requiredParameters = ["providerId", "providerUserId", "token"];
|
||||
|
||||
constructor(
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
route: ActivatedRoute,
|
||||
stateService: StateService,
|
||||
private apiService: ApiService,
|
||||
platformUtilService: PlatformUtilsService
|
||||
) {
|
||||
super(router, platformUtilService, i18nService, route, stateService);
|
||||
}
|
||||
|
||||
async authedHandler(qParams: any) {
|
||||
const request = new ProviderUserAcceptRequest();
|
||||
request.token = qParams.token;
|
||||
|
||||
await this.apiService.postProviderUserAccept(
|
||||
qParams.providerId,
|
||||
qParams.providerUserId,
|
||||
request
|
||||
);
|
||||
this.platformUtilService.showToast(
|
||||
"success",
|
||||
this.i18nService.t("inviteAccepted"),
|
||||
this.i18nService.t("providerInviteAcceptedDesc"),
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
this.router.navigate(["/vault"]);
|
||||
}
|
||||
|
||||
async unauthedHandler(qParams: any) {
|
||||
this.providerName = qParams.providerName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
import { ProviderUserBulkConfirmRequest } from "jslib-common/models/request/provider/providerUserBulkConfirmRequest";
|
||||
import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest";
|
||||
|
||||
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
|
||||
|
||||
import { BulkConfirmComponent as OrganizationBulkConfirmComponent } from "src/app/organizations/manage/bulk/bulk-confirm.component";
|
||||
import { BulkUserDetails } from "src/app/organizations/manage/bulk/bulk-status.component";
|
||||
|
||||
@Component({
|
||||
templateUrl: "../../../../../../src/app/organizations/manage/bulk/bulk-confirm.component.html",
|
||||
})
|
||||
export class BulkConfirmComponent extends OrganizationBulkConfirmComponent {
|
||||
@Input() providerId: string;
|
||||
|
||||
protected isAccepted(user: BulkUserDetails) {
|
||||
return user.status === ProviderUserStatusType.Accepted;
|
||||
}
|
||||
|
||||
protected async getPublicKeys() {
|
||||
const request = new ProviderUserBulkRequest(this.filteredUsers.map((user) => user.id));
|
||||
return await this.apiService.postProviderUsersPublicKey(this.providerId, request);
|
||||
}
|
||||
|
||||
protected getCryptoKey() {
|
||||
return this.cryptoService.getProviderKey(this.providerId);
|
||||
}
|
||||
|
||||
protected async postConfirmRequest(userIdsWithKeys: any[]) {
|
||||
const request = new ProviderUserBulkConfirmRequest(userIdsWithKeys);
|
||||
return await this.apiService.postProviderUserBulkConfirm(this.providerId, request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest";
|
||||
|
||||
import { BulkRemoveComponent as OrganizationBulkRemoveComponent } from "src/app/organizations/manage/bulk/bulk-remove.component";
|
||||
|
||||
@Component({
|
||||
templateUrl: "../../../../../../src/app/organizations/manage/bulk/bulk-remove.component.html",
|
||||
})
|
||||
export class BulkRemoveComponent extends OrganizationBulkRemoveComponent {
|
||||
@Input() providerId: string;
|
||||
|
||||
async deleteUsers() {
|
||||
const request = new ProviderUserBulkRequest(this.users.map((user) => user.id));
|
||||
return await this.apiService.deleteManyProviderUsers(this.providerId, request);
|
||||
}
|
||||
}
|
||||
107
bitwarden_license/src/app/providers/manage/events.component.html
Normal file
107
bitwarden_license/src/app/providers/manage/events.component.html
Normal file
@@ -0,0 +1,107 @@
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{ "eventLogs" | i18n }}</h1>
|
||||
<div class="ml-auto d-flex">
|
||||
<div class="form-inline">
|
||||
<label class="sr-only" for="start">{{ "startDate" | i18n }}</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
class="form-control form-control-sm"
|
||||
id="start"
|
||||
placeholder="{{ 'startDate' | i18n }}"
|
||||
[(ngModel)]="start"
|
||||
placeholder="YYYY-MM-DDTHH:MM"
|
||||
(change)="dirtyDates = true"
|
||||
/>
|
||||
<span class="mx-2">-</span>
|
||||
<label class="sr-only" for="end">{{ "endDate" | i18n }}</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
class="form-control form-control-sm"
|
||||
id="end"
|
||||
placeholder="{{ 'endDate' | i18n }}"
|
||||
[(ngModel)]="end"
|
||||
placeholder="YYYY-MM-DDTHH:MM"
|
||||
(change)="dirtyDates = true"
|
||||
/>
|
||||
</div>
|
||||
<form #refreshForm [appApiAction]="refreshPromise" class="d-inline">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-primary ml-3"
|
||||
(click)="loadEvents(true)"
|
||||
[disabled]="loaded && refreshForm.loading"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-refresh bwi-fw"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-spin': loaded && refreshForm.loading }"
|
||||
></i>
|
||||
{{ "refresh" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
<form #exportForm [appApiAction]="exportPromise" class="d-inline">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-primary btn-submit manual ml-3"
|
||||
[ngClass]="{ loading: exportForm.loading }"
|
||||
(click)="exportEvents()"
|
||||
[disabled]="(loaded && exportForm.loading) || dirtyDates"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" aria-hidden="true"></i>
|
||||
<span>{{ "export" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="loaded">
|
||||
<p *ngIf="!events || !events.length">{{ "noEventsInList" | i18n }}</p>
|
||||
<table class="table table-hover" *ngIf="events && events.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border-top-0" width="210">{{ "timestamp" | i18n }}</th>
|
||||
<th class="border-top-0" width="40">
|
||||
<span class="sr-only">{{ "device" | i18n }}</span>
|
||||
</th>
|
||||
<th class="border-top-0" width="150">{{ "user" | i18n }}</th>
|
||||
<th class="border-top-0">{{ "event" | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let e of events">
|
||||
<td>{{ e.date | date: "medium" }}</td>
|
||||
<td>
|
||||
<i
|
||||
class="text-muted bwi bwi-lg {{ e.appIcon }}"
|
||||
title="{{ e.appName }}, {{ e.ip }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ e.appName }}, {{ e.ip }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span title="{{ e.userEmail }}">{{ e.userName }}</span>
|
||||
</td>
|
||||
<td [innerHTML]="e.message"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button
|
||||
#moreBtn
|
||||
[appApiAction]="morePromise"
|
||||
type="button"
|
||||
class="btn btn-block btn-link btn-submit"
|
||||
(click)="loadEvents(false)"
|
||||
[disabled]="loaded && moreBtn.loading"
|
||||
*ngIf="continuationToken"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "loadMore" | i18n }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
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 { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
|
||||
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
|
||||
|
||||
import { EventResponse } from "jslib-common/models/response/eventResponse";
|
||||
|
||||
import { EventService } from "src/app/services/event.service";
|
||||
|
||||
import { BaseEventsComponent } from "src/app/common/base.events.component";
|
||||
|
||||
@Component({
|
||||
selector: "provider-events",
|
||||
templateUrl: "events.component.html",
|
||||
})
|
||||
export class EventsComponent extends BaseEventsComponent implements OnInit {
|
||||
exportFileName: string = "provider-events";
|
||||
providerId: string;
|
||||
|
||||
private providerUsersUserIdMap = new Map<string, any>();
|
||||
private providerUsersIdMap = new Map<string, any>();
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private route: ActivatedRoute,
|
||||
eventService: EventService,
|
||||
i18nService: I18nService,
|
||||
private providerService: ProviderService,
|
||||
exportService: ExportService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
private router: Router,
|
||||
logService: LogService,
|
||||
private userNamePipe: UserNamePipe
|
||||
) {
|
||||
super(eventService, i18nService, exportService, platformUtilsService, logService);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.providerId = params.providerId;
|
||||
const provider = await this.providerService.get(this.providerId);
|
||||
if (provider == null || !provider.useEvents) {
|
||||
this.router.navigate(["/providers", this.providerId]);
|
||||
return;
|
||||
}
|
||||
await this.load();
|
||||
});
|
||||
}
|
||||
|
||||
async load() {
|
||||
const response = await this.apiService.getProviderUsers(this.providerId);
|
||||
response.data.forEach((u) => {
|
||||
const name = this.userNamePipe.transform(u);
|
||||
this.providerUsersIdMap.set(u.id, { name: name, email: u.email });
|
||||
this.providerUsersUserIdMap.set(u.userId, { name: name, email: u.email });
|
||||
});
|
||||
await this.loadEvents(true);
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
protected requestEvents(startDate: string, endDate: string, continuationToken: string) {
|
||||
return this.apiService.getEventsProvider(
|
||||
this.providerId,
|
||||
startDate,
|
||||
endDate,
|
||||
continuationToken
|
||||
);
|
||||
}
|
||||
|
||||
protected getUserName(r: EventResponse, userId: string) {
|
||||
return userId != null && this.providerUsersUserIdMap.has(userId)
|
||||
? this.providerUsersUserIdMap.get(userId)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="card" *ngIf="provider">
|
||||
<div class="card-header">{{ "manage" | i18n }}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a
|
||||
routerLink="people"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="provider.canManageUsers"
|
||||
>
|
||||
{{ "people" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="events"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="provider.canAccessEventLogs && accessEvents"
|
||||
>
|
||||
{{ "eventLogs" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
|
||||
import { Provider } from "jslib-common/models/domain/provider";
|
||||
|
||||
@Component({
|
||||
selector: "provider-manage",
|
||||
templateUrl: "manage.component.html",
|
||||
})
|
||||
export class ManageComponent implements OnInit {
|
||||
provider: Provider;
|
||||
accessEvents = false;
|
||||
|
||||
constructor(private route: ActivatedRoute, private providerService: ProviderService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.parent.params.subscribe(async (params) => {
|
||||
this.provider = await this.providerService.get(params.providerId);
|
||||
this.accessEvents = this.provider.useEvents;
|
||||
});
|
||||
}
|
||||
}
|
||||
233
bitwarden_license/src/app/providers/manage/people.component.html
Normal file
233
bitwarden_license/src/app/providers/manage/people.component.html
Normal file
@@ -0,0 +1,233 @@
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{ "people" | i18n }}</h1>
|
||||
<div class="ml-auto d-flex">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
[ngClass]="{ active: status == null }"
|
||||
(click)="filter(null)"
|
||||
>
|
||||
{{ "all" | i18n }}
|
||||
<span class="badge badge-pill badge-info" *ngIf="allCount">{{ allCount }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
[ngClass]="{ active: status == userStatusType.Invited }"
|
||||
(click)="filter(userStatusType.Invited)"
|
||||
>
|
||||
{{ "invited" | i18n }}
|
||||
<span class="badge badge-pill badge-info" *ngIf="invitedCount">{{ invitedCount }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
[ngClass]="{ active: status == userStatusType.Accepted }"
|
||||
(click)="filter(userStatusType.Accepted)"
|
||||
>
|
||||
{{ "accepted" | i18n }}
|
||||
<span class="badge badge-pill badge-warning" *ngIf="acceptedCount">{{
|
||||
acceptedCount
|
||||
}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
|
||||
<input
|
||||
type="search"
|
||||
class="form-control form-control-sm"
|
||||
id="search"
|
||||
placeholder="{{ 'search' | i18n }}"
|
||||
[(ngModel)]="searchText"
|
||||
/>
|
||||
</div>
|
||||
<div class="dropdown ml-3" appListDropdown>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary dropdown-toggle"
|
||||
type="button"
|
||||
id="bulkActionsButton"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-cog" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
|
||||
<button class="dropdown-item" appStopClick (click)="bulkReinvite()">
|
||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||
{{ "reinviteSelected" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
class="dropdown-item text-success"
|
||||
appStopClick
|
||||
(click)="bulkConfirm()"
|
||||
*ngIf="showBulkConfirmUsers"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
{{ "confirmSelected" | i18n }}
|
||||
</button>
|
||||
<button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button class="dropdown-item" appStopClick (click)="selectAll(true)">
|
||||
<i class="bwi bwi-fw bwi-check-square" aria-hidden="true"></i>
|
||||
{{ "selectAll" | i18n }}
|
||||
</button>
|
||||
<button class="dropdown-item" appStopClick (click)="selectAll(false)">
|
||||
<i class="bwi bwi-fw bwi-minus-square" aria-hidden="true"></i>
|
||||
{{ "unselectAll" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="invite()">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "inviteUser" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
!loading &&
|
||||
(isPaging() ? pagedUsers : (users | search: searchText:'name':'email':'id')) as searchedUsers
|
||||
"
|
||||
>
|
||||
<p *ngIf="!searchedUsers.length">{{ "noUsersInList" | i18n }}</p>
|
||||
<ng-container *ngIf="searchedUsers.length">
|
||||
<app-callout
|
||||
type="info"
|
||||
title="{{ 'confirmUsers' | i18n }}"
|
||||
icon="bwi bwi-check-circle"
|
||||
*ngIf="showConfirmUsers"
|
||||
>
|
||||
{{ "providerUsersNeedConfirmed" | i18n }}
|
||||
</app-callout>
|
||||
<table
|
||||
class="table table-hover table-list"
|
||||
infiniteScroll
|
||||
[infiniteScrollDistance]="1"
|
||||
[infiniteScrollDisabled]="!isPaging()"
|
||||
(scrolled)="loadMore()"
|
||||
>
|
||||
<tbody>
|
||||
<tr *ngFor="let u of searchedUsers">
|
||||
<td (click)="checkUser(u)" class="table-list-checkbox">
|
||||
<input type="checkbox" [(ngModel)]="u.checked" appStopProp />
|
||||
</td>
|
||||
<td width="30">
|
||||
<app-avatar
|
||||
[data]="u | userName"
|
||||
[email]="u.email"
|
||||
size="25"
|
||||
[circle]="true"
|
||||
[fontSize]="14"
|
||||
>
|
||||
</app-avatar>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" appStopClick (click)="edit(u)">{{ u.email }}</a>
|
||||
<span class="badge badge-secondary" *ngIf="u.status === userStatusType.Invited">{{
|
||||
"invited" | i18n
|
||||
}}</span>
|
||||
<span class="badge badge-warning" *ngIf="u.status === userStatusType.Accepted">{{
|
||||
"accepted" | i18n
|
||||
}}</span>
|
||||
<small class="text-muted d-block" *ngIf="u.name">{{ u.name }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<ng-container *ngIf="u.twoFactorEnabled">
|
||||
<i
|
||||
class="bwi bwi-lock"
|
||||
title="{{ 'userUsingTwoStep' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "userUsingTwoStep" | i18n }}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
<span *ngIf="u.type === userType.ProviderAdmin">{{ "providerAdmin" | i18n }}</span>
|
||||
<span *ngIf="u.type === userType.ServiceUser">{{ "serviceUser" | i18n }}</span>
|
||||
<span *ngIf="u.type === userType.Custom">{{ "custom" | i18n }}</span>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button
|
||||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="reinvite(u)"
|
||||
*ngIf="u.status === userStatusType.Invited"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||
{{ "resendInvitation" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item text-success"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="confirm(u)"
|
||||
*ngIf="u.status === userStatusType.Accepted"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
{{ "confirm" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="groups(u)"
|
||||
*ngIf="accessGroups"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-sitemap" aria-hidden="true"></i>
|
||||
{{ "groups" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="events(u)"
|
||||
*ngIf="accessEvents && u.status === userStatusType.Confirmed"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-file-text" aria-hidden="true"></i>
|
||||
{{ "eventLogs" | i18n }}
|
||||
</a>
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
|
||||
<i class="bwi bwi-fw bwi-remove" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-template #addEdit></ng-template>
|
||||
<ng-template #eventsTemplate></ng-template>
|
||||
<ng-template #confirmTemplate></ng-template>
|
||||
<ng-template #bulkStatusTemplate></ng-template>
|
||||
<ng-template #bulkConfirmTemplate></ng-template>
|
||||
<ng-template #bulkRemoveTemplate></ng-template>
|
||||
292
bitwarden_license/src/app/providers/manage/people.component.ts
Normal file
292
bitwarden_license/src/app/providers/manage/people.component.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
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 { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
import { ValidationService } from "jslib-angular/services/validation.service";
|
||||
|
||||
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
|
||||
import { ProviderUserType } from "jslib-common/enums/providerUserType";
|
||||
|
||||
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
|
||||
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
|
||||
|
||||
import { ListResponse } from "jslib-common/models/response/listResponse";
|
||||
import { ProviderUserUserDetailsResponse } from "jslib-common/models/response/provider/providerUserResponse";
|
||||
|
||||
import { ProviderUserBulkRequest } from "jslib-common/models/request/provider/providerUserBulkRequest";
|
||||
import { ProviderUserConfirmRequest } from "jslib-common/models/request/provider/providerUserConfirmRequest";
|
||||
import { ProviderUserBulkResponse } from "jslib-common/models/response/provider/providerUserBulkResponse";
|
||||
|
||||
import { BasePeopleComponent } from "src/app/common/base.people.component";
|
||||
import { BulkStatusComponent } from "src/app/organizations/manage/bulk/bulk-status.component";
|
||||
import { EntityEventsComponent } from "src/app/organizations/manage/entity-events.component";
|
||||
import { BulkConfirmComponent } from "./bulk/bulk-confirm.component";
|
||||
import { BulkRemoveComponent } from "./bulk/bulk-remove.component";
|
||||
import { UserAddEditComponent } from "./user-add-edit.component";
|
||||
|
||||
@Component({
|
||||
selector: "provider-people",
|
||||
templateUrl: "people.component.html",
|
||||
})
|
||||
export class PeopleComponent
|
||||
extends BasePeopleComponent<ProviderUserUserDetailsResponse>
|
||||
implements OnInit
|
||||
{
|
||||
@ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
|
||||
@ViewChild("groupsTemplate", { read: ViewContainerRef, static: true })
|
||||
groupsModalRef: ViewContainerRef;
|
||||
@ViewChild("eventsTemplate", { read: ViewContainerRef, static: true })
|
||||
eventsModalRef: ViewContainerRef;
|
||||
@ViewChild("bulkStatusTemplate", { read: ViewContainerRef, static: true })
|
||||
bulkStatusModalRef: ViewContainerRef;
|
||||
@ViewChild("bulkConfirmTemplate", { read: ViewContainerRef, static: true })
|
||||
bulkConfirmModalRef: ViewContainerRef;
|
||||
@ViewChild("bulkRemoveTemplate", { read: ViewContainerRef, static: true })
|
||||
bulkRemoveModalRef: ViewContainerRef;
|
||||
|
||||
userType = ProviderUserType;
|
||||
userStatusType = ProviderUserStatusType;
|
||||
providerId: string;
|
||||
accessEvents = false;
|
||||
|
||||
constructor(
|
||||
apiService: ApiService,
|
||||
private route: ActivatedRoute,
|
||||
i18nService: I18nService,
|
||||
modalService: ModalService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
cryptoService: CryptoService,
|
||||
private router: Router,
|
||||
searchService: SearchService,
|
||||
validationService: ValidationService,
|
||||
logService: LogService,
|
||||
searchPipe: SearchPipe,
|
||||
userNamePipe: UserNamePipe,
|
||||
stateService: StateService,
|
||||
private providerService: ProviderService
|
||||
) {
|
||||
super(
|
||||
apiService,
|
||||
searchService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
cryptoService,
|
||||
validationService,
|
||||
modalService,
|
||||
logService,
|
||||
searchPipe,
|
||||
userNamePipe,
|
||||
stateService
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.parent.params.subscribe(async (params) => {
|
||||
this.providerId = params.providerId;
|
||||
const provider = await this.providerService.get(this.providerId);
|
||||
|
||||
if (!provider.canManageUsers) {
|
||||
this.router.navigate(["../"], { relativeTo: this.route });
|
||||
return;
|
||||
}
|
||||
|
||||
this.accessEvents = provider.useEvents;
|
||||
|
||||
await this.load();
|
||||
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
this.searchText = qParams.search;
|
||||
if (qParams.viewEvents != null) {
|
||||
const user = this.users.filter((u) => u.id === qParams.viewEvents);
|
||||
if (user.length > 0 && user[0].status === ProviderUserStatusType.Confirmed) {
|
||||
this.events(user[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getUsers(): Promise<ListResponse<ProviderUserUserDetailsResponse>> {
|
||||
return this.apiService.getProviderUsers(this.providerId);
|
||||
}
|
||||
|
||||
deleteUser(id: string): Promise<any> {
|
||||
return this.apiService.deleteProviderUser(this.providerId, id);
|
||||
}
|
||||
|
||||
reinviteUser(id: string): Promise<any> {
|
||||
return this.apiService.postProviderUserReinvite(this.providerId, id);
|
||||
}
|
||||
|
||||
async confirmUser(user: ProviderUserUserDetailsResponse, publicKey: Uint8Array): Promise<any> {
|
||||
const providerKey = await this.cryptoService.getProviderKey(this.providerId);
|
||||
const key = await this.cryptoService.rsaEncrypt(providerKey.key, publicKey.buffer);
|
||||
const request = new ProviderUserConfirmRequest();
|
||||
request.key = key.encryptedString;
|
||||
await this.apiService.postProviderUserConfirm(this.providerId, user.id, request);
|
||||
}
|
||||
|
||||
async edit(user: ProviderUserUserDetailsResponse) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
UserAddEditComponent,
|
||||
this.addEditModalRef,
|
||||
(comp) => {
|
||||
comp.name = this.userNamePipe.transform(user);
|
||||
comp.providerId = this.providerId;
|
||||
comp.providerUserId = user != null ? user.id : null;
|
||||
comp.onSavedUser.subscribe(() => {
|
||||
modal.close();
|
||||
this.load();
|
||||
});
|
||||
comp.onDeletedUser.subscribe(() => {
|
||||
modal.close();
|
||||
this.removeUser(user);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async events(user: ProviderUserUserDetailsResponse) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
EntityEventsComponent,
|
||||
this.eventsModalRef,
|
||||
(comp) => {
|
||||
comp.name = this.userNamePipe.transform(user);
|
||||
comp.providerId = this.providerId;
|
||||
comp.entityId = user.id;
|
||||
comp.showUser = false;
|
||||
comp.entity = "user";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async bulkRemove() {
|
||||
if (this.actionPromise != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
BulkRemoveComponent,
|
||||
this.bulkRemoveModalRef,
|
||||
(comp) => {
|
||||
comp.providerId = this.providerId;
|
||||
comp.users = this.getCheckedUsers();
|
||||
}
|
||||
);
|
||||
|
||||
await modal.onClosedPromise();
|
||||
await this.load();
|
||||
}
|
||||
|
||||
async bulkReinvite() {
|
||||
if (this.actionPromise != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const users = this.getCheckedUsers();
|
||||
const filteredUsers = users.filter((u) => u.status === ProviderUserStatusType.Invited);
|
||||
|
||||
if (filteredUsers.length <= 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("noSelectedUsersApplicable")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const request = new ProviderUserBulkRequest(filteredUsers.map((user) => user.id));
|
||||
const response = this.apiService.postManyProviderUserReinvite(this.providerId, request);
|
||||
this.showBulkStatus(
|
||||
users,
|
||||
filteredUsers,
|
||||
response,
|
||||
this.i18nService.t("bulkReinviteMessage")
|
||||
);
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
}
|
||||
this.actionPromise = null;
|
||||
}
|
||||
|
||||
async bulkConfirm() {
|
||||
if (this.actionPromise != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
BulkConfirmComponent,
|
||||
this.bulkConfirmModalRef,
|
||||
(comp) => {
|
||||
comp.providerId = this.providerId;
|
||||
comp.users = this.getCheckedUsers();
|
||||
}
|
||||
);
|
||||
|
||||
await modal.onClosedPromise();
|
||||
await this.load();
|
||||
}
|
||||
|
||||
private async showBulkStatus(
|
||||
users: ProviderUserUserDetailsResponse[],
|
||||
filteredUsers: ProviderUserUserDetailsResponse[],
|
||||
request: Promise<ListResponse<ProviderUserBulkResponse>>,
|
||||
successfullMessage: string
|
||||
) {
|
||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||
BulkStatusComponent,
|
||||
this.bulkStatusModalRef,
|
||||
(comp) => {
|
||||
comp.loading = true;
|
||||
}
|
||||
);
|
||||
|
||||
// Workaround to handle closing the modal shortly after it has been opened
|
||||
let close = false;
|
||||
modal.onShown.subscribe(() => {
|
||||
if (close) {
|
||||
modal.close();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await request;
|
||||
|
||||
if (modal) {
|
||||
const keyedErrors: any = response.data
|
||||
.filter((r) => r.error !== "")
|
||||
.reduce((a, x) => ({ ...a, [x.id]: x.error }), {});
|
||||
const keyedFilteredUsers: any = filteredUsers.reduce((a, x) => ({ ...a, [x.id]: x }), {});
|
||||
|
||||
childComponent.users = users.map((user) => {
|
||||
let message = keyedErrors[user.id] ?? successfullMessage;
|
||||
if (!keyedFilteredUsers.hasOwnProperty(user.id)) {
|
||||
message = this.i18nService.t("bulkFilteredMessage");
|
||||
}
|
||||
|
||||
return {
|
||||
user: user,
|
||||
error: keyedErrors.hasOwnProperty(user.id),
|
||||
message: message,
|
||||
};
|
||||
});
|
||||
childComponent.loading = false;
|
||||
}
|
||||
} catch {
|
||||
close = true;
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="userAddEditTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
|
||||
<form
|
||||
class="modal-content"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="userAddEditTitle">
|
||||
{{ title }}
|
||||
<small class="text-muted" *ngIf="name">{{ name }}</small>
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="!loading">
|
||||
<ng-container *ngIf="!editMode">
|
||||
<p>{{ "providerInviteUserDesc" | i18n }}</p>
|
||||
<div class="form-group mb-4">
|
||||
<label for="emails">{{ "email" | i18n }}</label>
|
||||
<input
|
||||
id="emails"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Emails"
|
||||
[(ngModel)]="emails"
|
||||
required
|
||||
appAutoFocus
|
||||
/>
|
||||
<small class="text-muted">{{ "inviteMultipleEmailDesc" | i18n: "20" }}</small>
|
||||
</div>
|
||||
</ng-container>
|
||||
<h3>
|
||||
{{ "userType" | i18n }}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
href="https://bitwarden.com/help/article/user-types-access-control/#user-types"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</h3>
|
||||
<div class="form-check mt-2 form-check-block">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="userType"
|
||||
id="userTypeServiceUser"
|
||||
[value]="userType.ServiceUser"
|
||||
[(ngModel)]="type"
|
||||
/>
|
||||
<label class="form-check-label" for="userTypeServiceUser">
|
||||
{{ "serviceUser" | i18n }}
|
||||
<small>{{ "serviceUserDesc" | i18n }}</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mt-2 form-check-block">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="userType"
|
||||
id="userTypeProviderAdmin"
|
||||
[value]="userType.ProviderAdmin"
|
||||
[(ngModel)]="type"
|
||||
/>
|
||||
<label class="form-check-label" for="userTypeProviderAdmin">
|
||||
{{ "providerAdmin" | i18n }}
|
||||
<small>{{ "providerAdminDesc" | i18n }}</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
#deleteBtn
|
||||
type="button"
|
||||
(click)="delete()"
|
||||
class="btn btn-outline-danger"
|
||||
appA11yTitle="{{ 'delete' | i18n }}"
|
||||
*ngIf="editMode"
|
||||
[disabled]="deleteBtn.loading"
|
||||
[appApiAction]="deletePromise"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-trash bwi-lg bwi-fw"
|
||||
[hidden]="deleteBtn.loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
|
||||
[hidden]="!deleteBtn.loading"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,121 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
|
||||
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 { ProviderUserInviteRequest } from "jslib-common/models/request/provider/providerUserInviteRequest";
|
||||
|
||||
import { PermissionsApi } from "jslib-common/models/api/permissionsApi";
|
||||
|
||||
import { ProviderUserType } from "jslib-common/enums/providerUserType";
|
||||
import { ProviderUserUpdateRequest } from "jslib-common/models/request/provider/providerUserUpdateRequest";
|
||||
|
||||
@Component({
|
||||
selector: "provider-user-add-edit",
|
||||
templateUrl: "user-add-edit.component.html",
|
||||
})
|
||||
export class UserAddEditComponent implements OnInit {
|
||||
@Input() name: string;
|
||||
@Input() providerUserId: string;
|
||||
@Input() providerId: string;
|
||||
@Output() onSavedUser = new EventEmitter();
|
||||
@Output() onDeletedUser = new EventEmitter();
|
||||
|
||||
loading = true;
|
||||
editMode: boolean = false;
|
||||
title: string;
|
||||
emails: string;
|
||||
type: ProviderUserType = ProviderUserType.ServiceUser;
|
||||
permissions = new PermissionsApi();
|
||||
showCustom = false;
|
||||
access: "all" | "selected" = "selected";
|
||||
formPromise: Promise<any>;
|
||||
deletePromise: Promise<any>;
|
||||
userType = ProviderUserType;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.editMode = this.loading = this.providerUserId != null;
|
||||
|
||||
if (this.editMode) {
|
||||
this.editMode = true;
|
||||
this.title = this.i18nService.t("editUser");
|
||||
try {
|
||||
const user = await this.apiService.getProviderUser(this.providerId, this.providerUserId);
|
||||
this.type = user.type;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
} else {
|
||||
this.title = this.i18nService.t("inviteUser");
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
if (this.editMode) {
|
||||
const request = new ProviderUserUpdateRequest();
|
||||
request.type = this.type;
|
||||
this.formPromise = this.apiService.putProviderUser(
|
||||
this.providerId,
|
||||
this.providerUserId,
|
||||
request
|
||||
);
|
||||
} else {
|
||||
const request = new ProviderUserInviteRequest();
|
||||
request.emails = this.emails.trim().split(/\s*,\s*/);
|
||||
request.type = this.type;
|
||||
this.formPromise = this.apiService.postProviderUserInvite(this.providerId, request);
|
||||
}
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t(this.editMode ? "editedUserId" : "invitedUsers", this.name)
|
||||
);
|
||||
this.onSavedUser.emit();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async delete() {
|
||||
if (!this.editMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("removeUserConfirmation"),
|
||||
this.name,
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.deletePromise = this.apiService.deleteProviderUser(this.providerId, this.providerUserId);
|
||||
await this.deletePromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("removedUserId", this.name)
|
||||
);
|
||||
this.onDeletedUser.emit();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<app-navbar></app-navbar>
|
||||
<div class="org-nav" *ngIf="provider">
|
||||
<div class="container d-flex">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="my-auto d-flex align-items-center pl-1">
|
||||
<app-avatar [data]="provider.name" size="45" [circle]="true"></app-avatar>
|
||||
<div class="org-name ml-3">
|
||||
<span>{{ provider.name }}</span>
|
||||
<small class="text-muted">{{ "provider" | i18n }}</small>
|
||||
</div>
|
||||
<div class="ml-3 card border-danger text-danger bg-transparent" *ngIf="!provider.enabled">
|
||||
<div class="card-body py-2">
|
||||
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{ "providerIsDisabled" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="nav nav-tabs" *ngIf="showMenuBar">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="clients" routerLinkActive="active">
|
||||
<i class="bwi bwi-bank" aria-hidden="true"></i>
|
||||
{{ "clients" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="showManageTab">
|
||||
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
|
||||
<i class="bwi bwi-sliders" aria-hidden="true"></i>
|
||||
{{ "manage" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="showSettingsTab">
|
||||
<a class="nav-link" routerLink="settings" routerLinkActive="active">
|
||||
<i class="bwi bwi-cogs" aria-hidden="true"></i>
|
||||
{{ "settings" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container page-content">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<app-footer></app-footer>
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
|
||||
import { Provider } from "jslib-common/models/domain/provider";
|
||||
|
||||
@Component({
|
||||
selector: "providers-layout",
|
||||
templateUrl: "providers-layout.component.html",
|
||||
})
|
||||
export class ProvidersLayoutComponent {
|
||||
provider: Provider;
|
||||
private providerId: string;
|
||||
|
||||
constructor(private route: ActivatedRoute, private providerService: ProviderService) {}
|
||||
|
||||
ngOnInit() {
|
||||
document.body.classList.remove("layout_frontend");
|
||||
this.route.params.subscribe(async (params) => {
|
||||
this.providerId = params.providerId;
|
||||
await this.load();
|
||||
});
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.provider = await this.providerService.get(this.providerId);
|
||||
}
|
||||
|
||||
get showMenuBar() {
|
||||
return this.showManageTab || this.showSettingsTab;
|
||||
}
|
||||
|
||||
get showManageTab() {
|
||||
return this.provider.canManageUsers || this.provider.canAccessEventLogs;
|
||||
}
|
||||
|
||||
get showSettingsTab() {
|
||||
return this.provider.isProviderAdmin;
|
||||
}
|
||||
|
||||
get manageRoute(): string {
|
||||
switch (true) {
|
||||
case this.provider.canManageUsers:
|
||||
return "manage/people";
|
||||
case this.provider.canAccessEventLogs:
|
||||
return "manage/events";
|
||||
}
|
||||
}
|
||||
}
|
||||
123
bitwarden_license/src/app/providers/providers-routing.module.ts
Normal file
123
bitwarden_license/src/app/providers/providers-routing.module.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
|
||||
import { Permissions } from "jslib-common/enums/permissions";
|
||||
|
||||
import { AddOrganizationComponent } from "./clients/add-organization.component";
|
||||
import { ClientsComponent } from "./clients/clients.component";
|
||||
import { CreateOrganizationComponent } from "./clients/create-organization.component";
|
||||
import { AcceptProviderComponent } from "./manage/accept-provider.component";
|
||||
import { EventsComponent } from "./manage/events.component";
|
||||
import { ManageComponent } from "./manage/manage.component";
|
||||
import { PeopleComponent } from "./manage/people.component";
|
||||
import { ProvidersLayoutComponent } from "./providers-layout.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
import { SetupProviderComponent } from "./setup/setup-provider.component";
|
||||
import { SetupComponent } from "./setup/setup.component";
|
||||
|
||||
import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component";
|
||||
|
||||
import { ProvidersComponent } from "src/app/providers/providers.component";
|
||||
import { ProviderGuardService } from "./services/provider-guard.service";
|
||||
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
|
||||
import { AccountComponent } from "./settings/account.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
canActivate: [AuthGuardService],
|
||||
component: ProvidersComponent,
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
component: FrontendLayoutComponent,
|
||||
children: [
|
||||
{
|
||||
path: "setup-provider",
|
||||
component: SetupProviderComponent,
|
||||
data: { titleId: "setupProvider" },
|
||||
},
|
||||
{
|
||||
path: "accept-provider",
|
||||
component: AcceptProviderComponent,
|
||||
data: { titleId: "acceptProvider" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
canActivate: [AuthGuardService],
|
||||
children: [
|
||||
{
|
||||
path: "setup",
|
||||
component: SetupComponent,
|
||||
},
|
||||
{
|
||||
path: ":providerId",
|
||||
component: ProvidersLayoutComponent,
|
||||
canActivate: [ProviderGuardService],
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "clients" },
|
||||
{ path: "clients/create", component: CreateOrganizationComponent },
|
||||
{ path: "clients", component: ClientsComponent, data: { titleId: "clients" } },
|
||||
{
|
||||
path: "manage",
|
||||
component: ManageComponent,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
pathMatch: "full",
|
||||
redirectTo: "people",
|
||||
},
|
||||
{
|
||||
path: "people",
|
||||
component: PeopleComponent,
|
||||
canActivate: [ProviderTypeGuardService],
|
||||
data: {
|
||||
titleId: "people",
|
||||
permissions: [Permissions.ManageUsers],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "events",
|
||||
component: EventsComponent,
|
||||
canActivate: [ProviderTypeGuardService],
|
||||
data: {
|
||||
titleId: "eventLogs",
|
||||
permissions: [Permissions.AccessEventLogs],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "settings",
|
||||
component: SettingsComponent,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
pathMatch: "full",
|
||||
redirectTo: "account",
|
||||
},
|
||||
{
|
||||
path: "account",
|
||||
component: AccountComponent,
|
||||
canActivate: [ProviderTypeGuardService],
|
||||
data: {
|
||||
titleId: "myProvider",
|
||||
permissions: [Permissions.ManageProvider],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class ProvidersRoutingModule {}
|
||||
63
bitwarden_license/src/app/providers/providers.module.ts
Normal file
63
bitwarden_license/src/app/providers/providers.module.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ComponentFactoryResolver } from "@angular/core";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
|
||||
import { ProviderGuardService } from "./services/provider-guard.service";
|
||||
import { ProviderTypeGuardService } from "./services/provider-type-guard.service";
|
||||
import { WebProviderService } from "./services/webProvider.service";
|
||||
|
||||
import { ProvidersLayoutComponent } from "./providers-layout.component";
|
||||
import { ProvidersRoutingModule } from "./providers-routing.module";
|
||||
|
||||
import { AddOrganizationComponent } from "./clients/add-organization.component";
|
||||
import { ClientsComponent } from "./clients/clients.component";
|
||||
import { CreateOrganizationComponent } from "./clients/create-organization.component";
|
||||
|
||||
import { AcceptProviderComponent } from "./manage/accept-provider.component";
|
||||
import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component";
|
||||
import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component";
|
||||
import { EventsComponent } from "./manage/events.component";
|
||||
import { ManageComponent } from "./manage/manage.component";
|
||||
import { PeopleComponent } from "./manage/people.component";
|
||||
import { UserAddEditComponent } from "./manage/user-add-edit.component";
|
||||
|
||||
import { AccountComponent } from "./settings/account.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
|
||||
import { SetupProviderComponent } from "./setup/setup-provider.component";
|
||||
import { SetupComponent } from "./setup/setup.component";
|
||||
|
||||
import { OssModule } from "src/app/oss.module";
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, FormsModule, OssModule, ProvidersRoutingModule],
|
||||
declarations: [
|
||||
AcceptProviderComponent,
|
||||
AccountComponent,
|
||||
AddOrganizationComponent,
|
||||
BulkConfirmComponent,
|
||||
BulkRemoveComponent,
|
||||
ClientsComponent,
|
||||
CreateOrganizationComponent,
|
||||
EventsComponent,
|
||||
ManageComponent,
|
||||
PeopleComponent,
|
||||
ProvidersLayoutComponent,
|
||||
SettingsComponent,
|
||||
SetupComponent,
|
||||
SetupProviderComponent,
|
||||
UserAddEditComponent,
|
||||
],
|
||||
providers: [WebProviderService, ProviderGuardService, ProviderTypeGuardService],
|
||||
})
|
||||
export class ProvidersModule {
|
||||
constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) {
|
||||
modalService.registerComponentFactoryResolver(
|
||||
AddOrganizationComponent,
|
||||
componentFactoryResolver
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
|
||||
@Injectable()
|
||||
export class ProviderGuardService implements CanActivate {
|
||||
constructor(
|
||||
private router: Router,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private providerService: ProviderService
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot) {
|
||||
const provider = await this.providerService.get(route.params.providerId);
|
||||
if (provider == null) {
|
||||
this.router.navigate(["/"]);
|
||||
return false;
|
||||
}
|
||||
if (!provider.isProviderAdmin && !provider.enabled) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("providerIsDisabled"));
|
||||
this.router.navigate(["/"]);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
|
||||
|
||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
|
||||
import { Permissions } from "jslib-common/enums/permissions";
|
||||
|
||||
@Injectable()
|
||||
export class ProviderTypeGuardService implements CanActivate {
|
||||
constructor(private providerService: ProviderService, private router: Router) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot) {
|
||||
const provider = await this.providerService.get(route.params.providerId);
|
||||
const permissions = route.data == null ? null : (route.data.permissions as Permissions[]);
|
||||
|
||||
if (
|
||||
(permissions.indexOf(Permissions.AccessEventLogs) !== -1 && provider.canAccessEventLogs) ||
|
||||
(permissions.indexOf(Permissions.ManageProvider) !== -1 && provider.isProviderAdmin) ||
|
||||
(permissions.indexOf(Permissions.ManageUsers) !== -1 && provider.canManageUsers)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.router.navigate(["/providers", provider.id]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||
|
||||
import { ProviderAddOrganizationRequest } from "jslib-common/models/request/provider/providerAddOrganizationRequest";
|
||||
|
||||
@Injectable()
|
||||
export class WebProviderService {
|
||||
constructor(
|
||||
private cryptoService: CryptoService,
|
||||
private syncService: SyncService,
|
||||
private apiService: ApiService
|
||||
) {}
|
||||
|
||||
async addOrganizationToProvider(providerId: string, organizationId: string) {
|
||||
const orgKey = await this.cryptoService.getOrgKey(organizationId);
|
||||
const providerKey = await this.cryptoService.getProviderKey(providerId);
|
||||
|
||||
const encryptedOrgKey = await this.cryptoService.encrypt(orgKey.key, providerKey);
|
||||
|
||||
const request = new ProviderAddOrganizationRequest();
|
||||
request.organizationId = organizationId;
|
||||
request.key = encryptedOrgKey.encryptedString;
|
||||
|
||||
const response = await this.apiService.postProviderAddOrganization(providerId, request);
|
||||
await this.syncService.fullSync(true);
|
||||
return response;
|
||||
}
|
||||
|
||||
async detachOrganizastion(providerId: string, organizationId: string): Promise<any> {
|
||||
await this.apiService.deleteProviderOrganization(providerId, organizationId);
|
||||
await this.syncService.fullSync(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "myProvider" | i18n }}</h1>
|
||||
</div>
|
||||
<div *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<form
|
||||
*ngIf="provider && !loading"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="name">{{ "providerName" | i18n }}</label>
|
||||
<input
|
||||
id="name"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Name"
|
||||
[(ngModel)]="provider.name"
|
||||
[disabled]="selfHosted"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="billingEmail">{{ "billingEmail" | i18n }}</label>
|
||||
<input
|
||||
id="billingEmail"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="BillingEmail"
|
||||
[(ngModel)]="provider.billingEmail"
|
||||
[disabled]="selfHosted"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<app-avatar data="{{ provider.name }}" dynamic="true" size="75" fontSize="35"></app-avatar>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
</form>
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute } 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 { SyncService } from "jslib-common/abstractions/sync.service";
|
||||
|
||||
import { ProviderUpdateRequest } from "jslib-common/models/request/provider/providerUpdateRequest";
|
||||
|
||||
import { ProviderResponse } from "jslib-common/models/response/provider/providerResponse";
|
||||
|
||||
@Component({
|
||||
selector: "provider-account",
|
||||
templateUrl: "account.component.html",
|
||||
})
|
||||
export class AccountComponent {
|
||||
selfHosted = false;
|
||||
loading = true;
|
||||
provider: ProviderResponse;
|
||||
formPromise: Promise<any>;
|
||||
taxFormPromise: Promise<any>;
|
||||
|
||||
private providerId: string;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
private route: ActivatedRoute,
|
||||
private syncService: SyncService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.selfHosted = this.platformUtilsService.isSelfHost();
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.providerId = params.providerId;
|
||||
try {
|
||||
this.provider = await this.apiService.getProvider(this.providerId);
|
||||
} catch (e) {
|
||||
this.logService.error(`Handled exception: ${e}`);
|
||||
}
|
||||
});
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
const request = new ProviderUpdateRequest();
|
||||
request.name = this.provider.name;
|
||||
request.businessName = this.provider.businessName;
|
||||
request.billingEmail = this.provider.billingEmail;
|
||||
|
||||
this.formPromise = this.apiService.putProvider(this.providerId, request).then(() => {
|
||||
return this.syncService.fullSync(true);
|
||||
});
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("providerUpdated"));
|
||||
} catch (e) {
|
||||
this.logService.error(`Handled exception: ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ "settings" | i18n }}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="account" class="list-group-item" routerLinkActive="active">
|
||||
{{ "myProvider" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
|
||||
@Component({
|
||||
selector: "provider-settings",
|
||||
templateUrl: "settings.component.html",
|
||||
})
|
||||
export class SettingsComponent {
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private providerService: ProviderService,
|
||||
private platformUtilsService: PlatformUtilsService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.parent.params.subscribe(async (params) => {
|
||||
const provider = await this.providerService.get(params.providerId);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<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">{{ "setupProvider" | i18n }}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<p>{{ "setupProviderLoginDesc" | i18n }}</p>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
|
||||
{{ "logIn" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { BaseAcceptComponent } from "src/app/common/base.accept.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-setup-provider",
|
||||
templateUrl: "setup-provider.component.html",
|
||||
})
|
||||
export class SetupProviderComponent extends BaseAcceptComponent {
|
||||
failedShortMessage = "inviteAcceptFailedShort";
|
||||
failedMessage = "inviteAcceptFailed";
|
||||
|
||||
requiredParameters = ["providerId", "email", "token"];
|
||||
|
||||
async authedHandler(qParams: any) {
|
||||
this.router.navigate(["/providers/setup"], { queryParams: qParams });
|
||||
}
|
||||
|
||||
// tslint:disable-next-line
|
||||
async unauthedHandler(qParams: any) {}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<app-navbar></app-navbar>
|
||||
<div class="container page-content">
|
||||
<div class="page-header">
|
||||
<h1>{{ "setupProvider" | i18n }}</h1>
|
||||
</div>
|
||||
<p>{{ "setupProviderDesc" | i18n }}</p>
|
||||
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="loading">
|
||||
<h2 class="mt-5">{{ "generalInformation" | i18n }}</h2>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<label for="name">{{ "providerName" | i18n }}</label>
|
||||
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name" required />
|
||||
</div>
|
||||
<div class="form-group col-6">
|
||||
<label for="billingEmail">{{ "billingEmail" | i18n }}</label>
|
||||
<input
|
||||
id="billingEmail"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="BillingEmail"
|
||||
[(ngModel)]="billingEmail"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary 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" (click)="cancel()" *ngIf="showCancel">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<app-footer></app-footer>
|
||||
99
bitwarden_license/src/app/providers/setup/setup.component.ts
Normal file
99
bitwarden_license/src/app/providers/setup/setup.component.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
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 { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
|
||||
import { ValidationService } from "jslib-angular/services/validation.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||
import { ProviderSetupRequest } from "jslib-common/models/request/provider/providerSetupRequest";
|
||||
|
||||
@Component({
|
||||
selector: "provider-setup",
|
||||
templateUrl: "setup.component.html",
|
||||
})
|
||||
export class SetupComponent implements OnInit {
|
||||
loading = true;
|
||||
authed = false;
|
||||
email: string;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
providerId: string;
|
||||
token: string;
|
||||
name: string;
|
||||
billingEmail: string;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private route: ActivatedRoute,
|
||||
private cryptoService: CryptoService,
|
||||
private apiService: ApiService,
|
||||
private syncService: SyncService,
|
||||
private validationService: ValidationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
document.body.classList.remove("layout_frontend");
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
const error = qParams.providerId == null || qParams.email == null || qParams.token == null;
|
||||
|
||||
if (error) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
this.i18nService.t("emergencyInviteAcceptFailed"),
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
this.router.navigate(["/"]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.providerId = qParams.providerId;
|
||||
this.token = qParams.token;
|
||||
|
||||
// Check if provider exists, redirect if it does
|
||||
try {
|
||||
const provider = await this.apiService.getProvider(this.providerId);
|
||||
if (provider.name != null) {
|
||||
this.router.navigate(["/providers", provider.id], { replaceUrl: true });
|
||||
}
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
this.router.navigate(["/"]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async submit() {
|
||||
this.formPromise = this.doSubmit();
|
||||
await this.formPromise;
|
||||
this.formPromise = null;
|
||||
}
|
||||
|
||||
async doSubmit() {
|
||||
try {
|
||||
const shareKey = await this.cryptoService.makeShareKey();
|
||||
const key = shareKey[0].encryptedString;
|
||||
|
||||
const request = new ProviderSetupRequest();
|
||||
request.name = this.name;
|
||||
request.billingEmail = this.billingEmail;
|
||||
request.token = this.token;
|
||||
request.key = key;
|
||||
|
||||
const provider = await this.apiService.postProviderSetup(this.providerId, request);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("providerSetup"));
|
||||
await this.syncService.fullSync(true);
|
||||
|
||||
this.router.navigate(["/providers", provider.id]);
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
bitwarden_license/webpack.config.js
Normal file
12
bitwarden_license/webpack.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const { AngularWebpackPlugin } = require("@ngtools/webpack");
|
||||
|
||||
const webpackConfig = require("../webpack.config");
|
||||
|
||||
webpackConfig.entry["app/main"] = "./bitwarden_license/src/app/main.ts";
|
||||
webpackConfig.plugins[webpackConfig.plugins.length - 1] = new AngularWebpackPlugin({
|
||||
tsConfigPath: "tsconfig.json",
|
||||
entryModule: "bitwarden_license/src/app/app.module#AppModule",
|
||||
sourceMap: true,
|
||||
});
|
||||
|
||||
module.exports = webpackConfig;
|
||||
33
build.sh
33
build.sh
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
echo ""
|
||||
|
||||
if [ $# -gt 1 -a "$1" == "push" ]
|
||||
then
|
||||
TAG=$2
|
||||
echo "# Pushing Web ($TAG)"
|
||||
echo ""
|
||||
docker push bitwarden/web:$TAG
|
||||
elif [ $# -gt 1 -a "$1" == "tag" ]
|
||||
then
|
||||
TAG=$2
|
||||
echo "Tagging Web as '$TAG'"
|
||||
docker tag bitwarden/web bitwarden/web:$TAG
|
||||
else
|
||||
echo "# Building Web"
|
||||
|
||||
echo ""
|
||||
echo "Building app"
|
||||
echo "npm version $(npm --version)"
|
||||
npm install
|
||||
npm run sub:update
|
||||
npm run dist:selfhost
|
||||
|
||||
echo ""
|
||||
echo "Building docker image"
|
||||
docker --version
|
||||
docker build -t bitwarden/web $DIR/.
|
||||
fi
|
||||
36
config.js
Normal file
36
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,
|
||||
};
|
||||
12
config/base.json
Normal file
12
config/base.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"urls": {},
|
||||
"stripeKey": "pk_test_KPoCfZXu7mznb9uSCPZ2JpTD",
|
||||
"braintreeKey": "sandbox_r72q8jq6_9pnxkwm75f87sdc2",
|
||||
"paypal": {
|
||||
"businessId": "AD3LAUZSNVPJY",
|
||||
"buttonAction": "https://www.sandbox.paypal.com/cgi-bin/webscr"
|
||||
},
|
||||
"dev": {
|
||||
"allowedHosts": "auto"
|
||||
}
|
||||
}
|
||||
17
config/cloud.json
Normal file
17
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
config/development.json
Normal file
11
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
config/qa.json
Normal file
11
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"
|
||||
}
|
||||
}
|
||||
1
config/selfhosted.json
Normal file
1
config/selfhosted.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -1,5 +1,9 @@
|
||||
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:
|
||||
@@ -9,3 +13,5 @@ files:
|
||||
zh-CN: zh_CN
|
||||
zh-TW: zh_TW
|
||||
en-GB: en_GB
|
||||
en-IN: en_IN
|
||||
sr-CY: sr_CY
|
||||
|
||||
@@ -32,7 +32,6 @@ mkhomedir_helper $USERNAME
|
||||
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
|
||||
#chown -R $USERNAME:$GROUPNAME /bitwarden_server
|
||||
|
||||
exec gosu $USERNAME:$GROUPNAME dotnet /bitwarden_server/Server.dll \
|
||||
/contentRoot=/app /webRoot=. /serveUnknown=false /webVault=true
|
||||
#exec nginx -g 'daemon off;'
|
||||
138
docker/mime.types
Normal file
138
docker/mime.types
Normal file
@@ -0,0 +1,138 @@
|
||||
types {
|
||||
|
||||
# Data interchange
|
||||
|
||||
application/atom+xml atom;
|
||||
application/json json map topojson;
|
||||
application/ld+json jsonld;
|
||||
application/rss+xml rss;
|
||||
application/vnd.geo+json geojson;
|
||||
application/xml rdf xml;
|
||||
|
||||
|
||||
# JavaScript
|
||||
|
||||
# Normalize to standard type.
|
||||
# https://tools.ietf.org/html/rfc4329#section-7.2
|
||||
application/javascript js;
|
||||
|
||||
|
||||
# Manifest files
|
||||
|
||||
application/manifest+json webmanifest;
|
||||
application/x-web-app-manifest+json webapp;
|
||||
text/cache-manifest appcache;
|
||||
|
||||
|
||||
# Media files
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mp4 aac f4a f4b m4a;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg oga ogg opus;
|
||||
audio/x-realaudio ra;
|
||||
audio/x-wav wav;
|
||||
image/bmp bmp;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
image/jxr jxr hdp wdp;
|
||||
image/png png;
|
||||
image/svg+xml svg svgz;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/webp webp;
|
||||
image/x-jng jng;
|
||||
video/3gpp 3gp 3gpp;
|
||||
video/mp4 f4p f4v m4v mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/ogg ogv;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asf asx;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
|
||||
# Serving `.ico` image files with a different media type
|
||||
# prevents Internet Explorer from displaying then as images:
|
||||
# https://github.com/h5bp/html5-boilerplate/commit/37b5fec090d00f38de64b591bcddcb205aadf8ee
|
||||
|
||||
image/x-icon cur ico;
|
||||
|
||||
|
||||
# Microsoft Office
|
||||
|
||||
application/msword doc;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
|
||||
|
||||
|
||||
# Web fonts
|
||||
|
||||
application/font-woff woff;
|
||||
application/font-woff2 woff2;
|
||||
application/vnd.ms-fontobject eot;
|
||||
|
||||
# Browsers usually ignore the font media types and simply sniff
|
||||
# the bytes to figure out the font type.
|
||||
# https://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern
|
||||
#
|
||||
# However, Blink and WebKit based browsers will show a warning
|
||||
# in the console if the following font types are served with any
|
||||
# other media types.
|
||||
|
||||
application/x-font-ttf ttc ttf;
|
||||
font/opentype otf;
|
||||
|
||||
|
||||
# Other
|
||||
|
||||
application/java-archive ear jar war;
|
||||
application/mac-binhex40 hqx;
|
||||
application/octet-stream bin deb dll dmg exe img iso msi msm msp safariextz;
|
||||
application/pdf pdf;
|
||||
application/postscript ai eps ps;
|
||||
application/rtf rtf;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-bb-appworld bbaw;
|
||||
application/x-bittorrent torrent;
|
||||
application/x-chrome-extension crx;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-opera-extension oex;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot pdb prc;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert crt der pem;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xslt+xml xsl;
|
||||
application/zip zip;
|
||||
text/css css;
|
||||
text/csv csv;
|
||||
text/html htm html shtml;
|
||||
text/markdown md;
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vcard vcard vcf;
|
||||
text/vnd.rim.location.xloc xloc;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/vtt vtt;
|
||||
text/x-component htc;
|
||||
|
||||
}
|
||||
25
docker/nginx-web.conf
Normal file
25
docker/nginx-web.conf
Normal file
@@ -0,0 +1,25 @@
|
||||
#######################################################################
|
||||
# WARNING: This file is generated. Do not make changes to this file. #
|
||||
# They will be overwritten on update. You can manage various settings #
|
||||
# used in this file from the ./bwdata/config.yml file for your #
|
||||
# installation. #
|
||||
#######################################################################
|
||||
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
listen [::]:8080 default_server;
|
||||
include /etc/nginx/security-headers.conf;
|
||||
|
||||
location / {
|
||||
root /app;
|
||||
index index.html index.htm;
|
||||
include /etc/nginx/security-headers.conf;
|
||||
add_header X-Frame-Options SAMEORIGIN;
|
||||
add_header X-Robots-Tag "noindex, nofollow";
|
||||
}
|
||||
|
||||
location /alive {
|
||||
return 200 'alive';
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
147
docker/nginx.conf
Normal file
147
docker/nginx.conf
Normal file
@@ -0,0 +1,147 @@
|
||||
# nginx Configuration File
|
||||
# http://wiki.nginx.org/Configuration
|
||||
|
||||
# Run as a less privileged user for security reasons.
|
||||
# user www www;
|
||||
|
||||
# How many worker threads to run;
|
||||
# "auto" sets it to the number of CPU cores available in the system, and
|
||||
# offers the best performance. Don't set it higher than the number of CPU
|
||||
# cores if changing this parameter.
|
||||
|
||||
# The maximum number of connections for Nginx is calculated by:
|
||||
# max_clients = worker_processes * worker_connections
|
||||
worker_processes auto;
|
||||
|
||||
# Maximum open file descriptors per process;
|
||||
# should be > worker_connections.
|
||||
worker_rlimit_nofile 8192;
|
||||
|
||||
events {
|
||||
# When you need > 8000 * cpu_cores connections, you start optimizing your OS,
|
||||
# and this is probably the point at which you hire people who are smarter than
|
||||
# you, as this is *a lot* of requests.
|
||||
worker_connections 8000;
|
||||
}
|
||||
|
||||
# Default error log file
|
||||
# (this is only used when you don't override error_log on a server{} level)
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
http {
|
||||
# Hide nginx version information.
|
||||
server_tokens off;
|
||||
|
||||
# Define the MIME types for files.
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Update charset_types to match updated mime.types.
|
||||
# text/html is always included by charset module.
|
||||
# Default: text/html text/xml text/plain text/vnd.wap.wml application/javascript application/rss+xml
|
||||
charset_types
|
||||
text/css
|
||||
text/plain
|
||||
text/vnd.wap.wml
|
||||
application/javascript
|
||||
application/json
|
||||
application/rss+xml
|
||||
application/xml;
|
||||
|
||||
# Format to use in log files
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
# Default log file
|
||||
# (this is only used when you don't override access_log on a server{} level)
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
# How long to allow each connection to stay idle; longer values are better
|
||||
# for each individual client, particularly for SSL, but means that worker
|
||||
# connections are tied up longer. (Default: 65)
|
||||
keepalive_timeout 20;
|
||||
|
||||
# Speed up file transfers by using sendfile() to copy directly
|
||||
# between descriptors rather than using read()/write().
|
||||
# For performance reasons, on FreeBSD systems w/ ZFS
|
||||
# this option should be disabled as ZFS's ARC caches
|
||||
# frequently used files in RAM by default.
|
||||
sendfile on;
|
||||
|
||||
# Tell Nginx not to send out partial frames; this increases throughput
|
||||
# since TCP frames are filled up before being sent out. (adds TCP_CORK)
|
||||
tcp_nopush on;
|
||||
|
||||
|
||||
# Compression
|
||||
|
||||
# Enable Gzip compressed.
|
||||
gzip on;
|
||||
|
||||
# Compression level (1-9).
|
||||
# 5 is a perfect compromise between size and cpu usage, offering about
|
||||
# 75% reduction for most ascii files (almost identical to level 9).
|
||||
gzip_comp_level 5;
|
||||
|
||||
# Don't compress anything that's already small and unlikely to shrink much
|
||||
# if at all (the default is 20 bytes, which is bad as that usually leads to
|
||||
# larger files after gzipping).
|
||||
gzip_min_length 256;
|
||||
|
||||
# Compress data even for clients that are connecting to us via proxies,
|
||||
# identified by the "Via" header (required for CloudFront).
|
||||
gzip_proxied any;
|
||||
|
||||
# Tell proxies to cache both the gzipped and regular version of a resource
|
||||
# whenever the client's Accept-Encoding capabilities header varies;
|
||||
# Avoids the issue where a non-gzip capable client (which is extremely rare
|
||||
# today) would display gibberish if their proxy gave them the gzipped version.
|
||||
gzip_vary on;
|
||||
|
||||
# Compress all output labeled with one of the following MIME-types.
|
||||
gzip_types
|
||||
application/atom+xml
|
||||
application/javascript
|
||||
application/json
|
||||
application/ld+json
|
||||
application/manifest+json
|
||||
application/rss+xml
|
||||
application/vnd.geo+json
|
||||
application/vnd.ms-fontobject
|
||||
application/x-font-ttf
|
||||
application/x-web-app-manifest+json
|
||||
application/xhtml+xml
|
||||
application/xml
|
||||
font/opentype
|
||||
image/bmp
|
||||
image/svg+xml
|
||||
image/x-icon
|
||||
text/cache-manifest
|
||||
text/css
|
||||
text/plain
|
||||
text/vcard
|
||||
text/vnd.rim.location.xloc
|
||||
text/vtt
|
||||
text/x-component
|
||||
text/x-cross-domain-policy;
|
||||
# text/html is always compressed by HttpGzipModule
|
||||
|
||||
# This should be turned on if you are going to have pre-compressed copies (.gz) of
|
||||
# static files available. If not it should be left off as it will cause extra I/O
|
||||
# for the check. It is best if you enable this in a location{} block for
|
||||
# a specific directory, or on an individual server{} level.
|
||||
# gzip_static on;
|
||||
|
||||
# Content type for FIDO U2F facets
|
||||
map $uri $fido_content_type {
|
||||
default "application/fido.trusted-apps+json";
|
||||
}
|
||||
|
||||
# Include files in the sites-enabled folder. server{} configuration files should be
|
||||
# placed in the sites-available folder, and then the configuration should be enabled
|
||||
# by creating a symlink to it in the sites-enabled folder.
|
||||
# See doc/sites-enabled.md for more info.
|
||||
include conf.d/*.conf;
|
||||
}
|
||||
3
docker/security-headers.conf
Normal file
3
docker/security-headers.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
add_header Referrer-Policy same-origin;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
37
gulpfile.js
37
gulpfile.js
@@ -1,37 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
const googleWebFonts = require('gulp-google-webfonts');
|
||||
const del = require('del');
|
||||
const package = require('./package.json');
|
||||
const fs = require('fs');
|
||||
|
||||
const paths = {
|
||||
node_modules: './node_modules/',
|
||||
src: './src/',
|
||||
build: './build/',
|
||||
cssDir: './src/css/',
|
||||
};
|
||||
|
||||
function clean() {
|
||||
return del([paths.cssDir]);
|
||||
}
|
||||
|
||||
function webfonts() {
|
||||
return gulp.src('./webfonts.list')
|
||||
.pipe(googleWebFonts({
|
||||
fontsDir: 'webfonts',
|
||||
cssFilename: 'webfonts.css',
|
||||
format: 'woff',
|
||||
}))
|
||||
.pipe(gulp.dest(paths.cssDir));
|
||||
};
|
||||
|
||||
function version(cb) {
|
||||
fs.writeFileSync(paths.build + 'version.json', '{"version":"' + package.version + '"}');
|
||||
cb();
|
||||
}
|
||||
|
||||
exports.clean = clean;
|
||||
exports.webfonts = gulp.series(clean, webfonts);
|
||||
exports.prebuild = gulp.series(clean, webfonts);
|
||||
exports.version = version;
|
||||
exports.postdist = version;
|
||||
2
jslib
2
jslib
Submodule jslib updated: 2858724f44...e372bf242b
24216
package-lock.json
generated
24216
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
166
package.json
166
package.json
@@ -1,101 +1,107 @@
|
||||
{
|
||||
"name": "bitwarden-web",
|
||||
"version": "2.14.0",
|
||||
"name": "@bitwarden/web-vault",
|
||||
"version": "2.25.1",
|
||||
"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",
|
||||
"postinstall": "npm run sub:init",
|
||||
"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": "gulp prebuild && webpack",
|
||||
"build:watch": "gulp prebuild && webpack-dev-server",
|
||||
"build:prod": "gulp prebuild && cross-env NODE_ENV=production webpack",
|
||||
"build:prod:watch": "gulp prebuild && cross-env NODE_ENV=production webpack-dev-server",
|
||||
"build:selfhost": "gulp prebuild && cross-env SELF_HOST=true webpack-dev-server",
|
||||
"build:selfhost:watch": "gulp prebuild && cross-env SELF_HOST=true webpack-dev-server",
|
||||
"build:selfhost:prod": "gulp prebuild && cross-env SELF_HOST=true NODE_ENV=production webpack",
|
||||
"build:selfhost:prod:watch": "gulp prebuild && cross-env SELF_HOST=true NODE_ENV=production webpack-dev-server",
|
||||
"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": "npm run build:prod && gulp postdist",
|
||||
"dist:selfhost": "npm run build:selfhost:prod && gulp postdist",
|
||||
"deploy": "npm run dist && gh-pages -d build",
|
||||
"deploy:dev": "npm run dist && gh-pages -d build -r git@github.com:kspearrin/bitwarden-web-dev.git",
|
||||
"lint": "tslint src/**/*.ts || true",
|
||||
"lint:fix": "tslint src/**/*.ts --fix"
|
||||
"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": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts' && prettier --check .",
|
||||
"lint:fix": "tslint 'src/**/*.ts' 'bitwarden_license/src/**/*.ts' --fix",
|
||||
"prettier": "prettier --write .",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/compiler-cli": "^7.2.11",
|
||||
"@ngtools/webpack": "^7.2.2",
|
||||
"@types/jquery": "^3.3.6",
|
||||
"@types/lunr": "^2.3.3",
|
||||
"@types/node-forge": "^0.7.5",
|
||||
"@types/papaparse": "^4.5.3",
|
||||
"@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": "^4.4.11",
|
||||
"@types/zxcvbn": "^4.4.0",
|
||||
"angular2-template-loader": "^0.6.2",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"copy-webpack-plugin": "^4.5.2",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^1.0.0",
|
||||
"del": "^3.0.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"file-loader": "^2.0.0",
|
||||
"gh-pages": "^1.2.0",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-google-webfonts": "^2.0.0",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.0",
|
||||
"terser-webpack-plugin": "^1.2.3",
|
||||
"ts-loader": "^5.3.3",
|
||||
"tslint": "^5.12.1",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"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",
|
||||
"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",
|
||||
"prettier": "2.5.1",
|
||||
"process": "^0.11.10",
|
||||
"sass": "^1.32.10",
|
||||
"sass-loader": "^12.4.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"terser-webpack-plugin": "^5.2.5",
|
||||
"ts-loader": "^9.2.5",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-loader": "^3.5.4",
|
||||
"typescript": "3.2.4",
|
||||
"webpack": "^4.29.0",
|
||||
"webpack-cli": "^3.2.1",
|
||||
"webpack-dev-server": "^3.1.14"
|
||||
"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": "7.2.1",
|
||||
"@angular/cdk": "7.2.1",
|
||||
"@angular/common": "7.2.1",
|
||||
"@angular/compiler": "7.2.1",
|
||||
"@angular/core": "7.2.1",
|
||||
"@angular/forms": "7.2.1",
|
||||
"@angular/platform-browser": "7.2.1",
|
||||
"@angular/platform-browser-dynamic": "7.2.1",
|
||||
"@angular/router": "7.2.1",
|
||||
"@angular/upgrade": "7.2.1",
|
||||
"@microsoft/signalr": "3.1.0",
|
||||
"@microsoft/signalr-protocol-msgpack": "3.1.0",
|
||||
"angular2-toaster": "6.1.0",
|
||||
"angulartics2": "6.3.0",
|
||||
"big-integer": "1.6.36",
|
||||
"bootstrap": "4.3.1",
|
||||
"braintree-web-drop-in": "1.13.0",
|
||||
"core-js": "2.6.2",
|
||||
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
|
||||
"@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.30.1",
|
||||
"browser-hrtime": "^1.1.8",
|
||||
"core-js": "^3.11.0",
|
||||
"date-input-polyfill": "^2.14.0",
|
||||
"font-awesome": "4.7.0",
|
||||
"jquery": "3.4.1",
|
||||
"lunr": "2.3.3",
|
||||
"ngx-infinite-scroll": "7.0.1",
|
||||
"node-forge": "0.7.6",
|
||||
"papaparse": "4.6.0",
|
||||
"popper.js": "1.14.4",
|
||||
"jquery": "3.6.0",
|
||||
"ngx-infinite-scroll": "^10.0.1",
|
||||
"ngx-toastr": "14.1.4",
|
||||
"popper.js": "1.16.1",
|
||||
"qrious": "4.0.2",
|
||||
"rxjs": "6.3.3",
|
||||
"sweetalert2": "9.8.1",
|
||||
"web-animations-js": "2.3.1",
|
||||
"webcrypto-shim": "0.1.4",
|
||||
"whatwg-fetch": "3.0.0",
|
||||
"zone.js": "0.8.28",
|
||||
"zxcvbn": "4.4.2"
|
||||
"rxjs": "^7.4.0",
|
||||
"sweetalert2": "^10.16.6",
|
||||
"webcrypto-shim": "0.1.7",
|
||||
"whatwg-fetch": "3.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "~16",
|
||||
"npm": "~8"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": "prettier --ignore-unknown --write",
|
||||
"*.png": "node scripts/optimize.js"
|
||||
}
|
||||
}
|
||||
|
||||
21
scripts/optimize.js
Normal file
21
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;
|
||||
}
|
||||
});
|
||||
52
src/404.html
Normal file
52
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
src/404/bootstrap.min.css
vendored
Normal file
7
src/404/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
151
src/404/styles.css
Normal file
151
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";
|
||||
}
|
||||
41
src/app/accounts/accept-emergency.component.html
Normal file
41
src/app/accounts/accept-emergency.component.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<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="/" [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>
|
||||
54
src/app/accounts/accept-emergency.component.ts
Normal file
54
src/app/accounts/accept-emergency.component.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
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, " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,42 @@
|
||||
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
|
||||
<div>
|
||||
<img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
|
||||
<p class="text-center">
|
||||
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</p>
|
||||
</div>
|
||||
<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="/" [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 class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="lead text-center mb-4">{{ "joinOrganization" | i18n }}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<p class="text-center">
|
||||
{{ orgName }}
|
||||
<strong class="d-block mt-2">{{ email }}</strong>
|
||||
</p>
|
||||
<p>{{ "joinOrganizationDesc" | i18n }}</p>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
|
||||
{{ "logIn" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="/register"
|
||||
[queryParams]="{ email: email }"
|
||||
class="btn btn-primary btn-block ml-2 mt-0"
|
||||
>
|
||||
{{ "createAccount" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,95 +1,127 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import {
|
||||
Toast,
|
||||
ToasterService,
|
||||
} from 'angular2-toaster';
|
||||
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 { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { StateService } from 'jslib/abstractions/state.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
import { OrganizationUserAcceptRequest } from "jslib-common/models/request/organizationUserAcceptRequest";
|
||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
|
||||
|
||||
import { OrganizationUserAcceptRequest } from 'jslib/models/request/organizationUserAcceptRequest';
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
import { Policy } from "jslib-common/models/domain/policy";
|
||||
import { BaseAcceptComponent } from "../common/base.accept.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-accept-organization',
|
||||
templateUrl: 'accept-organization.component.html',
|
||||
selector: "app-accept-organization",
|
||||
templateUrl: "accept-organization.component.html",
|
||||
})
|
||||
export class AcceptOrganizationComponent implements OnInit {
|
||||
loading = true;
|
||||
authed = false;
|
||||
orgName: string;
|
||||
email: string;
|
||||
actionPromise: Promise<any>;
|
||||
export class AcceptOrganizationComponent extends BaseAcceptComponent {
|
||||
orgName: string;
|
||||
|
||||
constructor(private router: Router, private toasterService: ToasterService,
|
||||
private i18nService: I18nService, private route: ActivatedRoute,
|
||||
private apiService: ApiService, private userService: UserService,
|
||||
private stateService: StateService) { }
|
||||
protected requiredParameters: string[] = ["organizationId", "organizationUserId", "token"];
|
||||
|
||||
ngOnInit() {
|
||||
let fired = false;
|
||||
this.route.queryParams.subscribe(async (qParams) => {
|
||||
if (fired) {
|
||||
return;
|
||||
}
|
||||
fired = true;
|
||||
await this.stateService.remove('orgInvitation');
|
||||
let error = qParams.organizationId == null || qParams.organizationUserId == null || qParams.token == null;
|
||||
let errorMessage: string = null;
|
||||
if (!error) {
|
||||
this.authed = await this.userService.isAuthenticated();
|
||||
if (this.authed) {
|
||||
const request = new OrganizationUserAcceptRequest();
|
||||
request.token = qParams.token;
|
||||
try {
|
||||
this.actionPromise = this.apiService.postOrganizationUserAccept(qParams.organizationId,
|
||||
qParams.organizationUserId, request);
|
||||
await this.actionPromise;
|
||||
const toast: Toast = {
|
||||
type: 'success',
|
||||
title: this.i18nService.t('inviteAccepted'),
|
||||
body: this.i18nService.t('inviteAcceptedDesc'),
|
||||
timeout: 10000,
|
||||
};
|
||||
this.toasterService.popAsync(toast);
|
||||
this.router.navigate(['/vault']);
|
||||
} catch (e) {
|
||||
error = true;
|
||||
errorMessage = e.message;
|
||||
}
|
||||
} else {
|
||||
await this.stateService.save('orgInvitation', qParams);
|
||||
this.email = qParams.email;
|
||||
this.orgName = qParams.organizationName;
|
||||
if (this.orgName != null) {
|
||||
// Fix URL encoding of space issue with Angular
|
||||
this.orgName = this.orgName.replace(/\+/g, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
const toast: Toast = {
|
||||
type: 'error',
|
||||
title: null,
|
||||
body: errorMessage != null ? this.i18nService.t('inviteAcceptFailedShort', errorMessage) :
|
||||
this.i18nService.t('inviteAcceptFailed'),
|
||||
timeout: 10000,
|
||||
};
|
||||
this.toasterService.popAsync(toast);
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
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"));
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +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="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{'cancel' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{ "cancel" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Component } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
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 { HintComponent as BaseHintComponent } from 'jslib/angular/components/hint.component';
|
||||
import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-hint',
|
||||
templateUrl: 'hint.component.html',
|
||||
selector: "app-hint",
|
||||
templateUrl: "hint.component.html",
|
||||
})
|
||||
export class HintComponent extends BaseHintComponent {
|
||||
constructor(router: Router, i18nService: I18nService,
|
||||
apiService: ApiService, platformUtilsService: PlatformUtilsService) {
|
||||
super(router, i18nService, apiService, platformUtilsService);
|
||||
}
|
||||
constructor(
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
apiService: ApiService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
logService: LogService
|
||||
) {
|
||||
super(router, i18nService, apiService, platformUtilsService, logService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,66 @@
|
||||
<form (ngSubmit)="submit()" class="container" ngNativeValidate>
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="text-center mb-4">
|
||||
<i class="fa fa-lock fa-4x text-muted" 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="fa fa-lg" aria-hidden="true"
|
||||
[ngClass]="{'fa-eye': !showPassword, 'fa-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">
|
||||
<i class="fa fa-unlock-alt" aria-hidden="true"></i>
|
||||
{{'unlock' | i18n}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-block ml-2 mt-0" (click)="logOut()">
|
||||
{{'logOut' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user