mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-05 04:29:09 +00:00
Compare commits
1282 Commits
fix/resolv
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
c6fa647825 | |||
496cebb08f | |||
33db6df7f2 | |||
88d25e97e9 | |||
4ad9fa1ad1 | |||
ea426e8b2d | |||
4d567f0b08 | |||
6548372e3b | |||
77af640c4c | |||
90f85152bc | |||
cfa8770bdc | |||
be8562824d | |||
7503876ca0 | |||
36b5a3dc90 | |||
dfe36f346f | |||
b1b61842c6 | |||
f9ca9b51b2 | |||
7e7e6ade5c | |||
4010817916 | |||
eea367c3bc | |||
860ebb73a9 | |||
56567ee7c9 | |||
1cd17a451c | |||
6b7bc2a3c4 | |||
cb52568ebd | |||
9d30fb3870 | |||
161ac5e097 | |||
bb5b585cf6 | |||
fa94191c40 | |||
6a5eabc411 | |||
c956a0f91f | |||
df7b55606e | |||
5f14b27f41 | |||
02b2395276 | |||
402fa2b0e0 | |||
3725241f52 | |||
10b457a695 | |||
3912e2082d | |||
7dd6eac20a | |||
5664e1ff26 | |||
a27a428329 | |||
b196251c19 | |||
b18d8d542f | |||
3c287600ab | |||
759d11ff21 | |||
2bd817765c | |||
7aa9c5dd00 | |||
b693c035ce | |||
c65a991943 | |||
3a3811cb3c | |||
332ca61f5d | |||
64f43e59d0 | |||
ccaf4c00af | |||
e3ba1c59bf | |||
ce0bc191d8 | |||
489ccb8e15 | |||
ae8f695b6f | |||
19357d4bd7 | |||
776d0a0fe1 | |||
85dec28667 | |||
21ea7dd317 | |||
57e214ef50 | |||
1986fe9617 | |||
1309f30af9 | |||
89a4fc91ca | |||
af0ec2400d | |||
770e73e40b | |||
39fdeabdea | |||
25c26f2cde | |||
1ca8b9ba08 | |||
14d9fe01e0 | |||
216810f289 | |||
f530b78eb8 | |||
c3809ed22b | |||
9f85d8bba1 | |||
1056645ee3 | |||
5e9914b738 | |||
1ea52e6a80 | |||
20da697de8 | |||
16abf48081 | |||
e73ae485bc | |||
621f73e223 | |||
93e69bd34e | |||
e382135384 | |||
f2a554b5fd | |||
df5bdf3773 | |||
8401048daf | |||
335a87d856 | |||
1add9dd965 | |||
df46daf93d | |||
f82f7ae8d0 | |||
8536a1c987 | |||
b3cf43b46d | |||
9d4dbb63ae | |||
9c6f23fba6 | |||
babe483ca9 | |||
38ede687cd | |||
5f465c4832 | |||
a0618086b0 | |||
9a9bb4ca43 | |||
b68ddfae1b | |||
7646670378 | |||
d18be0f74c | |||
ec96db3503 | |||
7245aaa9ec | |||
d32f69e052 | |||
726477e3d7 | |||
a4ca996a1b | |||
303312fe91 | |||
f3f2879d6d | |||
d0f3d96b3e | |||
70d2a21fbc | |||
418ae42d94 | |||
273c6b3842 | |||
6be8d5d2a7 | |||
9eb7640755 | |||
741138c4bd | |||
bed620aad0 | |||
2ddf75d2e6 | |||
02d9dbb987 | |||
0ed333c2b2 | |||
55db45cd36 | |||
2d82273158 | |||
b3e61f579d | |||
d0bcbe15c6 | |||
657130eb80 | |||
3841394eb7 | |||
b1ba770a71 | |||
3552119c7d | |||
7a46725523 | |||
0515c994c7 | |||
e0d0e22e39 | |||
2f79ae42ab | |||
3bc39c6cec | |||
b5b1e57fe7 | |||
1a5f66fe46 | |||
a01f235808 | |||
b9a1629db0 | |||
203422c131 | |||
35826c288e | |||
fae4e1fa55 | |||
8094ef607a | |||
104bff0586 | |||
0fb5fa0c8b | |||
f407022e16 | |||
34d6525418 | |||
911479baff | |||
05bdbbf59d | |||
c8e47771d4 | |||
e0cbcb0318 | |||
f8d65f44e3 | |||
58ce623a2c | |||
7ae28596ec | |||
833398ef39 | |||
4e6ebcc8d9 | |||
ce8689f568 | |||
e9ab19b7f9 | |||
f2b852a09e | |||
a1c2bc695c | |||
00573ebfda | |||
3b2b8ca013 | |||
2afc6b133e | |||
b6a1ab2376 | |||
d03f890471 | |||
5ef81cd935 | |||
3e8f1d8de7 | |||
558a809b4c | |||
a749e70815 | |||
6f44f3ae21 | |||
b062ca3075 | |||
a1397f0a66 | |||
91c11d61f1 | |||
93218d5a3f | |||
5f2144eca5 | |||
45b9de63f0 | |||
114966ded4 | |||
71081d8e9a | |||
dad3d50f3e | |||
e5ca5d3da2 | |||
301cd54dc3 | |||
593bda8bc6 | |||
4db79edf19 | |||
e3a356cda9 | |||
521b24debf | |||
d6ffd4fa5f | |||
ca3b64bf6c | |||
b7e48fd556 | |||
c01ea048ce | |||
7e7d9a2bd5 | |||
782e3a8985 | |||
1c32dd5d8a | |||
8497ac831f | |||
e5821122d5 | |||
c183ef2b4f | |||
340693cfcd | |||
014b9585e0 | |||
67373b0883 | |||
2101040a77 | |||
2e2fea304b | |||
571709370d | |||
e1dbe769a8 | |||
e7e0d84c8e | |||
4c2ed1cc8b | |||
067b0f4232 | |||
6ed786e6d0 | |||
d187cc3d4d | |||
764446a2d9 | |||
614e4934a2 | |||
14e92f895a | |||
0a38374a73 | |||
ec3b94a335 | |||
ca0241bb51 | |||
7403385e7c | |||
b6955d0e9b | |||
f4ba441ec3 | |||
2cd1141a65 | |||
256627b2cc | |||
fd7e196f8b | |||
212748f140 | |||
b61582a60e | |||
9ca8da152b | |||
c5aa1b8664 | |||
90dbb417ac | |||
7fb3076238 | |||
946651496f | |||
5a8ac850b5 | |||
77a88f1575 | |||
c6f66226c8 | |||
be00d13a46 | |||
84814a0012 | |||
a0865cda2e | |||
de03692469 | |||
fb2d3e4eb7 | |||
29150e809d | |||
e18a606b23 | |||
67708411cd | |||
1e7b1ccf22 | |||
3e4bd28916 | |||
a2e16370fa | |||
d677654311 | |||
903fac1005 | |||
ff045214d6 | |||
57dcf5ab28 | |||
959a5ec55b | |||
b22a93a175 | |||
5debeb421d | |||
25b30e441a | |||
0f314c45b4 | |||
d7d88f3356 | |||
dbaef9d227 | |||
38d8b14b03 | |||
8b9244b079 | |||
3d938ea62f | |||
78f668bd7f | |||
13c0b315a4 | |||
99e65f7b59 | |||
96bad7bf90 | |||
5e5f20cab2 | |||
8eb668cd72 | |||
2383c93139 | |||
154ea9e55d | |||
d36a9e2000 | |||
6f334e4cab | |||
700c5409bf | |||
6158b8a91d | |||
0c3024819c | |||
c8410ac6f3 | |||
41e4af4e65 | |||
bac9936c2a | |||
936a48f458 | |||
43cfd63660 | |||
0f10874f80 | |||
a9e6c229d0 | |||
7cd83ad945 | |||
2f691db0a2 | |||
eb6d5d2fb9 | |||
fc5487396b | |||
6db8c100ba | |||
acfb4693ee | |||
aeaabe2c27 | |||
c60d957269 | |||
b6dc6ffc01 | |||
181821f8f5 | |||
6ac44a79b2 | |||
77740d2c86 | |||
17567ebd0f | |||
7ed0818279 | |||
bb079b3e46 | |||
d94b4b2a3c | |||
9d90c35629 | |||
7a77dc7343 | |||
2cff772caa | |||
849cad054e | |||
518ca5fe58 | |||
65e42f980c | |||
f95957d534 | |||
bd1ed2614e | |||
01920d7a50 | |||
83ac8abf81 | |||
44544e0491 | |||
c47e0d661b | |||
9192c5caa2 | |||
b0fc5c7e27 | |||
bf5d7b2ba1 | |||
8da2213bf1 | |||
5b4c4f4543 | |||
080cf67b8c | |||
36bb954373 | |||
93afa91239 | |||
73fbf66d4c | |||
8ae0d97973 | |||
ca5ec94082 | |||
5d5da97b45 | |||
d61f36bca8 | |||
96f5dc7300 | |||
8e5debca90 | |||
08ed544e52 | |||
8c4a26b0e2 | |||
bda0681dee | |||
cf092d8b4f | |||
a11bcab0db | |||
986bcaf0df | |||
192d1b0be3 | |||
82c8ca9c3d | |||
4a1adb76ab | |||
94b799e80b | |||
bdae136bed | |||
73e73c5489 | |||
f3bcdf74df | |||
87cd3ea727 | |||
114f42fc14 | |||
6daa1aa221 | |||
52f85753c5 | |||
0a5634aa05 | |||
3e8b9aa296 | |||
67058d8b55 | |||
d112ec2f0a | |||
73382c5363 | |||
96c0e718d0 | |||
522e1dfd0e | |||
08145f9b96 | |||
faf2c6df90 | |||
b8f3814df0 | |||
1f4db2bd80 | |||
d8d784a0bc | |||
2dc1416f30 | |||
7fdcb29bab | |||
6a89e3527c | |||
d1d0667cd5 | |||
c176a20010 | |||
865db5a9b3 | |||
ad2f19658b | |||
bed8efb24c | |||
aa9af7b41c | |||
02fd484632 | |||
96eab464c7 | |||
162005d72f | |||
09d28156f8 | |||
fc67c496c5 | |||
540a1a29b1 | |||
3163adf486 | |||
e042f9b5e2 | |||
05a1b5397b | |||
19776df46c | |||
64fd65aa52 | |||
3d58eba78c | |||
565884d089 | |||
2a83da1cb6 | |||
f186ce9649 | |||
6ecfee5faf | |||
662f1a31f6 | |||
06f9a1484b | |||
c90e8ca715 | |||
6ddc4ce4b1 | |||
4fffac07fd | |||
059c552307 | |||
75d71d4208 | |||
e38628509d | |||
0b247176bb | |||
faad09961d | |||
98d4f808e5 | |||
2ae91db65d | |||
529328f0ae | |||
e59d9ff3c6 | |||
4aad36601c | |||
4aaba3ef9f | |||
b482a9cda7 | |||
595eb739af | |||
b46bbea0c5 | |||
6dad24ffde | |||
f8759b9801 | |||
049c77c902 | |||
1478833c9c | |||
c8d40c6905 | |||
ff815b5f42 | |||
e5138d0e99 | |||
f43725a16e | |||
f6c65584bf | |||
246020729e | |||
63cc4e347d | |||
ecaca82d9a | |||
d6ef0d1c83 | |||
f2a7f164e1 | |||
dfbdc46971 | |||
3049f9e719 | |||
391c9abbb0 | |||
e191a72ca0 | |||
68c38f228d | |||
a823347c99 | |||
22b417b50b | |||
98ed063ce6 | |||
c0fb493f57 | |||
eae5e57346 | |||
f6fcef24c6 | |||
5bf6f69fca | |||
acf054d992 | |||
56798f09bf | |||
4c1253dc87 | |||
09793979c7 | |||
fa360b8208 | |||
9f6d837a9b | |||
f94e100c30 | |||
33b54e78f9 | |||
98cca7039c | |||
f50b0876e4 | |||
c30763c98f | |||
6fc95c3ff8 | |||
eef1f2b6ef | |||
128b1cf856 | |||
6b9944001e | |||
1cc22a6195 | |||
af643468fd | |||
f8358a0807 | |||
3eefb98f30 | |||
8f39f953f8 | |||
5e4af7e568 | |||
24bd13403a | |||
4149cbdf07 | |||
ced3ab97e8 | |||
3f7f0a7b0a | |||
20bcf8aab8 | |||
0814245ce6 | |||
1687d66a0e | |||
cf446a38b3 | |||
36ef87909e | |||
6bfeac5e98 | |||
d669320385 | |||
8dbdb79833 | |||
2d2f27ea46 | |||
4aeb2bf65e | |||
24da76db19 | |||
3c49936eee | |||
b416e79d63 | |||
92c529587b | |||
3b74c232dc | |||
6164dc32d7 | |||
37e7040eea | |||
a7ebb4b241 | |||
2fc562ff2d | |||
b5c83fea4d | |||
b586f98926 | |||
e6205c086f | |||
2ca34099ed | |||
5da6c12941 | |||
e2612b75fc | |||
ca5edb95f1 | |||
724e2b3692 | |||
2c93561a3b | |||
0b24cc8631 | |||
6c6e932899 | |||
c66a711890 | |||
787f8318fe | |||
9a27873af5 | |||
0abab57d83 | |||
d5662dfef4 | |||
ee2ee48b47 | |||
896d977b95 | |||
d1966b60a8 | |||
e05f05f9ed | |||
81846d9c67 | |||
723f0e862d | |||
2d0433b96c | |||
e3cbcf5853 | |||
bdf1f7c601 | |||
24b23d4f90 | |||
09c1a5f778 | |||
73a9cf01f3 | |||
97e860cf21 | |||
25b55087cf | |||
25f694bbdb | |||
7cd85cf84a | |||
cf5c886b6f | |||
e667c7c988 | |||
fd254fbeec | |||
859c556425 | |||
9b1615f2fb | |||
a3cad030e5 | |||
342e9f99d3 | |||
8ed04d0b75 | |||
5b5a8ff03f | |||
e0199084ad | |||
dc8c3a30bd | |||
67a6deed72 | |||
86cb51364a | |||
355113e15d | |||
40c589eced | |||
ec4f175f73 | |||
2273c21eb2 | |||
97c2b15e29 | |||
2f90ee067b | |||
7b64288019 | |||
e6e1ed7ca9 | |||
73838190fd | |||
d32fad87d1 | |||
67db9679fa | |||
3edd48a8b3 | |||
a4091bfcdd | |||
24483631a0 | |||
0f74a1a011 | |||
62d6e3763b | |||
39ea7a032f | |||
3ac125f9c7 | |||
7667a7e665 | |||
d7499fc5c5 | |||
f6885b239b | |||
4928322cdb | |||
77e191d63e | |||
15c98a1d2e | |||
ed757bdeff | |||
65241ad8bf | |||
6a7760f33f | |||
fdc62e21ef | |||
32f866f834 | |||
fbf52850e8 | |||
ab9b207f96 | |||
5532b9cfea | |||
449d3f0304 | |||
f0210c2607 | |||
ad88aaf17f | |||
0485b56e8d | |||
b65842f5c1 | |||
22b6e0afcd | |||
b0e536e576 | |||
54e4314e88 | |||
d00b1847cc | |||
be02617855 | |||
b5065f13c9 | |||
659b6d5d19 | |||
9c33251c44 | |||
1a0896475c | |||
7e820745a4 | |||
fa63c150dd | |||
1a2495a95c | |||
d79099946a | |||
27afad583b | |||
acde0867a0 | |||
d44f99bac2 | |||
2b35e20b1d | |||
da15957c3f | |||
208fc3452d | |||
ba1db870a4 | |||
7885a3b0ff | |||
66485f0464 | |||
0741058c1d | |||
3a6e79c575 | |||
70aa73482e | |||
2fa30bdd0e | |||
b28fe30bba | |||
9ba39e99c6 | |||
0e6aed7497 | |||
7e11fbe7a3 | |||
23abab987f | |||
5856a42807 | |||
a44b3efeb7 | |||
1992a09ac2 | |||
efa54e0c46 | |||
bde2d5e0a6 | |||
4090c894fc | |||
221bde01f8 | |||
b191a3c2f4 | |||
032197ee9f | |||
d5a4eb609a | |||
e7f1980b80 | |||
d430293c66 | |||
180d2692cd | |||
433e58655a | |||
5ffb6b7232 | |||
55ca9149d5 | |||
4ea57ca9a0 | |||
7ac4b0b79f | |||
2d51ed317f | |||
02c51b05b6 | |||
cd09f03f0b | |||
bc475e0f08 | |||
441b008709 | |||
4d81a0251e | |||
59da513481 | |||
c17047a193 | |||
f50a881273 | |||
afd6dd5257 | |||
3a43d7c5d5 | |||
65375886bd | |||
8495107849 | |||
c011d99b8b | |||
adc3542750 | |||
82e3241f1b | |||
2bca46886a | |||
971987c786 | |||
cd71a13bb7 | |||
98290fe31b | |||
9f15fb1474 | |||
301a867f8b | |||
658a044e85 | |||
2c1e29445d | |||
3f4c4f7418 | |||
592cc13b1f | |||
e70c2f3d10 | |||
bac865eab1 | |||
3d8fbc0a58 | |||
1fcfab7efa | |||
499334eef1 | |||
9fd76b8729 | |||
80d450e980 | |||
a1f2629366 | |||
bf8e1f2bfd | |||
f7d10ceeda | |||
095883a94e | |||
51638b7c71 | |||
adaddad370 | |||
cf6ff58f16 | |||
3e3f42a8f7 | |||
974e21d856 | |||
da86338bfe | |||
3a9a6767a0 | |||
fe8a1e6ce6 | |||
55aa3f7b58 | |||
59f3581370 | |||
ccae63936c | |||
6733349af0 | |||
f63c6b725b | |||
50b51f1810 | |||
fc39b3b0dd | |||
5964976e47 | |||
677a87150b | |||
2469c8d0c6 | |||
dafb89d1dd | |||
8da01445e5 | |||
6b2273d314 | |||
b886e66ee9 | |||
3afcb19727 | |||
06d2480f30 | |||
fd7d8ddf2d | |||
1dc0f4e5b8 | |||
fa64a88c24 | |||
385ec05e57 | |||
3a38e1e413 | |||
7f04e9e97d | |||
839f0c7e1c | |||
2352e29902 | |||
fcbc7fcece | |||
c2252c65a4 | |||
e150673de4 | |||
4f5c49a529 | |||
7107089ad3 | |||
967818f57d | |||
14c89c9be5 | |||
02111c2dc2 | |||
ebea74b607 | |||
5bbe5421bf | |||
279289989f | |||
bb4a16cf7c | |||
309db49f1b | |||
62a582ef17 | |||
d6b389760d | |||
bd4deb02b0 | |||
449e7672f9 | |||
31ff6d3c17 | |||
cfcc32271f | |||
e2ea84f28a | |||
6885ef2e54 | |||
8fa9f476e3 | |||
1cf8d1e3fa | |||
9f61177b62 | |||
59b8e83476 | |||
eee4d00a08 | |||
51c0598b50 | |||
69311f058b | |||
0f70c3ea9a | |||
b5660c87a0 | |||
2a686e65cd | |||
2bb0386220 | |||
526605a0bb | |||
5b9903a226 | |||
3fc60bf596 | |||
7815d6538f | |||
4c4d525655 | |||
e44213a8a9 | |||
e87656631c | |||
e102ccf9f0 | |||
63af75a330 | |||
8a10af9b62 | |||
18308950d1 | |||
86a9676a9c | |||
aa12a71ff3 | |||
aee46d1902 | |||
279a1791f6 | |||
8d71b295ea | |||
f72cedae10 | |||
864cf23416 | |||
10574bfe26 | |||
02085ce902 | |||
4eeea0b27c | |||
93b7f56337 | |||
12ecefa832 | |||
dd9a00679d | |||
081502848d | |||
0fa9fa20bc | |||
0a1f25a659 | |||
bc74c44f97 | |||
c50e325f53 | |||
0225e6fabb | |||
3caa46ade8 | |||
998bbe92f7 | |||
009be0ded8 | |||
c9f6207e32 | |||
36adc5e00e | |||
cb24b2aac8 | |||
1e0eb26dce | |||
f8161c8c72 | |||
862e2e9d65 | |||
0e734bd638 | |||
a35054f6ba | |||
e0ace85d6e | |||
7867587884 | |||
0564d06923 | |||
8ace72d134 | |||
491331e9e3 | |||
4a324eafd8 | |||
173cf0238d | |||
fd792e7e1d | |||
d0656358a2 | |||
040fa511f6 | |||
75099f159f | |||
e4a83ad2e2 | |||
760f9d487c | |||
a02e73e2a4 | |||
d6b7045461 | |||
bd9c9ea1f4 | |||
d4c95ab1a7 | |||
fbebeaf38f | |||
97245c740e | |||
03c4c2056a | |||
cee982754b | |||
a6497b844a | |||
788dcf2c73 | |||
6d9f80805e | |||
7f055450df | |||
9234213c62 | |||
5a40b5a1cf | |||
19e4a6de4d | |||
0daca059c7 | |||
e7278c4cd9 | |||
3e79dbb3f5 | |||
0fd193f8e0 | |||
342c713805 | |||
9b2565e387 | |||
1c5a8cabe9 | |||
0df80c5b2d | |||
613b97c93d | |||
c577f51c19 | |||
335f3f7d37 | |||
5740d2b4e4 | |||
b3f0d36ddc | |||
24d121ab59 | |||
09887a7405 | |||
38ee3a005e | |||
10e7999334 | |||
8c458588ab | |||
2381a2e4ba | |||
9ef8812205 | |||
37a204e49e | |||
11927f341a | |||
6fc17a4964 | |||
eb00232db6 | |||
4fd245e493 | |||
d92c57d051 | |||
ccbf09398e | |||
afbca118b7 | |||
beaef1feb0 | |||
033fd5e7a4 | |||
bd29d6feb9 | |||
f49f3c926c | |||
280d44f1e5 | |||
4eea0dc544 | |||
8a33f1a591 | |||
74653e7ed1 | |||
56ff11d63f | |||
1ecce285f0 | |||
b5c9b6a1bd | |||
e12ac6c07e | |||
dbb8617180 | |||
8a0b1bb427 | |||
1f6faadf81 | |||
8f3b7e1698 | |||
24c460c695 | |||
8acceab1e7 | |||
d60aba9339 | |||
3a228f7521 | |||
3f7ac0f142 | |||
63cf535ebb | |||
69a2a46c47 | |||
d081077273 | |||
75034f9350 | |||
eacd7b0c6a | |||
5bad77083c | |||
ea480c222b | |||
1fb644af4a | |||
a6f4a95821 | |||
8578208f2d | |||
fc4189ba0f | |||
b9ecf42fb6 | |||
008e18638f | |||
ac3b9c25dd | |||
f4997dec12 | |||
fcf405c630 | |||
efc6876260 | |||
1025759efb | |||
8bab6d87bb | |||
39a49f12f5 | |||
cfd841ea08 | |||
4d67c03e3e | |||
8826bc5d60 | |||
03fdce67f1 | |||
72f3f7980e | |||
f1aa2fbd84 | |||
217de6250f | |||
f742bd01d9 | |||
3fe53d5183 | |||
a5f5f803df | |||
c37e3ba635 | |||
55279e5e41 | |||
88fb37e8c6 | |||
6271dcc25d | |||
0f7faa6bfe | |||
4ace339d5b | |||
e8c0d1ece9 | |||
bb1977976c | |||
bb3da75870 | |||
088e888560 | |||
180241fdf0 | |||
93f27a7ee8 | |||
ed3bc8dd27 | |||
8dc4809ec8 | |||
a55d64e430 | |||
02d54da74a | |||
d660168700 | |||
1c75fc84f0 | |||
f63da87c7f | |||
53b9fe2dec | |||
87dc0eed7e | |||
f2dd6f94a4 | |||
ac26ae3893 | |||
4c65e9910a | |||
5e5ab29ab9 | |||
5150c102e6 | |||
41c29d41e1 | |||
4de33190a9 | |||
7cfecb39e4 | |||
7524b83c29 | |||
7a41cdf51b | |||
17d99cb2cf | |||
bd0da0ff74 | |||
d2a54234f4 | |||
626262461a | |||
93ba29e57f | |||
1581aa088d | |||
ceab951bca | |||
2e3dcc50ae | |||
a79087670e | |||
7b04c08fc7 | |||
70842b8e5e | |||
36e3e4c1b5 | |||
ce9b66ef14 | |||
1384c8e855 | |||
f213c75ede | |||
6ade708e19 | |||
ce3af41ebc | |||
e442f10fa5 | |||
2e8ad18285 | |||
f03ca7f916 | |||
bfa533e9d2 | |||
a8759e7410 | |||
af1905a39e | |||
16182a9d1d | |||
1321aa712f | |||
c1f61f2db4 | |||
5ad00130ea | |||
ea5e8e29e6 | |||
e7f89bdfef | |||
d23a7e41f3 | |||
52a885716d | |||
3fc907f076 | |||
eaf10483c0 | |||
dcd0234fb5 | |||
4dda270e8e | |||
4e6b289e1b | |||
c1cb85b49f | |||
ed71e651f6 | |||
6fab7d9507 | |||
1a11dd954b | |||
5d3574d3f6 | |||
aa42aa05aa | |||
7a36badb23 | |||
9ce6fd3f8e | |||
a549c8b9e3 | |||
1c749c84f2 | |||
1bc1feb843 | |||
80ca115ccd | |||
5a6bb90870 | |||
de7a693a6a | |||
096417281e | |||
763a96faf8 | |||
870eaf9301 | |||
10abf192a1 | |||
508f697bdd | |||
8ea8a6f72e | |||
54e6f4b607 | |||
ea3b3c5cec | |||
a8fd83652d | |||
45f3675337 | |||
87a9a87dcd | |||
0b882ece8c | |||
e005e94165 | |||
0e07eaaa01 | |||
e10e313af3 | |||
e6c0bbb25b | |||
2b39d9e6c4 | |||
cf42279e5b | |||
fbc4b47198 | |||
4baa6b1d3d | |||
74ee77f41e | |||
ee1b12173a | |||
1bfbc7047c | |||
a410d560a7 | |||
99e150cc1d | |||
e7191c2f71 | |||
f6deb0969a | |||
1163e41e64 | |||
a0f93f995e | |||
50fcf97a36 | |||
8e68d21115 | |||
372b6cbaea | |||
26add7bfd1 | |||
364302a691 | |||
c8dc29d59b | |||
f3d207ab5c | |||
e1cd632546 | |||
655ee4f118 | |||
34a2452bf5 | |||
7846a81636 | |||
6bdf3455f5 | |||
556ae168dd | |||
7b19d2aa6a | |||
bda9bb3d61 | |||
4b66a9343c | |||
4930d7fc02 | |||
ad644db512 | |||
3707b75349 | |||
ffaf145317 | |||
17b0d0081d | |||
ecf177fecc | |||
6112bc9356 | |||
6c3156273c | |||
eb7c804bb9 | |||
9d7bfae519 | |||
1292b5bf56 | |||
f09e18a706 | |||
5d9a43a3fd | |||
12154c869f | |||
8d66272ab2 | |||
0e44e630cb | |||
49c4929c9c | |||
da561e37c5 | |||
ebc584d36f | |||
656d979d7d | |||
a29fb613b9 | |||
5382f3de2d | |||
b2b858f7e8 | |||
dbc5b5a3d1 | |||
8f3d328b9a | |||
b7d683ee1b | |||
9bd6ec19c4 | |||
03fd0a1eb9 | |||
97023e7714 | |||
1d23ed0680 | |||
1bd66a614b | |||
802a9cf83c | |||
9e95fdbb58 | |||
803f56cfe5 | |||
b163a6c5ad | |||
ddc119ceb6 | |||
302e068c74 | |||
95b92caff3 | |||
5d894b6d43 | |||
09e621539e | |||
dab3e2efad | |||
5e0b78b104 | |||
27852607d1 | |||
956719f797 | |||
04cbbccd25 | |||
7f48e9d62e | |||
8a0018eff2 | |||
e6a920caa3 | |||
71b8c59050 | |||
11411ca4eb | |||
b7c79fa45b | |||
18951b99de | |||
bd05c440c3 | |||
9ca5013a59 | |||
b65b8bc362 | |||
f494c182ff | |||
2fae822e1f | |||
5df140cbd5 | |||
d93cbb023d | |||
9056d1be0c | |||
5f503949eb | |||
15c5fe4095 | |||
91ebcca0fd | |||
9cf917de07 | |||
0826b40e2a | |||
911b62c63a | |||
5343c7af00 | |||
8c03c160a9 | |||
604b0467f9 | |||
a2b555dd81 | |||
ce7bb82f02 | |||
7cd092c0cf | |||
cbfb9af0b9 | |||
ef236106b4 | |||
773a338397 | |||
afb5820113 | |||
5acc0fc243 | |||
c56469ecdb | |||
c59a53180c | |||
f56d265e62 | |||
cc0ff98d4f | |||
4a14c3efd2 | |||
b2d2297914 | |||
836bb6d835 | |||
177eb2afee | |||
594df18611 | |||
3bcb8bf6fc | |||
23c362f9cd | |||
9120367562 | |||
a74c37c18b | |||
f509464947 | |||
07fd489982 | |||
f6d3831d6d | |||
3ece81d663 | |||
f6d87ebf32 | |||
23483ab7e1 | |||
fe31d44d22 | |||
58bab4d163 | |||
8f48a64fd6 | |||
929dc059c3 | |||
d604ef2480 | |||
45e471b16a | |||
fe096772e0 | |||
7c540b6be8 | |||
7dbe8dd3c9 | |||
0dec602729 | |||
66ded779fc | |||
01d24291f2 | |||
55b36b033e | |||
8f461bf50c | |||
1847491cb3 | |||
541c7b63cd | |||
7e5e177680 | |||
40f552e4f1 | |||
ecb54ee3b3 | |||
35a63b8cc6 | |||
2a4596d415 | |||
35e476d916 | |||
b975996158 | |||
122f789cdf | |||
c9911aa841 | |||
32cd0d8af8 | |||
585f0d9f1b | |||
d0292aa139 | |||
4e9be8ca3c | |||
ad49e9eaf1 | |||
fed60f7c03 | |||
1bc0e3087a | |||
80a4f838a1 | |||
d31ec44f50 | |||
d0caef37ce | |||
2d26febe58 | |||
c23ad8ebf2 | |||
bad068ef19 | |||
53430608a8 | |||
b9071ab2b3 | |||
a556c02df6 | |||
bfab270d68 | |||
8ea6a1f3d5 | |||
3c39bf6a0f | |||
828644799f | |||
411e67ae41 | |||
4914bc4b5a | |||
d7050a1947 | |||
3c59422511 | |||
c81204e6d5 | |||
880f39519f | |||
8646f6c50b | |||
437a9e6ccb | |||
b54139bd37 | |||
8a6a36ac54 | |||
c6eb973da0 | |||
21750a8c20 | |||
a598665b2f | |||
56bbf502a2 | |||
9975f7d83f | |||
7ad366b363 | |||
cca4d68d94 | |||
b82b94db54 | |||
de9cb265e0 | |||
5611b9aba1 | |||
53075d503a | |||
e47cfa262a | |||
0ab7a4e713 | |||
5138d588db | |||
7e2d093e29 | |||
2d780e0566 | |||
7ac4ad3194 | |||
3ab6eb62c8 | |||
8eb234a12f | |||
85590af99e | |||
5c7cec0c81 | |||
68f768749b | |||
2c7e342b18 | |||
632900e516 | |||
5fd975b1d7 | |||
d45ac66064 | |||
47cba8ec3c | |||
d4aab66da2 | |||
0dc4c92c89 | |||
f49c963367 | |||
fe11b8e57e | |||
79680b6a73 | |||
58838c541f | |||
03cc71cfed | |||
02529106c9 | |||
0401f55bc3 | |||
403e0d2d9d | |||
d939ff289d | |||
d1816c3051 | |||
cb350788c0 | |||
cd58768d6f | |||
dcd6f4d55d | |||
3c828614b8 | |||
09e7988596 | |||
f40df19334 | |||
76c9d3488b | |||
0809da33e0 | |||
b528eec4bb | |||
5179103680 | |||
25a9e5f58a | |||
8ddfe7b6e9 | |||
c23f21d57a | |||
1242a43d98 | |||
1655ca27d1 | |||
2bcead03b0 | |||
41ab1972ce | |||
b00fff6922 | |||
97b01ca5f8 | |||
c2bd6f5ef3 | |||
18efc9a6de | |||
436ccb25fb | |||
8f08a352dd | |||
00f86cfd00 | |||
3944aafb11 | |||
a6b852fab9 | |||
2a043afe11 | |||
df8f2cf9ab | |||
a18015b1e5 | |||
8b80622d2f | |||
c0fd0a56f3 | |||
326764dd41 | |||
1f24d02c5e | |||
c130fbddd9 | |||
f560534493 | |||
10a97f4522 | |||
7a2f0214f3 | |||
a2b994ab23 | |||
e73d3f87f3 | |||
c4715124dc | |||
b53607f8e4 | |||
8f79d3210a | |||
68b1984a76 | |||
ba45e83880 | |||
28ecc37163 | |||
a6a2e2bae0 | |||
d8bbfacae0 | |||
58549c398f | |||
842ed62bec | |||
06d8800ee0 | |||
2ecfd1bb7e | |||
783d4c7bd6 | |||
fbf3f26abd | |||
1d09693041 | |||
626e37e3d0 | |||
07fd67b328 | |||
3f1f018adc | |||
fe04e6d20c | |||
d7171a1617 | |||
384a0daa31 | |||
c5c949e034 | |||
c2c9edf156 | |||
c8248ef4e9 | |||
9f6a6a7b7c | |||
121b642d50 | |||
59b16f647e | |||
2ab5932693 | |||
8dfcef3900 | |||
8ca70eec44 | |||
60df59c7f0 | |||
e231c531a6 | |||
d48bb910fa | |||
1317266415 | |||
288f47f4bd | |||
b090ebfd41 | |||
67773bff5e | |||
8ef1cfda04 | |||
2a79d5ba36 | |||
0cb95f36ff | |||
288d7e88ae | |||
f88389bf9e | |||
2e88c5e2c5 | |||
73f3b8173e | |||
aa5b88ff04 | |||
b7caff88cf | |||
760a1e917a | |||
2d7ff66246 | |||
179497e830 | |||
4c08c80e5b | |||
7d6af64904 | |||
16519f9486 | |||
bb27d38a12 | |||
5b26928751 | |||
f425e7e48f | |||
4601f46afb | |||
692bdc060c | |||
3a4f8c2e54 | |||
146c4284a2 | |||
5ae33b9f3b | |||
1f38b92ec6 | |||
f2a49a79f0 | |||
3ddb4cd27a | |||
a5555c3816 | |||
8479c406a5 | |||
8e0b4254b1 | |||
069651bdb4 | |||
9061ec2dff | |||
b0a5023723 | |||
69fe5bf71d | |||
f12d4d80c6 | |||
56f2a3afa4 | |||
406da1b5f0 | |||
da45e132a3 | |||
fb719a9383 | |||
3c64359597 | |||
e420973dd2 | |||
15cc157c5f | |||
ad89ffe94d | |||
4de1713a18 | |||
1917e0fdb7 | |||
4b07234997 | |||
6a402950c3 | |||
63333159ca | |||
ce4ba24ef2 | |||
f606e31b98 | |||
ecdbb3eb53 | |||
0321ec32fb |
10
.env.example
10
.env.example
@ -36,16 +36,22 @@ CLIENT_ID_HEROKU=
|
|||||||
CLIENT_ID_VERCEL=
|
CLIENT_ID_VERCEL=
|
||||||
CLIENT_ID_NETLIFY=
|
CLIENT_ID_NETLIFY=
|
||||||
CLIENT_ID_GITHUB=
|
CLIENT_ID_GITHUB=
|
||||||
|
CLIENT_ID_GITHUB_APP=
|
||||||
|
CLIENT_SLUG_GITHUB_APP=
|
||||||
CLIENT_ID_GITLAB=
|
CLIENT_ID_GITLAB=
|
||||||
CLIENT_ID_BITBUCKET=
|
CLIENT_ID_BITBUCKET=
|
||||||
CLIENT_SECRET_HEROKU=
|
CLIENT_SECRET_HEROKU=
|
||||||
CLIENT_SECRET_VERCEL=
|
CLIENT_SECRET_VERCEL=
|
||||||
CLIENT_SECRET_NETLIFY=
|
CLIENT_SECRET_NETLIFY=
|
||||||
CLIENT_SECRET_GITHUB=
|
CLIENT_SECRET_GITHUB=
|
||||||
|
CLIENT_SECRET_GITHUB_APP=
|
||||||
CLIENT_SECRET_GITLAB=
|
CLIENT_SECRET_GITLAB=
|
||||||
CLIENT_SECRET_BITBUCKET=
|
CLIENT_SECRET_BITBUCKET=
|
||||||
CLIENT_SLUG_VERCEL=
|
CLIENT_SLUG_VERCEL=
|
||||||
|
|
||||||
|
CLIENT_PRIVATE_KEY_GITHUB_APP=
|
||||||
|
CLIENT_APP_ID_GITHUB_APP=
|
||||||
|
|
||||||
# Sentry (optional) for monitoring errors
|
# Sentry (optional) for monitoring errors
|
||||||
SENTRY_DSN=
|
SENTRY_DSN=
|
||||||
|
|
||||||
@ -70,3 +76,7 @@ NEXT_PUBLIC_CAPTCHA_SITE_KEY=
|
|||||||
|
|
||||||
PLAIN_API_KEY=
|
PLAIN_API_KEY=
|
||||||
PLAIN_WISH_LABEL_IDS=
|
PLAIN_WISH_LABEL_IDS=
|
||||||
|
|
||||||
|
SSL_CLIENT_CERTIFICATE_HEADER_KEY=
|
||||||
|
|
||||||
|
ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT=
|
||||||
|
@ -1 +1,2 @@
|
|||||||
DB_CONNECTION_URI=
|
DB_CONNECTION_URI=
|
||||||
|
AUDIT_LOGS_DB_CONNECTION_URI=
|
||||||
|
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
- [ ] Bug fix
|
- [ ] Bug fix
|
||||||
- [ ] New feature
|
- [ ] New feature
|
||||||
|
- [ ] Improvement
|
||||||
- [ ] Breaking change
|
- [ ] Breaking change
|
||||||
- [ ] Documentation
|
- [ ] Documentation
|
||||||
|
|
||||||
|
91
.github/workflows/build-binaries.yml
vendored
91
.github/workflows/build-binaries.yml
vendored
@ -7,7 +7,6 @@ on:
|
|||||||
description: "Version number"
|
description: "Version number"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./backend
|
working-directory: ./backend
|
||||||
@ -49,9 +48,9 @@ jobs:
|
|||||||
- name: Package into node binary
|
- name: Package into node binary
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ matrix.os }}" != "linux" ]; then
|
if [ "${{ matrix.os }}" != "linux" ]; then
|
||||||
pkg --no-bytecode --public-packages "*" --public --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core-${{ matrix.os }}-${{ matrix.arch }} .
|
pkg --no-bytecode --public-packages "*" --public --compress GZip --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core-${{ matrix.os }}-${{ matrix.arch }} .
|
||||||
else
|
else
|
||||||
pkg --no-bytecode --public-packages "*" --public --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core .
|
pkg --no-bytecode --public-packages "*" --public --compress GZip --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core .
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set up .deb package structure (Debian/Ubuntu only)
|
# Set up .deb package structure (Debian/Ubuntu only)
|
||||||
@ -83,6 +82,86 @@ jobs:
|
|||||||
dpkg-deb --build infisical-core
|
dpkg-deb --build infisical-core
|
||||||
mv infisical-core.deb ./binary/infisical-core-${{matrix.arch}}.deb
|
mv infisical-core.deb ./binary/infisical-core-${{matrix.arch}}.deb
|
||||||
|
|
||||||
|
### RPM
|
||||||
|
|
||||||
|
# Set up .rpm package structure
|
||||||
|
- name: Set up .rpm package structure
|
||||||
|
if: matrix.os == 'linux'
|
||||||
|
run: |
|
||||||
|
mkdir -p infisical-core-rpm/usr/local/bin
|
||||||
|
cp ./binary/infisical-core infisical-core-rpm/usr/local/bin/
|
||||||
|
chmod +x infisical-core-rpm/usr/local/bin/infisical-core
|
||||||
|
|
||||||
|
# Install RPM build tools
|
||||||
|
- name: Install RPM build tools
|
||||||
|
if: matrix.os == 'linux'
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y rpm
|
||||||
|
|
||||||
|
# Create .spec file for RPM
|
||||||
|
- name: Create .spec file for RPM
|
||||||
|
if: matrix.os == 'linux'
|
||||||
|
run: |
|
||||||
|
cat <<EOF > infisical-core.spec
|
||||||
|
|
||||||
|
%global _enable_debug_package 0
|
||||||
|
%global debug_package %{nil}
|
||||||
|
%global __os_install_post /usr/lib/rpm/brp-compress %{nil}
|
||||||
|
|
||||||
|
Name: infisical-core
|
||||||
|
Version: ${{ github.event.inputs.version }}
|
||||||
|
Release: 1%{?dist}
|
||||||
|
Summary: Infisical Core standalone executable
|
||||||
|
License: Proprietary
|
||||||
|
URL: https://app.infisical.com
|
||||||
|
|
||||||
|
%description
|
||||||
|
Infisical Core standalone executable (app.infisical.com)
|
||||||
|
|
||||||
|
%install
|
||||||
|
mkdir -p %{buildroot}/usr/local/bin
|
||||||
|
cp %{_sourcedir}/infisical-core %{buildroot}/usr/local/bin/
|
||||||
|
|
||||||
|
%files
|
||||||
|
/usr/local/bin/infisical-core
|
||||||
|
|
||||||
|
%pre
|
||||||
|
|
||||||
|
%post
|
||||||
|
|
||||||
|
%preun
|
||||||
|
|
||||||
|
%postun
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Build .rpm file
|
||||||
|
- name: Build .rpm package
|
||||||
|
if: matrix.os == 'linux'
|
||||||
|
run: |
|
||||||
|
# Create necessary directories
|
||||||
|
mkdir -p rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
||||||
|
|
||||||
|
# Copy the binary directly to SOURCES
|
||||||
|
cp ./binary/infisical-core rpmbuild/SOURCES/
|
||||||
|
|
||||||
|
# Run rpmbuild with verbose output
|
||||||
|
rpmbuild -vv -bb \
|
||||||
|
--define "_topdir $(pwd)/rpmbuild" \
|
||||||
|
--define "_sourcedir $(pwd)/rpmbuild/SOURCES" \
|
||||||
|
--define "_rpmdir $(pwd)/rpmbuild/RPMS" \
|
||||||
|
--target ${{ matrix.arch == 'x64' && 'x86_64' || 'aarch64' }} \
|
||||||
|
infisical-core.spec
|
||||||
|
|
||||||
|
# Try to find the RPM file
|
||||||
|
find rpmbuild -name "*.rpm"
|
||||||
|
|
||||||
|
# Move the RPM file if found
|
||||||
|
if [ -n "$(find rpmbuild -name '*.rpm')" ]; then
|
||||||
|
mv $(find rpmbuild -name '*.rpm') ./binary/infisical-core-${{matrix.arch}}.rpm
|
||||||
|
else
|
||||||
|
echo "RPM file not found!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.x" # Specify the Python version you need
|
python-version: "3.x" # Specify the Python version you need
|
||||||
@ -97,6 +176,12 @@ jobs:
|
|||||||
working-directory: ./backend
|
working-directory: ./backend
|
||||||
run: cloudsmith push deb --republish --no-wait-for-sync --api-key=${{ secrets.CLOUDSMITH_API_KEY }} infisical/infisical-core/any-distro/any-version ./binary/infisical-core-${{ matrix.arch }}.deb
|
run: cloudsmith push deb --republish --no-wait-for-sync --api-key=${{ secrets.CLOUDSMITH_API_KEY }} infisical/infisical-core/any-distro/any-version ./binary/infisical-core-${{ matrix.arch }}.deb
|
||||||
|
|
||||||
|
# Publish .rpm file to Cloudsmith (Red Hat-based systems only)
|
||||||
|
- name: Publish .rpm to Cloudsmith
|
||||||
|
if: matrix.os == 'linux'
|
||||||
|
working-directory: ./backend
|
||||||
|
run: cloudsmith push rpm --republish --no-wait-for-sync --api-key=${{ secrets.CLOUDSMITH_API_KEY }} infisical/infisical-core/any-distro/any-version ./binary/infisical-core-${{ matrix.arch }}.rpm
|
||||||
|
|
||||||
# Publish .exe file to Cloudsmith (Windows only)
|
# Publish .exe file to Cloudsmith (Windows only)
|
||||||
- name: Publish to Cloudsmith (Windows)
|
- name: Publish to Cloudsmith (Windows)
|
||||||
if: matrix.os == 'win'
|
if: matrix.os == 'win'
|
||||||
|
@ -6,9 +6,15 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
infisical-tests:
|
||||||
|
name: Integration tests
|
||||||
|
# https://docs.github.com/en/actions/using-workflows/reusing-workflows#overview
|
||||||
|
uses: ./.github/workflows/run-backend-tests.yml
|
||||||
|
|
||||||
infisical-image:
|
infisical-image:
|
||||||
name: Build backend image
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
needs: [infisical-tests]
|
||||||
steps:
|
steps:
|
||||||
- name: ☁️ Checkout source
|
- name: ☁️ Checkout source
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@ -98,8 +104,8 @@ jobs:
|
|||||||
cluster: infisical-gamma-stage
|
cluster: infisical-gamma-stage
|
||||||
wait-for-service-stability: true
|
wait-for-service-stability: true
|
||||||
|
|
||||||
production-postgres-deployment:
|
production-us:
|
||||||
name: Deploy to production
|
name: US production deploy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [gamma-deployment]
|
needs: [gamma-deployment]
|
||||||
environment:
|
environment:
|
||||||
@ -121,6 +127,7 @@ jobs:
|
|||||||
- name: Change directory to backend and install dependencies
|
- name: Change directory to backend and install dependencies
|
||||||
env:
|
env:
|
||||||
DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
|
DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
|
||||||
|
AUDIT_LOGS_DB_CONNECTION_URI: ${{ secrets.AUDIT_LOGS_DB_CONNECTION_URI }}
|
||||||
run: |
|
run: |
|
||||||
cd backend
|
cd backend
|
||||||
npm install
|
npm install
|
||||||
@ -152,3 +159,54 @@ jobs:
|
|||||||
service: infisical-core-platform
|
service: infisical-core-platform
|
||||||
cluster: infisical-core-platform
|
cluster: infisical-core-platform
|
||||||
wait-for-service-stability: true
|
wait-for-service-stability: true
|
||||||
|
|
||||||
|
production-eu:
|
||||||
|
name: EU production deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [production-us]
|
||||||
|
environment:
|
||||||
|
name: production-eu
|
||||||
|
steps:
|
||||||
|
- uses: twingate/github-action@v1
|
||||||
|
with:
|
||||||
|
service-key: ${{ secrets.TWINGATE_SERVICE_KEY }}
|
||||||
|
- name: Configure AWS Credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
|
with:
|
||||||
|
audience: sts.amazonaws.com
|
||||||
|
aws-region: eu-central-1
|
||||||
|
role-to-assume: arn:aws:iam::345594589636:role/gha-make-prod-deployment
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Setup Node.js environment
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
- name: Change directory to backend and install dependencies
|
||||||
|
env:
|
||||||
|
DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
npm install
|
||||||
|
npm run migration:latest
|
||||||
|
- name: Save commit hashes for tag
|
||||||
|
id: commit
|
||||||
|
uses: pr-mpt/actions-commit-hash@v2
|
||||||
|
- name: Download task definition
|
||||||
|
run: |
|
||||||
|
aws ecs describe-task-definition --task-definition infisical-core-platform --query taskDefinition > task-definition.json
|
||||||
|
- name: Render Amazon ECS task definition
|
||||||
|
id: render-web-container
|
||||||
|
uses: aws-actions/amazon-ecs-render-task-definition@v1
|
||||||
|
with:
|
||||||
|
task-definition: task-definition.json
|
||||||
|
container-name: infisical-core-platform
|
||||||
|
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||||
|
environment-variables: "LOG_LEVEL=info"
|
||||||
|
- name: Deploy to Amazon ECS service
|
||||||
|
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
|
||||||
|
with:
|
||||||
|
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
|
||||||
|
service: infisical-core-platform
|
||||||
|
cluster: infisical-core-platform
|
||||||
|
wait-for-service-stability: true
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -63,6 +63,7 @@ yarn-error.log*
|
|||||||
|
|
||||||
# Editor specific
|
# Editor specific
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
.idea/*
|
||||||
|
|
||||||
frontend-build
|
frontend-build
|
||||||
|
|
||||||
|
@ -95,6 +95,10 @@ RUN mkdir frontend-build
|
|||||||
# Production stage
|
# Production stage
|
||||||
FROM base AS production
|
FROM base AS production
|
||||||
RUN apk add --upgrade --no-cache ca-certificates
|
RUN apk add --upgrade --no-cache ca-certificates
|
||||||
|
RUN apk add --no-cache bash curl && curl -1sLf \
|
||||||
|
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
|
||||||
|
&& apk add infisical=0.31.1 && apk add --no-cache git
|
||||||
|
|
||||||
RUN addgroup --system --gid 1001 nodejs \
|
RUN addgroup --system --gid 1001 nodejs \
|
||||||
&& adduser --system --uid 1001 non-root-user
|
&& adduser --system --uid 1001 non-root-user
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ describe("Identity v1", async () => {
|
|||||||
test("Create identity", async () => {
|
test("Create identity", async () => {
|
||||||
const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin);
|
const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin);
|
||||||
expect(newIdentity.name).toBe("mac1");
|
expect(newIdentity.name).toBe("mac1");
|
||||||
expect(newIdentity.authMethod).toBeNull();
|
expect(newIdentity.authMethods).toEqual([]);
|
||||||
|
|
||||||
await deleteIdentity(newIdentity.id);
|
await deleteIdentity(newIdentity.id);
|
||||||
});
|
});
|
||||||
@ -42,7 +42,7 @@ describe("Identity v1", async () => {
|
|||||||
test("Update identity", async () => {
|
test("Update identity", async () => {
|
||||||
const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin);
|
const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin);
|
||||||
expect(newIdentity.name).toBe("mac1");
|
expect(newIdentity.name).toBe("mac1");
|
||||||
expect(newIdentity.authMethod).toBeNull();
|
expect(newIdentity.authMethods).toEqual([]);
|
||||||
|
|
||||||
const updatedIdentity = await testServer.inject({
|
const updatedIdentity = await testServer.inject({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
@ -39,8 +39,6 @@ describe("Login V1 Router", async () => {
|
|||||||
});
|
});
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
const payload = JSON.parse(res.payload);
|
const payload = JSON.parse(res.payload);
|
||||||
expect(payload).toHaveProperty("mfaEnabled");
|
|
||||||
expect(payload).toHaveProperty("token");
|
expect(payload).toHaveProperty("token");
|
||||||
expect(payload.mfaEnabled).toBeFalsy();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -123,7 +123,7 @@ describe("Project Environment Router", async () => {
|
|||||||
id: deletedProjectEnvironment.id,
|
id: deletedProjectEnvironment.id,
|
||||||
name: mockProjectEnv.name,
|
name: mockProjectEnv.name,
|
||||||
slug: mockProjectEnv.slug,
|
slug: mockProjectEnv.slug,
|
||||||
position: 4,
|
position: 5,
|
||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
updatedAt: expect.any(String)
|
updatedAt: expect.any(String)
|
||||||
})
|
})
|
||||||
|
36
backend/e2e-test/routes/v1/secret-approval-policy.spec.ts
Normal file
36
backend/e2e-test/routes/v1/secret-approval-policy.spec.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { seedData1 } from "@app/db/seed-data";
|
||||||
|
import { ApproverType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
||||||
|
|
||||||
|
const createPolicy = async (dto: { name: string; secretPath: string; approvers: {type: ApproverType.User, id: string}[]; approvals: number }) => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v1/secret-approvals`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
name: dto.name,
|
||||||
|
secretPath: dto.secretPath,
|
||||||
|
approvers: dto.approvers,
|
||||||
|
approvals: dto.approvals
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
return res.json().approval;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("Secret approval policy router", async () => {
|
||||||
|
test("Create policy", async () => {
|
||||||
|
const policy = await createPolicy({
|
||||||
|
secretPath: "/",
|
||||||
|
approvals: 1,
|
||||||
|
approvers: [{id:seedData1.id, type: ApproverType.User}],
|
||||||
|
name: "test-policy"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(policy.name).toBe("test-policy");
|
||||||
|
});
|
||||||
|
});
|
@ -1,73 +1,61 @@
|
|||||||
|
import { createFolder, deleteFolder } from "e2e-test/testUtils/folders";
|
||||||
|
import { createSecretImport, deleteSecretImport } from "e2e-test/testUtils/secret-imports";
|
||||||
|
import { createSecretV2, deleteSecretV2, getSecretByNameV2, getSecretsV2 } from "e2e-test/testUtils/secrets";
|
||||||
|
|
||||||
import { seedData1 } from "@app/db/seed-data";
|
import { seedData1 } from "@app/db/seed-data";
|
||||||
|
|
||||||
const createSecretImport = async (importPath: string, importEnv: string) => {
|
|
||||||
const res = await testServer.inject({
|
|
||||||
method: "POST",
|
|
||||||
url: `/api/v1/secret-imports`,
|
|
||||||
headers: {
|
|
||||||
authorization: `Bearer ${jwtAuthToken}`
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
workspaceId: seedData1.project.id,
|
|
||||||
environment: seedData1.environment.slug,
|
|
||||||
path: "/",
|
|
||||||
import: {
|
|
||||||
environment: importEnv,
|
|
||||||
path: importPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.statusCode).toBe(200);
|
|
||||||
const payload = JSON.parse(res.payload);
|
|
||||||
expect(payload).toHaveProperty("secretImport");
|
|
||||||
return payload.secretImport;
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteSecretImport = async (id: string) => {
|
|
||||||
const res = await testServer.inject({
|
|
||||||
method: "DELETE",
|
|
||||||
url: `/api/v1/secret-imports/${id}`,
|
|
||||||
headers: {
|
|
||||||
authorization: `Bearer ${jwtAuthToken}`
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
workspaceId: seedData1.project.id,
|
|
||||||
environment: seedData1.environment.slug,
|
|
||||||
path: "/"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.statusCode).toBe(200);
|
|
||||||
const payload = JSON.parse(res.payload);
|
|
||||||
expect(payload).toHaveProperty("secretImport");
|
|
||||||
return payload.secretImport;
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("Secret Import Router", async () => {
|
describe("Secret Import Router", async () => {
|
||||||
test.each([
|
test.each([
|
||||||
{ importEnv: "prod", importPath: "/" }, // one in root
|
{ importEnv: "prod", importPath: "/" }, // one in root
|
||||||
{ importEnv: "staging", importPath: "/" } // then create a deep one creating intermediate ones
|
{ importEnv: "staging", importPath: "/" } // then create a deep one creating intermediate ones
|
||||||
])("Create secret import $importEnv with path $importPath", async ({ importPath, importEnv }) => {
|
])("Create secret import $importEnv with path $importPath", async ({ importPath, importEnv }) => {
|
||||||
// check for default environments
|
// check for default environments
|
||||||
const payload = await createSecretImport(importPath, importEnv);
|
const payload = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
importPath,
|
||||||
|
importEnv
|
||||||
|
});
|
||||||
expect(payload).toEqual(
|
expect(payload).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
importPath: expect.any(String),
|
importPath,
|
||||||
importEnv: expect.objectContaining({
|
importEnv: expect.objectContaining({
|
||||||
name: expect.any(String),
|
name: expect.any(String),
|
||||||
slug: expect.any(String),
|
slug: importEnv,
|
||||||
id: expect.any(String)
|
id: expect.any(String)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
await deleteSecretImport(payload.id);
|
|
||||||
|
await deleteSecretImport({
|
||||||
|
id: payload.id,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Get secret imports", async () => {
|
test("Get secret imports", async () => {
|
||||||
const createdImport1 = await createSecretImport("/", "prod");
|
const createdImport1 = await createSecretImport({
|
||||||
const createdImport2 = await createSecretImport("/", "staging");
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
importPath: "/",
|
||||||
|
importEnv: "prod"
|
||||||
|
});
|
||||||
|
const createdImport2 = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
importPath: "/",
|
||||||
|
importEnv: "staging"
|
||||||
|
});
|
||||||
const res = await testServer.inject({
|
const res = await testServer.inject({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: `/api/v1/secret-imports`,
|
url: `/api/v1/secret-imports`,
|
||||||
@ -89,25 +77,60 @@ describe("Secret Import Router", async () => {
|
|||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
importPath: expect.any(String),
|
importPath: "/",
|
||||||
importEnv: expect.objectContaining({
|
importEnv: expect.objectContaining({
|
||||||
name: expect.any(String),
|
name: expect.any(String),
|
||||||
slug: expect.any(String),
|
slug: "prod",
|
||||||
|
id: expect.any(String)
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
importPath: "/",
|
||||||
|
importEnv: expect.objectContaining({
|
||||||
|
name: expect.any(String),
|
||||||
|
slug: "staging",
|
||||||
id: expect.any(String)
|
id: expect.any(String)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
await deleteSecretImport(createdImport1.id);
|
await deleteSecretImport({
|
||||||
await deleteSecretImport(createdImport2.id);
|
id: createdImport1.id,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
await deleteSecretImport({
|
||||||
|
id: createdImport2.id,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Update secret import position", async () => {
|
test("Update secret import position", async () => {
|
||||||
const prodImportDetails = { path: "/", envSlug: "prod" };
|
const prodImportDetails = { path: "/", envSlug: "prod" };
|
||||||
const stagingImportDetails = { path: "/", envSlug: "staging" };
|
const stagingImportDetails = { path: "/", envSlug: "staging" };
|
||||||
|
|
||||||
const createdImport1 = await createSecretImport(prodImportDetails.path, prodImportDetails.envSlug);
|
const createdImport1 = await createSecretImport({
|
||||||
const createdImport2 = await createSecretImport(stagingImportDetails.path, stagingImportDetails.envSlug);
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
importPath: prodImportDetails.path,
|
||||||
|
importEnv: prodImportDetails.envSlug
|
||||||
|
});
|
||||||
|
const createdImport2 = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
importPath: stagingImportDetails.path,
|
||||||
|
importEnv: stagingImportDetails.envSlug
|
||||||
|
});
|
||||||
|
|
||||||
const updateImportRes = await testServer.inject({
|
const updateImportRes = await testServer.inject({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
@ -161,22 +184,55 @@ describe("Secret Import Router", async () => {
|
|||||||
expect(secretImportList.secretImports[1].id).toEqual(createdImport1.id);
|
expect(secretImportList.secretImports[1].id).toEqual(createdImport1.id);
|
||||||
expect(secretImportList.secretImports[0].id).toEqual(createdImport2.id);
|
expect(secretImportList.secretImports[0].id).toEqual(createdImport2.id);
|
||||||
|
|
||||||
await deleteSecretImport(createdImport1.id);
|
await deleteSecretImport({
|
||||||
await deleteSecretImport(createdImport2.id);
|
id: createdImport1.id,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
await deleteSecretImport({
|
||||||
|
id: createdImport2.id,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Delete secret import position", async () => {
|
test("Delete secret import position", async () => {
|
||||||
const createdImport1 = await createSecretImport("/", "prod");
|
const createdImport1 = await createSecretImport({
|
||||||
const createdImport2 = await createSecretImport("/", "staging");
|
authToken: jwtAuthToken,
|
||||||
const deletedImport = await deleteSecretImport(createdImport1.id);
|
secretPath: "/",
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
importPath: "/",
|
||||||
|
importEnv: "prod"
|
||||||
|
});
|
||||||
|
const createdImport2 = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
importPath: "/",
|
||||||
|
importEnv: "staging"
|
||||||
|
});
|
||||||
|
const deletedImport = await deleteSecretImport({
|
||||||
|
id: createdImport1.id,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
|
||||||
// check for default environments
|
// check for default environments
|
||||||
expect(deletedImport).toEqual(
|
expect(deletedImport).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
importPath: expect.any(String),
|
importPath: "/",
|
||||||
importEnv: expect.objectContaining({
|
importEnv: expect.objectContaining({
|
||||||
name: expect.any(String),
|
name: expect.any(String),
|
||||||
slug: expect.any(String),
|
slug: "prod",
|
||||||
id: expect.any(String)
|
id: expect.any(String)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -201,6 +257,552 @@ describe("Secret Import Router", async () => {
|
|||||||
expect(secretImportList.secretImports.length).toEqual(1);
|
expect(secretImportList.secretImports.length).toEqual(1);
|
||||||
expect(secretImportList.secretImports[0].position).toEqual(1);
|
expect(secretImportList.secretImports[0].position).toEqual(1);
|
||||||
|
|
||||||
await deleteSecretImport(createdImport2.id);
|
await deleteSecretImport({
|
||||||
|
id: createdImport2.id,
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// dev <- stage <- prod
|
||||||
|
describe.each([{ path: "/" }, { path: "/deep" }])(
|
||||||
|
"Secret import waterfall pattern testing - %path",
|
||||||
|
({ path: testSuitePath }) => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
let prodFolder: { id: string };
|
||||||
|
let stagingFolder: { id: string };
|
||||||
|
let devFolder: { id: string };
|
||||||
|
|
||||||
|
if (testSuitePath !== "/") {
|
||||||
|
prodFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
|
||||||
|
stagingFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
|
||||||
|
devFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const devImportFromStage = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
importPath: testSuitePath,
|
||||||
|
importEnv: "staging"
|
||||||
|
});
|
||||||
|
|
||||||
|
const stageImportFromProd = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
importPath: testSuitePath,
|
||||||
|
importEnv: "prod"
|
||||||
|
});
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
await deleteSecretImport({
|
||||||
|
id: stageImportFromProd.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "staging",
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
|
||||||
|
await deleteSecretImport({
|
||||||
|
id: devImportFromStage.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
|
||||||
|
if (prodFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: prodFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "prod"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stagingFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: stagingFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "staging"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: devFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: seedData1.environment.slug
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Check one level imported secret exist", async () => {
|
||||||
|
await createSecretV2({
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY",
|
||||||
|
value: "stage-value"
|
||||||
|
});
|
||||||
|
|
||||||
|
const secret = await getSecretByNameV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(secret.secretKey).toBe("STAGING_KEY");
|
||||||
|
expect(secret.secretValue).toBe("stage-value");
|
||||||
|
|
||||||
|
const listSecrets = await getSecretsV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
expect(listSecrets.imports).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secrets: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "STAGING_KEY",
|
||||||
|
secretValue: "stage-value"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await deleteSecretV2({
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Check two level imported secret exist", async () => {
|
||||||
|
await createSecretV2({
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "PROD_KEY",
|
||||||
|
value: "prod-value"
|
||||||
|
});
|
||||||
|
|
||||||
|
const secret = await getSecretByNameV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "PROD_KEY"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(secret.secretKey).toBe("PROD_KEY");
|
||||||
|
expect(secret.secretValue).toBe("prod-value");
|
||||||
|
|
||||||
|
const listSecrets = await getSecretsV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
expect(listSecrets.imports).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secrets: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "PROD_KEY",
|
||||||
|
secretValue: "prod-value"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await deleteSecretV2({
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "PROD_KEY"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// dev <- stage, dev <- prod
|
||||||
|
describe.each([{ path: "/" }, { path: "/deep" }])(
|
||||||
|
"Secret import multiple destination to one source pattern testing - %path",
|
||||||
|
({ path: testSuitePath }) => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
let prodFolder: { id: string };
|
||||||
|
let stagingFolder: { id: string };
|
||||||
|
let devFolder: { id: string };
|
||||||
|
|
||||||
|
if (testSuitePath !== "/") {
|
||||||
|
prodFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
|
||||||
|
stagingFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
|
||||||
|
devFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const devImportFromStage = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
importPath: testSuitePath,
|
||||||
|
importEnv: "staging"
|
||||||
|
});
|
||||||
|
|
||||||
|
const devImportFromProd = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
importPath: testSuitePath,
|
||||||
|
importEnv: "prod"
|
||||||
|
});
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
await deleteSecretImport({
|
||||||
|
id: devImportFromProd.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
|
||||||
|
await deleteSecretImport({
|
||||||
|
id: devImportFromStage.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
|
||||||
|
if (prodFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: prodFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "prod"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stagingFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: stagingFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "staging"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: devFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: seedData1.environment.slug
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Check imported secret exist", async () => {
|
||||||
|
await createSecretV2({
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY",
|
||||||
|
value: "stage-value"
|
||||||
|
});
|
||||||
|
|
||||||
|
await createSecretV2({
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "PROD_KEY",
|
||||||
|
value: "prod-value"
|
||||||
|
});
|
||||||
|
|
||||||
|
const secret = await getSecretByNameV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(secret.secretKey).toBe("STAGING_KEY");
|
||||||
|
expect(secret.secretValue).toBe("stage-value");
|
||||||
|
|
||||||
|
const listSecrets = await getSecretsV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
expect(listSecrets.imports).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secrets: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "STAGING_KEY",
|
||||||
|
secretValue: "stage-value"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
secrets: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "PROD_KEY",
|
||||||
|
secretValue: "prod-value"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await deleteSecretV2({
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY"
|
||||||
|
});
|
||||||
|
await deleteSecretV2({
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "PROD_KEY"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// dev -> stage, prod
|
||||||
|
describe.each([{ path: "/" }, { path: "/deep" }])(
|
||||||
|
"Secret import one source to multiple destination pattern testing - %path",
|
||||||
|
({ path: testSuitePath }) => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
let prodFolder: { id: string };
|
||||||
|
let stagingFolder: { id: string };
|
||||||
|
let devFolder: { id: string };
|
||||||
|
|
||||||
|
if (testSuitePath !== "/") {
|
||||||
|
prodFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
|
||||||
|
stagingFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
|
||||||
|
devFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const stageImportFromDev = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
importPath: testSuitePath,
|
||||||
|
importEnv: seedData1.environment.slug
|
||||||
|
});
|
||||||
|
|
||||||
|
const prodImportFromDev = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
importPath: testSuitePath,
|
||||||
|
importEnv: seedData1.environment.slug
|
||||||
|
});
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
await deleteSecretImport({
|
||||||
|
id: prodImportFromDev.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "prod",
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
|
||||||
|
await deleteSecretImport({
|
||||||
|
id: stageImportFromDev.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "staging",
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
|
||||||
|
if (prodFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: prodFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "prod"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stagingFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: stagingFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "staging"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: devFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: seedData1.environment.slug
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Check imported secret exist", async () => {
|
||||||
|
await createSecretV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY",
|
||||||
|
value: "stage-value"
|
||||||
|
});
|
||||||
|
|
||||||
|
await createSecretV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "PROD_KEY",
|
||||||
|
value: "prod-value"
|
||||||
|
});
|
||||||
|
|
||||||
|
const stagingSecret = await getSecretByNameV2({
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(stagingSecret.secretKey).toBe("STAGING_KEY");
|
||||||
|
expect(stagingSecret.secretValue).toBe("stage-value");
|
||||||
|
|
||||||
|
const prodSecret = await getSecretByNameV2({
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "PROD_KEY"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prodSecret.secretKey).toBe("PROD_KEY");
|
||||||
|
expect(prodSecret.secretValue).toBe("prod-value");
|
||||||
|
|
||||||
|
await deleteSecretV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY"
|
||||||
|
});
|
||||||
|
await deleteSecretV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "PROD_KEY"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
406
backend/e2e-test/routes/v1/secret-replication.spec.ts
Normal file
406
backend/e2e-test/routes/v1/secret-replication.spec.ts
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
import { createFolder, deleteFolder } from "e2e-test/testUtils/folders";
|
||||||
|
import { createSecretImport, deleteSecretImport } from "e2e-test/testUtils/secret-imports";
|
||||||
|
import { createSecretV2, deleteSecretV2, getSecretByNameV2, getSecretsV2 } from "e2e-test/testUtils/secrets";
|
||||||
|
|
||||||
|
import { seedData1 } from "@app/db/seed-data";
|
||||||
|
|
||||||
|
// dev <- stage <- prod
|
||||||
|
describe.each([{ secretPath: "/" }, { secretPath: "/deep" }])(
|
||||||
|
"Secret replication waterfall pattern testing - %secretPath",
|
||||||
|
({ secretPath: testSuitePath }) => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
let prodFolder: { id: string };
|
||||||
|
let stagingFolder: { id: string };
|
||||||
|
let devFolder: { id: string };
|
||||||
|
|
||||||
|
if (testSuitePath !== "/") {
|
||||||
|
prodFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
|
||||||
|
stagingFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
|
||||||
|
devFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const devImportFromStage = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
importPath: testSuitePath,
|
||||||
|
importEnv: "staging",
|
||||||
|
isReplication: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const stageImportFromProd = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
importPath: testSuitePath,
|
||||||
|
importEnv: "prod",
|
||||||
|
isReplication: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
await deleteSecretImport({
|
||||||
|
id: stageImportFromProd.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "staging",
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
|
||||||
|
await deleteSecretImport({
|
||||||
|
id: devImportFromStage.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
|
||||||
|
if (prodFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: prodFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "prod"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stagingFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: stagingFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "staging"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: devFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: seedData1.environment.slug
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Check one level imported secret exist", async () => {
|
||||||
|
await createSecretV2({
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY",
|
||||||
|
value: "stage-value"
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait for 5 second for replication to finish
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 5000); // time to breathe for db
|
||||||
|
});
|
||||||
|
|
||||||
|
const secret = await getSecretByNameV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(secret.secretKey).toBe("STAGING_KEY");
|
||||||
|
expect(secret.secretValue).toBe("stage-value");
|
||||||
|
|
||||||
|
const listSecrets = await getSecretsV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(listSecrets.imports).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secrets: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "STAGING_KEY",
|
||||||
|
secretValue: "stage-value"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await deleteSecretV2({
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Check two level imported secret exist", async () => {
|
||||||
|
await createSecretV2({
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "PROD_KEY",
|
||||||
|
value: "prod-value"
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait for 5 second for replication to finish
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 5000); // time to breathe for db
|
||||||
|
});
|
||||||
|
|
||||||
|
const secret = await getSecretByNameV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "PROD_KEY"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(secret.secretKey).toBe("PROD_KEY");
|
||||||
|
expect(secret.secretValue).toBe("prod-value");
|
||||||
|
|
||||||
|
const listSecrets = await getSecretsV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
expect(listSecrets.imports).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secrets: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "PROD_KEY",
|
||||||
|
secretValue: "prod-value"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await deleteSecretV2({
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "PROD_KEY"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ timeout: 30000 }
|
||||||
|
);
|
||||||
|
|
||||||
|
// dev <- stage, dev <- prod
|
||||||
|
describe.each([{ path: "/" }, { path: "/deep" }])(
|
||||||
|
"Secret replication 1-N pattern testing - %path",
|
||||||
|
({ path: testSuitePath }) => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
let prodFolder: { id: string };
|
||||||
|
let stagingFolder: { id: string };
|
||||||
|
let devFolder: { id: string };
|
||||||
|
|
||||||
|
if (testSuitePath !== "/") {
|
||||||
|
prodFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
|
||||||
|
stagingFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
|
||||||
|
devFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const devImportFromStage = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
importPath: testSuitePath,
|
||||||
|
importEnv: "staging",
|
||||||
|
isReplication: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const devImportFromProd = await createSecretImport({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
importPath: testSuitePath,
|
||||||
|
importEnv: "prod",
|
||||||
|
isReplication: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
await deleteSecretImport({
|
||||||
|
id: devImportFromProd.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
|
||||||
|
await deleteSecretImport({
|
||||||
|
id: devImportFromStage.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
|
||||||
|
if (prodFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: prodFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "prod"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stagingFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: stagingFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: "staging"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devFolder) {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: devFolder.id,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environmentSlug: seedData1.environment.slug
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Check imported secret exist", async () => {
|
||||||
|
await createSecretV2({
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY",
|
||||||
|
value: "stage-value"
|
||||||
|
});
|
||||||
|
|
||||||
|
await createSecretV2({
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "PROD_KEY",
|
||||||
|
value: "prod-value"
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait for 5 second for replication to finish
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 5000); // time to breathe for db
|
||||||
|
});
|
||||||
|
|
||||||
|
const secret = await getSecretByNameV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(secret.secretKey).toBe("STAGING_KEY");
|
||||||
|
expect(secret.secretValue).toBe("stage-value");
|
||||||
|
|
||||||
|
const listSecrets = await getSecretsV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
expect(listSecrets.imports).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secrets: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "STAGING_KEY",
|
||||||
|
secretValue: "stage-value"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
secrets: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "PROD_KEY",
|
||||||
|
secretValue: "prod-value"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await deleteSecretV2({
|
||||||
|
environmentSlug: "staging",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "STAGING_KEY"
|
||||||
|
});
|
||||||
|
await deleteSecretV2({
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
secretPath: testSuitePath,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "PROD_KEY"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ timeout: 30000 }
|
||||||
|
);
|
@ -510,7 +510,7 @@ describe("Service token fail cases", async () => {
|
|||||||
authorization: `Bearer ${serviceToken}`
|
authorization: `Bearer ${serviceToken}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
expect(fetchSecrets.statusCode).toBe(401);
|
expect(fetchSecrets.statusCode).toBe(403);
|
||||||
expect(fetchSecrets.json().error).toBe("PermissionDenied");
|
expect(fetchSecrets.json().error).toBe("PermissionDenied");
|
||||||
await deleteServiceToken();
|
await deleteServiceToken();
|
||||||
});
|
});
|
||||||
@ -532,7 +532,7 @@ describe("Service token fail cases", async () => {
|
|||||||
authorization: `Bearer ${serviceToken}`
|
authorization: `Bearer ${serviceToken}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
expect(fetchSecrets.statusCode).toBe(401);
|
expect(fetchSecrets.statusCode).toBe(403);
|
||||||
expect(fetchSecrets.json().error).toBe("PermissionDenied");
|
expect(fetchSecrets.json().error).toBe("PermissionDenied");
|
||||||
await deleteServiceToken();
|
await deleteServiceToken();
|
||||||
});
|
});
|
||||||
@ -557,7 +557,7 @@ describe("Service token fail cases", async () => {
|
|||||||
authorization: `Bearer ${serviceToken}`
|
authorization: `Bearer ${serviceToken}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
expect(writeSecrets.statusCode).toBe(401);
|
expect(writeSecrets.statusCode).toBe(403);
|
||||||
expect(writeSecrets.json().error).toBe("PermissionDenied");
|
expect(writeSecrets.json().error).toBe("PermissionDenied");
|
||||||
|
|
||||||
// but read access should still work fine
|
// but read access should still work fine
|
||||||
|
344
backend/e2e-test/routes/v3/secret-reference.spec.ts
Normal file
344
backend/e2e-test/routes/v3/secret-reference.spec.ts
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
import { createFolder, deleteFolder } from "e2e-test/testUtils/folders";
|
||||||
|
import { createSecretImport, deleteSecretImport } from "e2e-test/testUtils/secret-imports";
|
||||||
|
import { createSecretV2, deleteSecretV2, getSecretByNameV2, getSecretsV2 } from "e2e-test/testUtils/secrets";
|
||||||
|
|
||||||
|
import { seedData1 } from "@app/db/seed-data";
|
||||||
|
|
||||||
|
describe("Secret expansion", () => {
|
||||||
|
const projectId = seedData1.projectV3.id;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const prodRootFolder = await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
name: "deep"
|
||||||
|
});
|
||||||
|
|
||||||
|
await createFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/deep",
|
||||||
|
name: "nested"
|
||||||
|
});
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
await deleteFolder({
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
secretPath: "/",
|
||||||
|
id: prodRootFolder.id,
|
||||||
|
workspaceId: projectId,
|
||||||
|
environmentSlug: "prod"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Local secret reference", async () => {
|
||||||
|
const secrets = [
|
||||||
|
{
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "HELLO",
|
||||||
|
value: "world"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "TEST",
|
||||||
|
// eslint-disable-next-line
|
||||||
|
value: "hello ${HELLO}"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const secret of secrets) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await createSecretV2(secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandedSecret = await getSecretByNameV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "TEST"
|
||||||
|
});
|
||||||
|
expect(expandedSecret.secretValue).toBe("hello world");
|
||||||
|
|
||||||
|
const listSecrets = await getSecretsV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
expect(listSecrets.secrets).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "TEST",
|
||||||
|
secretValue: "hello world"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(secrets.map((el) => deleteSecretV2(el)));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Cross environment secret reference", async () => {
|
||||||
|
const secrets = [
|
||||||
|
{
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/deep",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "DEEP_KEY_1",
|
||||||
|
value: "testing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/deep/nested",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "NESTED_KEY_1",
|
||||||
|
value: "reference"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/deep/nested",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "NESTED_KEY_2",
|
||||||
|
// eslint-disable-next-line
|
||||||
|
value: "secret ${NESTED_KEY_1}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "KEY",
|
||||||
|
// eslint-disable-next-line
|
||||||
|
value: "hello ${prod.deep.DEEP_KEY_1} ${prod.deep.nested.NESTED_KEY_2}"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const secret of secrets) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await createSecretV2(secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandedSecret = await getSecretByNameV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "KEY"
|
||||||
|
});
|
||||||
|
expect(expandedSecret.secretValue).toBe("hello testing secret reference");
|
||||||
|
|
||||||
|
const listSecrets = await getSecretsV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
expect(listSecrets.secrets).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "KEY",
|
||||||
|
secretValue: "hello testing secret reference"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(secrets.map((el) => deleteSecretV2(el)));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Non replicated secret import secret expansion on local reference and nested reference", async () => {
|
||||||
|
const secrets = [
|
||||||
|
{
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/deep",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "DEEP_KEY_1",
|
||||||
|
value: "testing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/deep/nested",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "NESTED_KEY_1",
|
||||||
|
value: "reference"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/deep/nested",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "NESTED_KEY_2",
|
||||||
|
// eslint-disable-next-line
|
||||||
|
value: "secret ${NESTED_KEY_1} ${prod.deep.DEEP_KEY_1}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "KEY",
|
||||||
|
// eslint-disable-next-line
|
||||||
|
value: "hello world"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const secret of secrets) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await createSecretV2(secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
const secretImportFromProdToDev = await createSecretImport({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
importEnv: "prod",
|
||||||
|
importPath: "/deep/nested"
|
||||||
|
});
|
||||||
|
|
||||||
|
const listSecrets = await getSecretsV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
expect(listSecrets.imports).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretPath: "/deep/nested",
|
||||||
|
environment: "prod",
|
||||||
|
secrets: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "NESTED_KEY_1",
|
||||||
|
secretValue: "reference"
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "NESTED_KEY_2",
|
||||||
|
secretValue: "secret reference testing"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(secrets.map((el) => deleteSecretV2(el)));
|
||||||
|
await deleteSecretImport({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
id: secretImportFromProdToDev.id,
|
||||||
|
secretPath: "/"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
"Replicated secret import secret expansion on local reference and nested reference",
|
||||||
|
async () => {
|
||||||
|
const secrets = [
|
||||||
|
{
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/deep",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "DEEP_KEY_1",
|
||||||
|
value: "testing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/deep/nested",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "NESTED_KEY_1",
|
||||||
|
value: "reference"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
environmentSlug: "prod",
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/deep/nested",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "NESTED_KEY_2",
|
||||||
|
// eslint-disable-next-line
|
||||||
|
value: "secret ${NESTED_KEY_1} ${prod.deep.DEEP_KEY_1}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
key: "KEY",
|
||||||
|
// eslint-disable-next-line
|
||||||
|
value: "hello world"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const secret of secrets) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await createSecretV2(secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
const secretImportFromProdToDev = await createSecretImport({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
importEnv: "prod",
|
||||||
|
importPath: "/deep/nested",
|
||||||
|
isReplication: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait for 5 second for replication to finish
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 5000); // time to breathe for db
|
||||||
|
});
|
||||||
|
|
||||||
|
const listSecrets = await getSecretsV2({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath: "/",
|
||||||
|
authToken: jwtAuthToken
|
||||||
|
});
|
||||||
|
expect(listSecrets.imports).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretPath: `/__reserve_replication_${secretImportFromProdToDev.id}`,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
secrets: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "NESTED_KEY_1",
|
||||||
|
secretValue: "reference"
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: "NESTED_KEY_2",
|
||||||
|
secretValue: "secret reference testing"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(secrets.map((el) => deleteSecretV2(el)));
|
||||||
|
await deleteSecretImport({
|
||||||
|
environmentSlug: seedData1.environment.slug,
|
||||||
|
workspaceId: projectId,
|
||||||
|
authToken: jwtAuthToken,
|
||||||
|
id: secretImportFromProdToDev.id,
|
||||||
|
secretPath: "/"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ timeout: 10000 }
|
||||||
|
);
|
||||||
|
});
|
@ -8,6 +8,7 @@ type TRawSecret = {
|
|||||||
secretComment?: string;
|
secretComment?: string;
|
||||||
version: number;
|
version: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createSecret = async (dto: { path: string; key: string; value: string; comment: string; type?: SecretType }) => {
|
const createSecret = async (dto: { path: string; key: string; value: string; comment: string; type?: SecretType }) => {
|
||||||
const createSecretReqBody = {
|
const createSecretReqBody = {
|
||||||
workspaceId: seedData1.projectV3.id,
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
@ -1075,7 +1075,7 @@ describe("Secret V3 Raw Router Without E2EE enabled", async () => {
|
|||||||
},
|
},
|
||||||
body: createSecretReqBody
|
body: createSecretReqBody
|
||||||
});
|
});
|
||||||
expect(createSecRes.statusCode).toBe(400);
|
expect(createSecRes.statusCode).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Update secret raw", async () => {
|
test("Update secret raw", async () => {
|
||||||
@ -1093,7 +1093,7 @@ describe("Secret V3 Raw Router Without E2EE enabled", async () => {
|
|||||||
},
|
},
|
||||||
body: updateSecretReqBody
|
body: updateSecretReqBody
|
||||||
});
|
});
|
||||||
expect(updateSecRes.statusCode).toBe(400);
|
expect(updateSecRes.statusCode).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Delete secret raw", async () => {
|
test("Delete secret raw", async () => {
|
||||||
@ -1110,6 +1110,6 @@ describe("Secret V3 Raw Router Without E2EE enabled", async () => {
|
|||||||
},
|
},
|
||||||
body: deletedSecretReqBody
|
body: deletedSecretReqBody
|
||||||
});
|
});
|
||||||
expect(deletedSecRes.statusCode).toBe(400);
|
expect(deletedSecRes.statusCode).toBe(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
73
backend/e2e-test/testUtils/folders.ts
Normal file
73
backend/e2e-test/testUtils/folders.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
type TFolder = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createFolder = async (dto: {
|
||||||
|
workspaceId: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
secretPath: string;
|
||||||
|
name: string;
|
||||||
|
authToken: string;
|
||||||
|
}) => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v1/folders`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${dto.authToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: dto.workspaceId,
|
||||||
|
environment: dto.environmentSlug,
|
||||||
|
name: dto.name,
|
||||||
|
path: dto.secretPath
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
return res.json().folder as TFolder;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteFolder = async (dto: {
|
||||||
|
workspaceId: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
secretPath: string;
|
||||||
|
id: string;
|
||||||
|
authToken: string;
|
||||||
|
}) => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/api/v1/folders/${dto.id}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${dto.authToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: dto.workspaceId,
|
||||||
|
environment: dto.environmentSlug,
|
||||||
|
path: dto.secretPath
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
return res.json().folder as TFolder;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listFolders = async (dto: {
|
||||||
|
workspaceId: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
secretPath: string;
|
||||||
|
authToken: string;
|
||||||
|
}) => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v1/folders`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${dto.authToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: dto.workspaceId,
|
||||||
|
environment: dto.environmentSlug,
|
||||||
|
path: dto.secretPath
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
return res.json().folders as TFolder[];
|
||||||
|
};
|
93
backend/e2e-test/testUtils/secret-imports.ts
Normal file
93
backend/e2e-test/testUtils/secret-imports.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
type TSecretImport = {
|
||||||
|
id: string;
|
||||||
|
importEnv: {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
importPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createSecretImport = async (dto: {
|
||||||
|
workspaceId: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
isReplication?: boolean;
|
||||||
|
secretPath: string;
|
||||||
|
importPath: string;
|
||||||
|
importEnv: string;
|
||||||
|
authToken: string;
|
||||||
|
}) => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v1/secret-imports`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${dto.authToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: dto.workspaceId,
|
||||||
|
environment: dto.environmentSlug,
|
||||||
|
isReplication: dto.isReplication,
|
||||||
|
path: dto.secretPath,
|
||||||
|
import: {
|
||||||
|
environment: dto.importEnv,
|
||||||
|
path: dto.importPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("secretImport");
|
||||||
|
return payload.secretImport as TSecretImport;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteSecretImport = async (dto: {
|
||||||
|
workspaceId: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
secretPath: string;
|
||||||
|
authToken: string;
|
||||||
|
id: string;
|
||||||
|
}) => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/api/v1/secret-imports/${dto.id}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${dto.authToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: dto.workspaceId,
|
||||||
|
environment: dto.environmentSlug,
|
||||||
|
path: dto.secretPath
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("secretImport");
|
||||||
|
return payload.secretImport as TSecretImport;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listSecretImport = async (dto: {
|
||||||
|
workspaceId: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
secretPath: string;
|
||||||
|
authToken: string;
|
||||||
|
}) => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v1/secret-imports`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${dto.authToken}`
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
workspaceId: dto.workspaceId,
|
||||||
|
environment: dto.environmentSlug,
|
||||||
|
path: dto.secretPath
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("secretImports");
|
||||||
|
return payload.secretImports as TSecretImport[];
|
||||||
|
};
|
128
backend/e2e-test/testUtils/secrets.ts
Normal file
128
backend/e2e-test/testUtils/secrets.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { SecretType } from "@app/db/schemas";
|
||||||
|
|
||||||
|
type TRawSecret = {
|
||||||
|
secretKey: string;
|
||||||
|
secretValue: string;
|
||||||
|
secretComment?: string;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createSecretV2 = async (dto: {
|
||||||
|
workspaceId: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
secretPath: string;
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
comment?: string;
|
||||||
|
authToken: string;
|
||||||
|
type?: SecretType;
|
||||||
|
}) => {
|
||||||
|
const createSecretReqBody = {
|
||||||
|
workspaceId: dto.workspaceId,
|
||||||
|
environment: dto.environmentSlug,
|
||||||
|
type: dto.type || SecretType.Shared,
|
||||||
|
secretPath: dto.secretPath,
|
||||||
|
secretKey: dto.key,
|
||||||
|
secretValue: dto.value,
|
||||||
|
secretComment: dto.comment
|
||||||
|
};
|
||||||
|
const createSecRes = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v3/secrets/raw/${dto.key}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${dto.authToken}`
|
||||||
|
},
|
||||||
|
body: createSecretReqBody
|
||||||
|
});
|
||||||
|
expect(createSecRes.statusCode).toBe(200);
|
||||||
|
const createdSecretPayload = JSON.parse(createSecRes.payload);
|
||||||
|
expect(createdSecretPayload).toHaveProperty("secret");
|
||||||
|
return createdSecretPayload.secret as TRawSecret;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteSecretV2 = async (dto: {
|
||||||
|
workspaceId: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
secretPath: string;
|
||||||
|
key: string;
|
||||||
|
authToken: string;
|
||||||
|
}) => {
|
||||||
|
const deleteSecRes = await testServer.inject({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/api/v3/secrets/raw/${dto.key}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${dto.authToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: dto.workspaceId,
|
||||||
|
environment: dto.environmentSlug,
|
||||||
|
secretPath: dto.secretPath
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(deleteSecRes.statusCode).toBe(200);
|
||||||
|
const updatedSecretPayload = JSON.parse(deleteSecRes.payload);
|
||||||
|
expect(updatedSecretPayload).toHaveProperty("secret");
|
||||||
|
return updatedSecretPayload.secret as TRawSecret;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSecretByNameV2 = async (dto: {
|
||||||
|
workspaceId: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
secretPath: string;
|
||||||
|
key: string;
|
||||||
|
authToken: string;
|
||||||
|
}) => {
|
||||||
|
const response = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v3/secrets/raw/${dto.key}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${dto.authToken}`
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
workspaceId: dto.workspaceId,
|
||||||
|
environment: dto.environmentSlug,
|
||||||
|
secretPath: dto.secretPath,
|
||||||
|
expandSecretReferences: "true",
|
||||||
|
include_imports: "true"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(response.payload);
|
||||||
|
expect(payload).toHaveProperty("secret");
|
||||||
|
return payload.secret as TRawSecret;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSecretsV2 = async (dto: {
|
||||||
|
workspaceId: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
secretPath: string;
|
||||||
|
authToken: string;
|
||||||
|
}) => {
|
||||||
|
const getSecretsResponse = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v3/secrets/raw`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${dto.authToken}`
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
workspaceId: dto.workspaceId,
|
||||||
|
environment: dto.environmentSlug,
|
||||||
|
secretPath: dto.secretPath,
|
||||||
|
expandSecretReferences: "true",
|
||||||
|
include_imports: "true"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(getSecretsResponse.statusCode).toBe(200);
|
||||||
|
const getSecretsPayload = JSON.parse(getSecretsResponse.payload);
|
||||||
|
expect(getSecretsPayload).toHaveProperty("secrets");
|
||||||
|
expect(getSecretsPayload).toHaveProperty("imports");
|
||||||
|
return getSecretsPayload as {
|
||||||
|
secrets: TRawSecret[];
|
||||||
|
imports: {
|
||||||
|
secretPath: string;
|
||||||
|
environment: string;
|
||||||
|
folderId: string;
|
||||||
|
secrets: TRawSecret[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
@ -11,10 +11,11 @@ import { initLogger } from "@app/lib/logger";
|
|||||||
import { main } from "@app/server/app";
|
import { main } from "@app/server/app";
|
||||||
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
import { mockQueue } from "./mocks/queue";
|
|
||||||
import { mockSmtpServer } from "./mocks/smtp";
|
import { mockSmtpServer } from "./mocks/smtp";
|
||||||
import { mockKeyStore } from "./mocks/keystore";
|
|
||||||
import { initDbConnection } from "@app/db";
|
import { initDbConnection } from "@app/db";
|
||||||
|
import { queueServiceFactory } from "@app/queue";
|
||||||
|
import { keyStoreFactory } from "@app/keystore/keystore";
|
||||||
|
import { Redis } from "ioredis";
|
||||||
|
|
||||||
dotenv.config({ path: path.join(__dirname, "../../.env.test"), debug: true });
|
dotenv.config({ path: path.join(__dirname, "../../.env.test"), debug: true });
|
||||||
export default {
|
export default {
|
||||||
@ -28,19 +29,31 @@ export default {
|
|||||||
dbRootCert: cfg.DB_ROOT_CERT
|
dbRootCert: cfg.DB_ROOT_CERT
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const redis = new Redis(cfg.REDIS_URL);
|
||||||
|
await redis.flushdb("SYNC");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
await db.migrate.rollback(
|
||||||
|
{
|
||||||
|
directory: path.join(__dirname, "../src/db/migrations"),
|
||||||
|
extension: "ts",
|
||||||
|
tableName: "infisical_migrations"
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
await db.migrate.latest({
|
await db.migrate.latest({
|
||||||
directory: path.join(__dirname, "../src/db/migrations"),
|
directory: path.join(__dirname, "../src/db/migrations"),
|
||||||
extension: "ts",
|
extension: "ts",
|
||||||
tableName: "infisical_migrations"
|
tableName: "infisical_migrations"
|
||||||
});
|
});
|
||||||
|
|
||||||
await db.seed.run({
|
await db.seed.run({
|
||||||
directory: path.join(__dirname, "../src/db/seeds"),
|
directory: path.join(__dirname, "../src/db/seeds"),
|
||||||
extension: "ts"
|
extension: "ts"
|
||||||
});
|
});
|
||||||
const smtp = mockSmtpServer();
|
const smtp = mockSmtpServer();
|
||||||
const queue = mockQueue();
|
const queue = queueServiceFactory(cfg.REDIS_URL);
|
||||||
const keyStore = mockKeyStore();
|
const keyStore = keyStoreFactory(cfg.REDIS_URL);
|
||||||
const server = await main({ db, smtp, logger, queue, keyStore });
|
const server = await main({ db, smtp, logger, queue, keyStore });
|
||||||
// @ts-expect-error type
|
// @ts-expect-error type
|
||||||
globalThis.testServer = server;
|
globalThis.testServer = server;
|
||||||
@ -58,10 +71,12 @@ export default {
|
|||||||
{ expiresIn: cfg.JWT_AUTH_LIFETIME }
|
{ expiresIn: cfg.JWT_AUTH_LIFETIME }
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line
|
||||||
console.log("[TEST] Error setting up environment", error);
|
console.log("[TEST] Error setting up environment", error);
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// custom setup
|
// custom setup
|
||||||
return {
|
return {
|
||||||
async teardown() {
|
async teardown() {
|
||||||
@ -80,6 +95,9 @@ export default {
|
|||||||
},
|
},
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await redis.flushdb("ASYNC");
|
||||||
|
redis.disconnect();
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
5620
backend/package-lock.json
generated
5620
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -44,13 +44,21 @@
|
|||||||
"test:e2e-watch": "vitest -c vitest.e2e.config.ts --bail=1",
|
"test:e2e-watch": "vitest -c vitest.e2e.config.ts --bail=1",
|
||||||
"test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts",
|
"test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts",
|
||||||
"generate:component": "tsx ./scripts/create-backend-file.ts",
|
"generate:component": "tsx ./scripts/create-backend-file.ts",
|
||||||
"generate:schema": "tsx ./scripts/generate-schema-types.ts",
|
"generate:schema": "tsx ./scripts/generate-schema-types.ts && eslint --fix --ext ts ./src/db/schemas",
|
||||||
|
"auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:latest",
|
||||||
|
"auditlog-migration:up": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:up",
|
||||||
|
"auditlog-migration:down": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:down",
|
||||||
|
"auditlog-migration:list": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:list",
|
||||||
|
"auditlog-migration:status": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:status",
|
||||||
|
"auditlog-migration:rollback": "knex --knexfile ./src/db/auditlog-knexfile.ts migrate:rollback",
|
||||||
"migration:new": "tsx ./scripts/create-migration.ts",
|
"migration:new": "tsx ./scripts/create-migration.ts",
|
||||||
"migration:up": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:up",
|
"migration:up": "npm run auditlog-migration:up && knex --knexfile ./src/db/knexfile.ts --client pg migrate:up",
|
||||||
"migration:down": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:down",
|
"migration:down": "npm run auditlog-migration:down && knex --knexfile ./src/db/knexfile.ts --client pg migrate:down",
|
||||||
"migration:list": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:list",
|
"migration:list": "npm run auditlog-migration:list && knex --knexfile ./src/db/knexfile.ts --client pg migrate:list",
|
||||||
"migration:latest": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest",
|
"migration:latest": "npm run auditlog-migration:latest && knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest",
|
||||||
"migration:rollback": "knex --knexfile ./src/db/knexfile.ts migrate:rollback",
|
"migration:status": "npm run auditlog-migration:status && knex --knexfile ./src/db/knexfile.ts --client pg migrate:status",
|
||||||
|
"migration:rollback": "npm run auditlog-migration:rollback && knex --knexfile ./src/db/knexfile.ts migrate:rollback",
|
||||||
|
"migrate:org": "tsx ./scripts/migrate-organization.ts",
|
||||||
"seed:new": "tsx ./scripts/create-seed-file.ts",
|
"seed:new": "tsx ./scripts/create-seed-file.ts",
|
||||||
"seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run",
|
"seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run",
|
||||||
"db:reset": "npm run migration:rollback -- --all && npm run migration:latest"
|
"db:reset": "npm run migration:rollback -- --all && npm run migration:latest"
|
||||||
@ -79,6 +87,7 @@
|
|||||||
"@types/prompt-sync": "^4.2.3",
|
"@types/prompt-sync": "^4.2.3",
|
||||||
"@types/resolve": "^1.20.6",
|
"@types/resolve": "^1.20.6",
|
||||||
"@types/safe-regex": "^1.1.6",
|
"@types/safe-regex": "^1.1.6",
|
||||||
|
"@types/sjcl": "^1.0.34",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||||
"@typescript-eslint/parser": "^6.20.0",
|
"@typescript-eslint/parser": "^6.20.0",
|
||||||
@ -102,33 +111,38 @@
|
|||||||
"tsup": "^8.0.1",
|
"tsup": "^8.0.1",
|
||||||
"tsx": "^4.4.0",
|
"tsx": "^4.4.0",
|
||||||
"typescript": "^5.3.2",
|
"typescript": "^5.3.2",
|
||||||
"vite-tsconfig-paths": "^4.2.2",
|
|
||||||
"vitest": "^1.2.2"
|
"vitest": "^1.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-elasticache": "^3.637.0",
|
||||||
"@aws-sdk/client-iam": "^3.525.0",
|
"@aws-sdk/client-iam": "^3.525.0",
|
||||||
"@aws-sdk/client-kms": "^3.609.0",
|
"@aws-sdk/client-kms": "^3.609.0",
|
||||||
"@aws-sdk/client-secrets-manager": "^3.504.0",
|
"@aws-sdk/client-secrets-manager": "^3.504.0",
|
||||||
"@aws-sdk/client-sts": "^3.600.0",
|
"@aws-sdk/client-sts": "^3.600.0",
|
||||||
"@casl/ability": "^6.5.0",
|
"@casl/ability": "^6.5.0",
|
||||||
|
"@elastic/elasticsearch": "^8.15.0",
|
||||||
"@fastify/cookie": "^9.3.1",
|
"@fastify/cookie": "^9.3.1",
|
||||||
"@fastify/cors": "^8.5.0",
|
"@fastify/cors": "^8.5.0",
|
||||||
"@fastify/etag": "^5.1.0",
|
"@fastify/etag": "^5.1.0",
|
||||||
"@fastify/formbody": "^7.4.0",
|
"@fastify/formbody": "^7.4.0",
|
||||||
"@fastify/helmet": "^11.1.1",
|
"@fastify/helmet": "^11.1.1",
|
||||||
|
"@fastify/multipart": "8.3.0",
|
||||||
"@fastify/passport": "^2.4.0",
|
"@fastify/passport": "^2.4.0",
|
||||||
"@fastify/rate-limit": "^9.0.0",
|
"@fastify/rate-limit": "^9.0.0",
|
||||||
"@fastify/session": "^10.7.0",
|
"@fastify/session": "^10.7.0",
|
||||||
"@fastify/swagger": "^8.14.0",
|
"@fastify/swagger": "^8.14.0",
|
||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
|
"@octokit/auth-app": "^7.1.1",
|
||||||
"@octokit/plugin-retry": "^5.0.5",
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
"@peculiar/asn1-schema": "^2.3.8",
|
"@peculiar/asn1-schema": "^2.3.8",
|
||||||
"@peculiar/x509": "^1.10.0",
|
"@peculiar/x509": "^1.12.1",
|
||||||
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
||||||
"@sindresorhus/slugify": "1.1.0",
|
"@sindresorhus/slugify": "1.1.0",
|
||||||
|
"@slack/oauth": "^3.0.1",
|
||||||
|
"@slack/web-api": "^7.3.4",
|
||||||
"@team-plain/typescript-sdk": "^4.6.1",
|
"@team-plain/typescript-sdk": "^4.6.1",
|
||||||
"@ucast/mongo2js": "^1.3.4",
|
"@ucast/mongo2js": "^1.3.4",
|
||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
@ -142,11 +156,12 @@
|
|||||||
"connect-redis": "^7.1.1",
|
"connect-redis": "^7.1.1",
|
||||||
"cron": "^3.1.7",
|
"cron": "^3.1.7",
|
||||||
"dotenv": "^16.4.1",
|
"dotenv": "^16.4.1",
|
||||||
"fastify": "^4.26.0",
|
"fastify": "^4.28.1",
|
||||||
"fastify-plugin": "^4.5.1",
|
"fastify-plugin": "^4.5.1",
|
||||||
"google-auth-library": "^9.9.0",
|
"google-auth-library": "^9.9.0",
|
||||||
"googleapis": "^137.1.0",
|
"googleapis": "^137.1.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
|
"hdb": "^0.19.10",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.3.2",
|
||||||
"jmespath": "^0.16.0",
|
"jmespath": "^0.16.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
@ -154,8 +169,10 @@
|
|||||||
"jwks-rsa": "^3.1.0",
|
"jwks-rsa": "^3.1.0",
|
||||||
"knex": "^3.0.1",
|
"knex": "^3.0.1",
|
||||||
"ldapjs": "^3.0.7",
|
"ldapjs": "^3.0.7",
|
||||||
|
"ldif": "0.5.1",
|
||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
|
"mongodb": "^6.8.1",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"mysql2": "^3.9.8",
|
"mysql2": "^3.9.8",
|
||||||
"nanoid": "^3.3.4",
|
"nanoid": "^3.3.4",
|
||||||
@ -171,10 +188,15 @@
|
|||||||
"pg-query-stream": "^4.5.3",
|
"pg-query-stream": "^4.5.3",
|
||||||
"picomatch": "^3.0.1",
|
"picomatch": "^3.0.1",
|
||||||
"pino": "^8.16.2",
|
"pino": "^8.16.2",
|
||||||
|
"pkijs": "^3.2.4",
|
||||||
"posthog-node": "^3.6.2",
|
"posthog-node": "^3.6.2",
|
||||||
"probot": "^13.0.0",
|
"probot": "^13.3.8",
|
||||||
"safe-regex": "^2.1.1",
|
"safe-regex": "^2.1.1",
|
||||||
|
"scim-patch": "^0.8.3",
|
||||||
|
"scim2-parse-filter": "^0.2.10",
|
||||||
|
"sjcl": "^1.0.8",
|
||||||
"smee-client": "^2.0.0",
|
"smee-client": "^2.0.0",
|
||||||
|
"snowflake-sdk": "^1.14.0",
|
||||||
"tedious": "^18.2.1",
|
"tedious": "^18.2.1",
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
"tweetnacl-util": "^0.15.1",
|
"tweetnacl-util": "^0.15.1",
|
||||||
|
@ -90,7 +90,12 @@ const main = async () => {
|
|||||||
.whereRaw("table_schema = current_schema()")
|
.whereRaw("table_schema = current_schema()")
|
||||||
.select<{ tableName: string }[]>("table_name as tableName")
|
.select<{ tableName: string }[]>("table_name as tableName")
|
||||||
.orderBy("table_name")
|
.orderBy("table_name")
|
||||||
).filter((el) => !el.tableName.includes("_migrations"));
|
).filter(
|
||||||
|
(el) =>
|
||||||
|
!el.tableName.includes("_migrations") &&
|
||||||
|
!el.tableName.includes("audit_logs_") &&
|
||||||
|
el.tableName !== "intermediate_audit_logs"
|
||||||
|
);
|
||||||
|
|
||||||
for (let i = 0; i < tables.length; i += 1) {
|
for (let i = 0; i < tables.length; i += 1) {
|
||||||
const { tableName } = tables[i];
|
const { tableName } = tables[i];
|
||||||
|
84
backend/scripts/migrate-organization.ts
Normal file
84
backend/scripts/migrate-organization.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
import promptSync from "prompt-sync";
|
||||||
|
import { execSync } from "child_process";
|
||||||
|
import path from "path";
|
||||||
|
import { existsSync } from "fs";
|
||||||
|
|
||||||
|
const prompt = promptSync({
|
||||||
|
sigint: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const exportDb = () => {
|
||||||
|
const exportHost = prompt("Enter your Postgres Host to migrate from: ");
|
||||||
|
const exportPort = prompt("Enter your Postgres Port to migrate from [Default = 5432]: ") ?? "5432";
|
||||||
|
const exportUser = prompt("Enter your Postgres User to migrate from: [Default = infisical]: ") ?? "infisical";
|
||||||
|
const exportPassword = prompt("Enter your Postgres Password to migrate from: ");
|
||||||
|
const exportDatabase = prompt("Enter your Postgres Database to migrate from [Default = infisical]: ") ?? "infisical";
|
||||||
|
|
||||||
|
// we do not include the audit_log and secret_sharing entries
|
||||||
|
execSync(
|
||||||
|
`PGDATABASE="${exportDatabase}" PGPASSWORD="${exportPassword}" PGHOST="${exportHost}" PGPORT=${exportPort} PGUSER=${exportUser} pg_dump infisical --exclude-table-data="secret_sharing" --exclude-table-data="audit_log*" > ${path.join(
|
||||||
|
__dirname,
|
||||||
|
"../src/db/dump.sql"
|
||||||
|
)}`,
|
||||||
|
{ stdio: "inherit" }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const importDbForOrg = () => {
|
||||||
|
const importHost = prompt("Enter your Postgres Host to migrate to: ");
|
||||||
|
const importPort = prompt("Enter your Postgres Port to migrate to [Default = 5432]: ") ?? "5432";
|
||||||
|
const importUser = prompt("Enter your Postgres User to migrate to: [Default = infisical]: ") ?? "infisical";
|
||||||
|
const importPassword = prompt("Enter your Postgres Password to migrate to: ");
|
||||||
|
const importDatabase = prompt("Enter your Postgres Database to migrate to [Default = infisical]: ") ?? "infisical";
|
||||||
|
const orgId = prompt("Enter the organization ID to migrate: ");
|
||||||
|
|
||||||
|
if (!existsSync(path.join(__dirname, "../src/db/dump.sql"))) {
|
||||||
|
console.log("File not found, please export the database first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
execSync(
|
||||||
|
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -f ${path.join(
|
||||||
|
__dirname,
|
||||||
|
"../src/db/dump.sql"
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
execSync(
|
||||||
|
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c "DELETE FROM public.organizations WHERE id != '${orgId}'"`
|
||||||
|
);
|
||||||
|
|
||||||
|
// delete global/instance-level resources not relevant to the organization to migrate
|
||||||
|
// users
|
||||||
|
execSync(
|
||||||
|
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c 'DELETE FROM users WHERE users.id NOT IN (SELECT org_memberships."userId" FROM org_memberships)'`
|
||||||
|
);
|
||||||
|
|
||||||
|
// identities
|
||||||
|
execSync(
|
||||||
|
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c 'DELETE FROM identities WHERE id NOT IN (SELECT "identityId" FROM identity_org_memberships)'`
|
||||||
|
);
|
||||||
|
|
||||||
|
// reset slack configuration in superAdmin
|
||||||
|
execSync(
|
||||||
|
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c 'UPDATE super_admin SET "encryptedSlackClientId" = null, "encryptedSlackClientSecret" = null'`
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("Organization migrated successfully.");
|
||||||
|
};
|
||||||
|
|
||||||
|
const main = () => {
|
||||||
|
const action = prompt(
|
||||||
|
"Enter the action to perform\n 1. Export from existing instance.\n 2. Import org to instance.\n \n Action: "
|
||||||
|
);
|
||||||
|
if (action === "1") {
|
||||||
|
exportDb();
|
||||||
|
} else if (action === "2") {
|
||||||
|
importDbForOrg();
|
||||||
|
} else {
|
||||||
|
console.log("Invalid action");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
main();
|
14
backend/src/@types/fastify.d.ts
vendored
14
backend/src/@types/fastify.d.ts
vendored
@ -7,11 +7,13 @@ import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-se
|
|||||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service";
|
import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service";
|
||||||
import { TCertificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-service";
|
import { TCertificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-service";
|
||||||
|
import { TCertificateEstServiceFactory } from "@app/ee/services/certificate-est/certificate-est-service";
|
||||||
import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service";
|
import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service";
|
||||||
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
|
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
|
||||||
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
|
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
|
||||||
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
|
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
|
||||||
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
||||||
|
import { TIdentityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
|
||||||
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TOidcConfigServiceFactory } from "@app/ee/services/oidc/oidc-config-service";
|
import { TOidcConfigServiceFactory } from "@app/ee/services/oidc/oidc-config-service";
|
||||||
@ -37,6 +39,9 @@ import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-se
|
|||||||
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
|
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
|
||||||
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
||||||
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
||||||
|
import { TCmekServiceFactory } from "@app/services/cmek/cmek-service";
|
||||||
|
import { TExternalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
|
||||||
|
import { TExternalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
|
||||||
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
||||||
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
||||||
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||||
@ -69,12 +74,14 @@ import { TSecretReplicationServiceFactory } from "@app/services/secret-replicati
|
|||||||
import { TSecretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
import { TSecretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
||||||
import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
|
import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
|
||||||
import { TServiceTokenServiceFactory } from "@app/services/service-token/service-token-service";
|
import { TServiceTokenServiceFactory } from "@app/services/service-token/service-token-service";
|
||||||
|
import { TSlackServiceFactory } from "@app/services/slack/slack-service";
|
||||||
import { TSuperAdminServiceFactory } from "@app/services/super-admin/super-admin-service";
|
import { TSuperAdminServiceFactory } from "@app/services/super-admin/super-admin-service";
|
||||||
import { TTelemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
|
import { TTelemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
import { TUserServiceFactory } from "@app/services/user/user-service";
|
import { TUserServiceFactory } from "@app/services/user/user-service";
|
||||||
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
||||||
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||||
|
import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||||
|
|
||||||
declare module "fastify" {
|
declare module "fastify" {
|
||||||
interface FastifyRequest {
|
interface FastifyRequest {
|
||||||
@ -160,6 +167,7 @@ declare module "fastify" {
|
|||||||
certificateTemplate: TCertificateTemplateServiceFactory;
|
certificateTemplate: TCertificateTemplateServiceFactory;
|
||||||
certificateAuthority: TCertificateAuthorityServiceFactory;
|
certificateAuthority: TCertificateAuthorityServiceFactory;
|
||||||
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
|
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
|
||||||
|
certificateEst: TCertificateEstServiceFactory;
|
||||||
pkiCollection: TPkiCollectionServiceFactory;
|
pkiCollection: TPkiCollectionServiceFactory;
|
||||||
secretScanning: TSecretScanningServiceFactory;
|
secretScanning: TSecretScanningServiceFactory;
|
||||||
license: TLicenseServiceFactory;
|
license: TLicenseServiceFactory;
|
||||||
@ -170,11 +178,17 @@ declare module "fastify" {
|
|||||||
dynamicSecretLease: TDynamicSecretLeaseServiceFactory;
|
dynamicSecretLease: TDynamicSecretLeaseServiceFactory;
|
||||||
projectUserAdditionalPrivilege: TProjectUserAdditionalPrivilegeServiceFactory;
|
projectUserAdditionalPrivilege: TProjectUserAdditionalPrivilegeServiceFactory;
|
||||||
identityProjectAdditionalPrivilege: TIdentityProjectAdditionalPrivilegeServiceFactory;
|
identityProjectAdditionalPrivilege: TIdentityProjectAdditionalPrivilegeServiceFactory;
|
||||||
|
identityProjectAdditionalPrivilegeV2: TIdentityProjectAdditionalPrivilegeV2ServiceFactory;
|
||||||
secretSharing: TSecretSharingServiceFactory;
|
secretSharing: TSecretSharingServiceFactory;
|
||||||
rateLimit: TRateLimitServiceFactory;
|
rateLimit: TRateLimitServiceFactory;
|
||||||
userEngagement: TUserEngagementServiceFactory;
|
userEngagement: TUserEngagementServiceFactory;
|
||||||
externalKms: TExternalKmsServiceFactory;
|
externalKms: TExternalKmsServiceFactory;
|
||||||
orgAdmin: TOrgAdminServiceFactory;
|
orgAdmin: TOrgAdminServiceFactory;
|
||||||
|
slack: TSlackServiceFactory;
|
||||||
|
workflowIntegration: TWorkflowIntegrationServiceFactory;
|
||||||
|
cmek: TCmekServiceFactory;
|
||||||
|
migration: TExternalMigrationServiceFactory;
|
||||||
|
externalGroupOrgRoleMapping: TExternalGroupOrgRoleMappingServiceFactory;
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
4
backend/src/@types/hdb.d.ts
vendored
Normal file
4
backend/src/@types/hdb.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module "hdb" {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Untyped, the function returns `any`.
|
||||||
|
function createClient(options): any;
|
||||||
|
}
|
52
backend/src/@types/knex.d.ts
vendored
52
backend/src/@types/knex.d.ts
vendored
@ -53,6 +53,9 @@ import {
|
|||||||
TCertificateSecretsUpdate,
|
TCertificateSecretsUpdate,
|
||||||
TCertificatesInsert,
|
TCertificatesInsert,
|
||||||
TCertificatesUpdate,
|
TCertificatesUpdate,
|
||||||
|
TCertificateTemplateEstConfigs,
|
||||||
|
TCertificateTemplateEstConfigsInsert,
|
||||||
|
TCertificateTemplateEstConfigsUpdate,
|
||||||
TCertificateTemplates,
|
TCertificateTemplates,
|
||||||
TCertificateTemplatesInsert,
|
TCertificateTemplatesInsert,
|
||||||
TCertificateTemplatesUpdate,
|
TCertificateTemplatesUpdate,
|
||||||
@ -98,6 +101,9 @@ import {
|
|||||||
TIdentityKubernetesAuths,
|
TIdentityKubernetesAuths,
|
||||||
TIdentityKubernetesAuthsInsert,
|
TIdentityKubernetesAuthsInsert,
|
||||||
TIdentityKubernetesAuthsUpdate,
|
TIdentityKubernetesAuthsUpdate,
|
||||||
|
TIdentityMetadata,
|
||||||
|
TIdentityMetadataInsert,
|
||||||
|
TIdentityMetadataUpdate,
|
||||||
TIdentityOidcAuths,
|
TIdentityOidcAuths,
|
||||||
TIdentityOidcAuthsInsert,
|
TIdentityOidcAuthsInsert,
|
||||||
TIdentityOidcAuthsUpdate,
|
TIdentityOidcAuthsUpdate,
|
||||||
@ -190,6 +196,9 @@ import {
|
|||||||
TProjectRolesUpdate,
|
TProjectRolesUpdate,
|
||||||
TProjects,
|
TProjects,
|
||||||
TProjectsInsert,
|
TProjectsInsert,
|
||||||
|
TProjectSlackConfigs,
|
||||||
|
TProjectSlackConfigsInsert,
|
||||||
|
TProjectSlackConfigsUpdate,
|
||||||
TProjectsUpdate,
|
TProjectsUpdate,
|
||||||
TProjectUserAdditionalPrivilege,
|
TProjectUserAdditionalPrivilege,
|
||||||
TProjectUserAdditionalPrivilegeInsert,
|
TProjectUserAdditionalPrivilegeInsert,
|
||||||
@ -296,6 +305,9 @@ import {
|
|||||||
TServiceTokens,
|
TServiceTokens,
|
||||||
TServiceTokensInsert,
|
TServiceTokensInsert,
|
||||||
TServiceTokensUpdate,
|
TServiceTokensUpdate,
|
||||||
|
TSlackIntegrations,
|
||||||
|
TSlackIntegrationsInsert,
|
||||||
|
TSlackIntegrationsUpdate,
|
||||||
TSuperAdmin,
|
TSuperAdmin,
|
||||||
TSuperAdminInsert,
|
TSuperAdminInsert,
|
||||||
TSuperAdminUpdate,
|
TSuperAdminUpdate,
|
||||||
@ -319,8 +331,16 @@ import {
|
|||||||
TUsersUpdate,
|
TUsersUpdate,
|
||||||
TWebhooks,
|
TWebhooks,
|
||||||
TWebhooksInsert,
|
TWebhooksInsert,
|
||||||
TWebhooksUpdate
|
TWebhooksUpdate,
|
||||||
|
TWorkflowIntegrations,
|
||||||
|
TWorkflowIntegrationsInsert,
|
||||||
|
TWorkflowIntegrationsUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
TExternalGroupOrgRoleMappings,
|
||||||
|
TExternalGroupOrgRoleMappingsInsert,
|
||||||
|
TExternalGroupOrgRoleMappingsUpdate
|
||||||
|
} from "@app/db/schemas/external-group-org-role-mappings";
|
||||||
import {
|
import {
|
||||||
TSecretV2TagJunction,
|
TSecretV2TagJunction,
|
||||||
TSecretV2TagJunctionInsert,
|
TSecretV2TagJunctionInsert,
|
||||||
@ -372,6 +392,11 @@ declare module "knex/types/tables" {
|
|||||||
TCertificateTemplatesInsert,
|
TCertificateTemplatesInsert,
|
||||||
TCertificateTemplatesUpdate
|
TCertificateTemplatesUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.CertificateTemplateEstConfig]: KnexOriginal.CompositeTableType<
|
||||||
|
TCertificateTemplateEstConfigs,
|
||||||
|
TCertificateTemplateEstConfigsInsert,
|
||||||
|
TCertificateTemplateEstConfigsUpdate
|
||||||
|
>;
|
||||||
[TableName.CertificateBody]: KnexOriginal.CompositeTableType<
|
[TableName.CertificateBody]: KnexOriginal.CompositeTableType<
|
||||||
TCertificateBodies,
|
TCertificateBodies,
|
||||||
TCertificateBodiesInsert,
|
TCertificateBodiesInsert,
|
||||||
@ -529,6 +554,11 @@ declare module "knex/types/tables" {
|
|||||||
TIdentityUniversalAuthsInsert,
|
TIdentityUniversalAuthsInsert,
|
||||||
TIdentityUniversalAuthsUpdate
|
TIdentityUniversalAuthsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.IdentityMetadata]: KnexOriginal.CompositeTableType<
|
||||||
|
TIdentityMetadata,
|
||||||
|
TIdentityMetadataInsert,
|
||||||
|
TIdentityMetadataUpdate
|
||||||
|
>;
|
||||||
[TableName.IdentityKubernetesAuth]: KnexOriginal.CompositeTableType<
|
[TableName.IdentityKubernetesAuth]: KnexOriginal.CompositeTableType<
|
||||||
TIdentityKubernetesAuths,
|
TIdentityKubernetesAuths,
|
||||||
TIdentityKubernetesAuthsInsert,
|
TIdentityKubernetesAuthsInsert,
|
||||||
@ -768,5 +798,25 @@ declare module "knex/types/tables" {
|
|||||||
TKmsKeyVersionsInsert,
|
TKmsKeyVersionsInsert,
|
||||||
TKmsKeyVersionsUpdate
|
TKmsKeyVersionsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.SlackIntegrations]: KnexOriginal.CompositeTableType<
|
||||||
|
TSlackIntegrations,
|
||||||
|
TSlackIntegrationsInsert,
|
||||||
|
TSlackIntegrationsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.ProjectSlackConfigs]: KnexOriginal.CompositeTableType<
|
||||||
|
TProjectSlackConfigs,
|
||||||
|
TProjectSlackConfigsInsert,
|
||||||
|
TProjectSlackConfigsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.WorkflowIntegrations]: KnexOriginal.CompositeTableType<
|
||||||
|
TWorkflowIntegrations,
|
||||||
|
TWorkflowIntegrationsInsert,
|
||||||
|
TWorkflowIntegrationsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.ExternalGroupOrgRoleMapping]: KnexOriginal.CompositeTableType<
|
||||||
|
TExternalGroupOrgRoleMappings,
|
||||||
|
TExternalGroupOrgRoleMappingsInsert,
|
||||||
|
TExternalGroupOrgRoleMappingsUpdate
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
backend/src/@types/ldif.d.ts
vendored
Normal file
4
backend/src/@types/ldif.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module "ldif" {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Untyped, the function returns `any`.
|
||||||
|
function parse(input: string, ...args: any[]): any;
|
||||||
|
}
|
75
backend/src/db/auditlog-knexfile.ts
Normal file
75
backend/src/db/auditlog-knexfile.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// eslint-disable-next-line
|
||||||
|
import "ts-node/register";
|
||||||
|
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import type { Knex } from "knex";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
// Update with your config settings. .
|
||||||
|
dotenv.config({
|
||||||
|
path: path.join(__dirname, "../../../.env.migration")
|
||||||
|
});
|
||||||
|
dotenv.config({
|
||||||
|
path: path.join(__dirname, "../../../.env")
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!process.env.AUDIT_LOGS_DB_CONNECTION_URI && !process.env.AUDIT_LOGS_DB_HOST) {
|
||||||
|
console.info("Dedicated audit log database not found. No further migrations necessary");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info("Executing migration on audit log database...");
|
||||||
|
|
||||||
|
export default {
|
||||||
|
development: {
|
||||||
|
client: "postgres",
|
||||||
|
connection: {
|
||||||
|
connectionString: process.env.AUDIT_LOGS_DB_CONNECTION_URI,
|
||||||
|
host: process.env.AUDIT_LOGS_DB_HOST,
|
||||||
|
port: process.env.AUDIT_LOGS_DB_PORT,
|
||||||
|
user: process.env.AUDIT_LOGS_DB_USER,
|
||||||
|
database: process.env.AUDIT_LOGS_DB_NAME,
|
||||||
|
password: process.env.AUDIT_LOGS_DB_PASSWORD,
|
||||||
|
ssl: process.env.AUDIT_LOGS_DB_ROOT_CERT
|
||||||
|
? {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
ca: Buffer.from(process.env.AUDIT_LOGS_DB_ROOT_CERT, "base64").toString("ascii")
|
||||||
|
}
|
||||||
|
: false
|
||||||
|
},
|
||||||
|
pool: {
|
||||||
|
min: 2,
|
||||||
|
max: 10
|
||||||
|
},
|
||||||
|
seeds: {
|
||||||
|
directory: "./seeds"
|
||||||
|
},
|
||||||
|
migrations: {
|
||||||
|
tableName: "infisical_migrations"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
client: "postgres",
|
||||||
|
connection: {
|
||||||
|
connectionString: process.env.AUDIT_LOGS_DB_CONNECTION_URI,
|
||||||
|
host: process.env.AUDIT_LOGS_DB_HOST,
|
||||||
|
port: process.env.AUDIT_LOGS_DB_PORT,
|
||||||
|
user: process.env.AUDIT_LOGS_DB_USER,
|
||||||
|
database: process.env.AUDIT_LOGS_DB_NAME,
|
||||||
|
password: process.env.AUDIT_LOGS_DB_PASSWORD,
|
||||||
|
ssl: process.env.AUDIT_LOGS_DB_ROOT_CERT
|
||||||
|
? {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
ca: Buffer.from(process.env.AUDIT_LOGS_DB_ROOT_CERT, "base64").toString("ascii")
|
||||||
|
}
|
||||||
|
: false
|
||||||
|
},
|
||||||
|
pool: {
|
||||||
|
min: 2,
|
||||||
|
max: 10
|
||||||
|
},
|
||||||
|
migrations: {
|
||||||
|
tableName: "infisical_migrations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as Knex.Config;
|
@ -1,2 +1,2 @@
|
|||||||
export type { TDbClient } from "./instance";
|
export type { TDbClient } from "./instance";
|
||||||
export { initDbConnection } from "./instance";
|
export { initAuditLogDbConnection, initDbConnection } from "./instance";
|
||||||
|
@ -70,3 +70,45 @@ export const initDbConnection = ({
|
|||||||
|
|
||||||
return db;
|
return db;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const initAuditLogDbConnection = ({
|
||||||
|
dbConnectionUri,
|
||||||
|
dbRootCert
|
||||||
|
}: {
|
||||||
|
dbConnectionUri: string;
|
||||||
|
dbRootCert?: string;
|
||||||
|
}) => {
|
||||||
|
// akhilmhdh: the default Knex is knex.Knex<any, any[]>. but when assigned with knex({<config>}) the value is knex.Knex<any, unknown[]>
|
||||||
|
// this was causing issue with files like `snapshot-dal` `findRecursivelySnapshots` this i am explicitly putting the any and unknown[]
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const db: Knex<any, unknown[]> = knex({
|
||||||
|
client: "pg",
|
||||||
|
connection: {
|
||||||
|
connectionString: dbConnectionUri,
|
||||||
|
host: process.env.AUDIT_LOGS_DB_HOST,
|
||||||
|
// @ts-expect-error I have no clue why only for the port there is a type error
|
||||||
|
// eslint-disable-next-line
|
||||||
|
port: process.env.AUDIT_LOGS_DB_PORT,
|
||||||
|
user: process.env.AUDIT_LOGS_DB_USER,
|
||||||
|
database: process.env.AUDIT_LOGS_DB_NAME,
|
||||||
|
password: process.env.AUDIT_LOGS_DB_PASSWORD,
|
||||||
|
ssl: dbRootCert
|
||||||
|
? {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
ca: Buffer.from(dbRootCert, "base64").toString("ascii")
|
||||||
|
}
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// we add these overrides so that auditLogDb and the primary DB are interchangeable
|
||||||
|
db.primaryNode = () => {
|
||||||
|
return db;
|
||||||
|
};
|
||||||
|
|
||||||
|
db.replicaNode = () => {
|
||||||
|
return db;
|
||||||
|
};
|
||||||
|
|
||||||
|
return db;
|
||||||
|
};
|
||||||
|
161
backend/src/db/manual-migrations/partition-audit-logs.ts
Normal file
161
backend/src/db/manual-migrations/partition-audit-logs.ts
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import kx, { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
const INTERMEDIATE_AUDIT_LOG_TABLE = "intermediate_audit_logs";
|
||||||
|
|
||||||
|
const formatPartitionDate = (date: Date) => {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(date.getDate()).padStart(2, "0");
|
||||||
|
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createAuditLogPartition = async (knex: Knex, startDate: Date, endDate: Date) => {
|
||||||
|
const startDateStr = formatPartitionDate(startDate);
|
||||||
|
const endDateStr = formatPartitionDate(endDate);
|
||||||
|
|
||||||
|
const partitionName = `${TableName.AuditLog}_${startDateStr.replace(/-/g, "")}_${endDateStr.replace(/-/g, "")}`;
|
||||||
|
|
||||||
|
await knex.schema.raw(
|
||||||
|
`CREATE TABLE ${partitionName} PARTITION OF ${TableName.AuditLog} FOR VALUES FROM ('${startDateStr}') TO ('${endDateStr}')`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const up = async (knex: Knex): Promise<void> => {
|
||||||
|
console.info("Dropping primary key of audit log table...");
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
// remove existing keys
|
||||||
|
t.dropPrimary();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get all indices of the audit log table and drop them
|
||||||
|
const indexNames: { rows: { indexname: string }[] } = await knex.raw(
|
||||||
|
`
|
||||||
|
SELECT indexname
|
||||||
|
FROM pg_indexes
|
||||||
|
WHERE tablename = '${TableName.AuditLog}'
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"Deleting existing audit log indices:",
|
||||||
|
indexNames.rows.map((e) => e.indexname)
|
||||||
|
);
|
||||||
|
|
||||||
|
for await (const row of indexNames.rows) {
|
||||||
|
await knex.raw(`DROP INDEX IF EXISTS ${row.indexname}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// renaming audit log to intermediate table
|
||||||
|
console.log("Renaming audit log table to the intermediate name");
|
||||||
|
await knex.schema.renameTable(TableName.AuditLog, INTERMEDIATE_AUDIT_LOG_TABLE);
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.AuditLog))) {
|
||||||
|
const createTableSql = knex.schema
|
||||||
|
.createTable(TableName.AuditLog, (t) => {
|
||||||
|
t.uuid("id").defaultTo(knex.fn.uuid());
|
||||||
|
t.string("actor").notNullable();
|
||||||
|
t.jsonb("actorMetadata").notNullable();
|
||||||
|
t.string("ipAddress");
|
||||||
|
t.string("eventType").notNullable();
|
||||||
|
t.jsonb("eventMetadata");
|
||||||
|
t.string("userAgent");
|
||||||
|
t.string("userAgentType");
|
||||||
|
t.datetime("expiresAt");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.uuid("orgId");
|
||||||
|
t.string("projectId");
|
||||||
|
t.string("projectName");
|
||||||
|
t.primary(["id", "createdAt"]);
|
||||||
|
})
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
console.info("Creating partition table...");
|
||||||
|
await knex.schema.raw(`
|
||||||
|
${createTableSql} PARTITION BY RANGE ("createdAt");
|
||||||
|
`);
|
||||||
|
|
||||||
|
console.log("Adding indices...");
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
t.index(["projectId", "createdAt"]);
|
||||||
|
t.index(["orgId", "createdAt"]);
|
||||||
|
t.index("expiresAt");
|
||||||
|
t.index("orgId");
|
||||||
|
t.index("projectId");
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Adding GIN indices...");
|
||||||
|
|
||||||
|
await knex.raw(
|
||||||
|
`CREATE INDEX IF NOT EXISTS "audit_logs_actorMetadata_idx" ON ${TableName.AuditLog} USING gin("actorMetadata" jsonb_path_ops)`
|
||||||
|
);
|
||||||
|
console.log("GIN index for actorMetadata done");
|
||||||
|
|
||||||
|
await knex.raw(
|
||||||
|
`CREATE INDEX IF NOT EXISTS "audit_logs_eventMetadata_idx" ON ${TableName.AuditLog} USING gin("eventMetadata" jsonb_path_ops)`
|
||||||
|
);
|
||||||
|
console.log("GIN index for eventMetadata done");
|
||||||
|
|
||||||
|
// create default partition
|
||||||
|
console.log("Creating default partition...");
|
||||||
|
await knex.schema.raw(`CREATE TABLE ${TableName.AuditLog}_default PARTITION OF ${TableName.AuditLog} DEFAULT`);
|
||||||
|
|
||||||
|
const nextDate = new Date();
|
||||||
|
nextDate.setDate(nextDate.getDate() + 1);
|
||||||
|
const nextDateStr = formatPartitionDate(nextDate);
|
||||||
|
|
||||||
|
console.log("Attaching existing audit log table as a partition...");
|
||||||
|
await knex.schema.raw(`
|
||||||
|
ALTER TABLE ${INTERMEDIATE_AUDIT_LOG_TABLE} ADD CONSTRAINT audit_log_old
|
||||||
|
CHECK ( "createdAt" < DATE '${nextDateStr}' );
|
||||||
|
|
||||||
|
ALTER TABLE ${TableName.AuditLog} ATTACH PARTITION ${INTERMEDIATE_AUDIT_LOG_TABLE}
|
||||||
|
FOR VALUES FROM (MINVALUE) TO ('${nextDateStr}' );
|
||||||
|
`);
|
||||||
|
|
||||||
|
// create partition from now until end of month
|
||||||
|
console.log("Creating audit log partitions ahead of time... next date:", nextDateStr);
|
||||||
|
await createAuditLogPartition(knex, nextDate, new Date(nextDate.getFullYear(), nextDate.getMonth() + 1));
|
||||||
|
|
||||||
|
// create partitions 4 years ahead
|
||||||
|
const partitionMonths = 4 * 12;
|
||||||
|
const partitionPromises: Promise<void>[] = [];
|
||||||
|
for (let x = 1; x <= partitionMonths; x += 1) {
|
||||||
|
partitionPromises.push(
|
||||||
|
createAuditLogPartition(
|
||||||
|
knex,
|
||||||
|
new Date(nextDate.getFullYear(), nextDate.getMonth() + x, 1),
|
||||||
|
new Date(nextDate.getFullYear(), nextDate.getMonth() + (x + 1), 1)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(partitionPromises);
|
||||||
|
console.log("Partition migration complete");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const executeMigration = async (url: string) => {
|
||||||
|
console.log("Executing migration...");
|
||||||
|
const knex = kx({
|
||||||
|
client: "pg",
|
||||||
|
connection: url
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.transaction(async (tx) => {
|
||||||
|
await up(tx);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const dbUrl = process.env.AUDIT_LOGS_DB_CONNECTION_URI;
|
||||||
|
if (!dbUrl) {
|
||||||
|
console.error("Please provide a DB connection URL to the AUDIT_LOGS_DB_CONNECTION_URI env");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void executeMigration(dbUrl).then(() => {
|
||||||
|
console.log("Migration: partition-audit-logs DONE");
|
||||||
|
process.exit(0);
|
||||||
|
});
|
@ -9,7 +9,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
t.string("integration").notNullable();
|
t.string("integration").notNullable();
|
||||||
t.string("teamId"); // vercel-specific
|
t.string("teamId"); // vercel-specific
|
||||||
t.string("url"); // for self hosted
|
t.string("url"); // for self-hosted
|
||||||
t.string("namespace"); // hashicorp specific
|
t.string("namespace"); // hashicorp specific
|
||||||
t.string("accountId"); // netlify
|
t.string("accountId"); // netlify
|
||||||
t.text("refreshCiphertext");
|
t.text("refreshCiphertext");
|
||||||
@ -36,7 +36,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
await knex.schema.createTable(TableName.Integration, (t) => {
|
await knex.schema.createTable(TableName.Integration, (t) => {
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
t.boolean("isActive").notNullable();
|
t.boolean("isActive").notNullable();
|
||||||
t.string("url"); // self hosted
|
t.string("url"); // self-hosted
|
||||||
t.string("app"); // name of app in provider
|
t.string("app"); // name of app in provider
|
||||||
t.string("appId");
|
t.string("appId");
|
||||||
t.string("targetEnvironment");
|
t.string("targetEnvironment");
|
||||||
|
@ -115,7 +115,14 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
// @ts-ignore because generate schema happens after this
|
// @ts-ignore because generate schema happens after this
|
||||||
approverId: knex(TableName.ProjectMembership)
|
approverId: knex(TableName.ProjectMembership)
|
||||||
.select("id")
|
.join(
|
||||||
|
TableName.SecretApprovalPolicy,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
|
`${TableName.SecretApprovalPolicyApprover}.policyId`
|
||||||
|
)
|
||||||
|
.join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretApprovalPolicy}.envId`)
|
||||||
|
.select(knex.ref("id").withSchema(TableName.ProjectMembership))
|
||||||
|
.where(`${TableName.ProjectMembership}.projectId`, knex.raw("??", [`${TableName.Environment}.projectId`]))
|
||||||
.where("userId", knex.raw("??", [`${TableName.SecretApprovalPolicyApprover}.approverUserId`]))
|
.where("userId", knex.raw("??", [`${TableName.SecretApprovalPolicyApprover}.approverUserId`]))
|
||||||
});
|
});
|
||||||
await knex.schema.alterTable(TableName.SecretApprovalPolicyApprover, (tb) => {
|
await knex.schema.alterTable(TableName.SecretApprovalPolicyApprover, (tb) => {
|
||||||
@ -147,13 +154,27 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
// @ts-ignore because generate schema happens after this
|
// @ts-ignore because generate schema happens after this
|
||||||
committerId: knex(TableName.ProjectMembership)
|
committerId: knex(TableName.ProjectMembership)
|
||||||
.select("id")
|
.join(
|
||||||
.where("userId", knex.raw("??", [`${TableName.SecretApprovalRequest}.committerUserId`])),
|
TableName.SecretApprovalPolicy,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
|
`${TableName.SecretApprovalRequest}.policyId`
|
||||||
|
)
|
||||||
|
.join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretApprovalPolicy}.envId`)
|
||||||
|
.where(`${TableName.ProjectMembership}.projectId`, knex.raw("??", [`${TableName.Environment}.projectId`]))
|
||||||
|
.where("userId", knex.raw("??", [`${TableName.SecretApprovalRequest}.committerUserId`]))
|
||||||
|
.select(knex.ref("id").withSchema(TableName.ProjectMembership)),
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
// @ts-ignore because generate schema happens after this
|
// @ts-ignore because generate schema happens after this
|
||||||
statusChangeBy: knex(TableName.ProjectMembership)
|
statusChangeBy: knex(TableName.ProjectMembership)
|
||||||
.select("id")
|
.join(
|
||||||
|
TableName.SecretApprovalPolicy,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
|
`${TableName.SecretApprovalRequest}.policyId`
|
||||||
|
)
|
||||||
|
.join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretApprovalPolicy}.envId`)
|
||||||
|
.where(`${TableName.ProjectMembership}.projectId`, knex.raw("??", [`${TableName.Environment}.projectId`]))
|
||||||
.where("userId", knex.raw("??", [`${TableName.SecretApprovalRequest}.statusChangedByUserId`]))
|
.where("userId", knex.raw("??", [`${TableName.SecretApprovalRequest}.statusChangedByUserId`]))
|
||||||
|
.select(knex.ref("id").withSchema(TableName.ProjectMembership))
|
||||||
});
|
});
|
||||||
|
|
||||||
await knex.schema.alterTable(TableName.SecretApprovalRequest, (tb) => {
|
await knex.schema.alterTable(TableName.SecretApprovalRequest, (tb) => {
|
||||||
@ -177,8 +198,20 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
// @ts-ignore because generate schema happens after this
|
// @ts-ignore because generate schema happens after this
|
||||||
member: knex(TableName.ProjectMembership)
|
member: knex(TableName.ProjectMembership)
|
||||||
.select("id")
|
.join(
|
||||||
|
TableName.SecretApprovalRequest,
|
||||||
|
`${TableName.SecretApprovalRequest}.id`,
|
||||||
|
`${TableName.SecretApprovalRequestReviewer}.requestId`
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
TableName.SecretApprovalPolicy,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
|
`${TableName.SecretApprovalRequest}.policyId`
|
||||||
|
)
|
||||||
|
.join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretApprovalPolicy}.envId`)
|
||||||
|
.where(`${TableName.ProjectMembership}.projectId`, knex.raw("??", [`${TableName.Environment}.projectId`]))
|
||||||
.where("userId", knex.raw("??", [`${TableName.SecretApprovalRequestReviewer}.reviewerUserId`]))
|
.where("userId", knex.raw("??", [`${TableName.SecretApprovalRequestReviewer}.reviewerUserId`]))
|
||||||
|
.select(knex.ref("id").withSchema(TableName.ProjectMembership))
|
||||||
});
|
});
|
||||||
await knex.schema.alterTable(TableName.SecretApprovalRequestReviewer, (tb) => {
|
await knex.schema.alterTable(TableName.SecretApprovalRequestReviewer, (tb) => {
|
||||||
tb.uuid("member").notNullable().alter();
|
tb.uuid("member").notNullable().alter();
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||||
|
const doesPasswordExist = await knex.schema.hasColumn(TableName.SecretSharing, "password");
|
||||||
|
if (!doesPasswordExist) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||||
|
t.string("password").nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||||
|
const doesPasswordExist = await knex.schema.hasColumn(TableName.SecretSharing, "password");
|
||||||
|
if (doesPasswordExist) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||||
|
t.dropColumn("password");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasEstConfigTable = await knex.schema.hasTable(TableName.CertificateTemplateEstConfig);
|
||||||
|
if (!hasEstConfigTable) {
|
||||||
|
await knex.schema.createTable(TableName.CertificateTemplateEstConfig, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
tb.uuid("certificateTemplateId").notNullable().unique();
|
||||||
|
tb.foreign("certificateTemplateId").references("id").inTable(TableName.CertificateTemplate).onDelete("CASCADE");
|
||||||
|
tb.binary("encryptedCaChain").notNullable();
|
||||||
|
tb.string("hashedPassphrase").notNullable();
|
||||||
|
tb.boolean("isEnabled").notNullable();
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.CertificateTemplateEstConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.CertificateTemplateEstConfig);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.CertificateTemplateEstConfig);
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.CertificateAuthorityCrl)) {
|
||||||
|
const hasCaSecretIdColumn = await knex.schema.hasColumn(TableName.CertificateAuthorityCrl, "caSecretId");
|
||||||
|
if (!hasCaSecretIdColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.CertificateAuthorityCrl, (t) => {
|
||||||
|
t.uuid("caSecretId").nullable();
|
||||||
|
t.foreign("caSecretId").references("id").inTable(TableName.CertificateAuthoritySecret).onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.raw(`
|
||||||
|
UPDATE "${TableName.CertificateAuthorityCrl}" crl
|
||||||
|
SET "caSecretId" = (
|
||||||
|
SELECT sec.id
|
||||||
|
FROM "${TableName.CertificateAuthoritySecret}" sec
|
||||||
|
WHERE sec."caId" = crl."caId"
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.CertificateAuthorityCrl, (t) => {
|
||||||
|
t.uuid("caSecretId").notNullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.CertificateAuthorityCrl)) {
|
||||||
|
await knex.schema.alterTable(TableName.CertificateAuthorityCrl, (t) => {
|
||||||
|
t.dropColumn("caSecretId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.WorkflowIntegrations))) {
|
||||||
|
await knex.schema.createTable(TableName.WorkflowIntegrations, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
tb.string("integration").notNullable();
|
||||||
|
tb.string("slug").notNullable();
|
||||||
|
tb.uuid("orgId").notNullable();
|
||||||
|
tb.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
tb.string("description");
|
||||||
|
tb.unique(["orgId", "slug"]);
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.WorkflowIntegrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SlackIntegrations))) {
|
||||||
|
await knex.schema.createTable(TableName.SlackIntegrations, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).notNullable();
|
||||||
|
tb.foreign("id").references("id").inTable(TableName.WorkflowIntegrations).onDelete("CASCADE");
|
||||||
|
tb.string("teamId").notNullable();
|
||||||
|
tb.string("teamName").notNullable();
|
||||||
|
tb.string("slackUserId").notNullable();
|
||||||
|
tb.string("slackAppId").notNullable();
|
||||||
|
tb.binary("encryptedBotAccessToken").notNullable();
|
||||||
|
tb.string("slackBotId").notNullable();
|
||||||
|
tb.string("slackBotUserId").notNullable();
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.SlackIntegrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.ProjectSlackConfigs))) {
|
||||||
|
await knex.schema.createTable(TableName.ProjectSlackConfigs, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
tb.string("projectId").notNullable().unique();
|
||||||
|
tb.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
tb.uuid("slackIntegrationId").notNullable();
|
||||||
|
tb.foreign("slackIntegrationId").references("id").inTable(TableName.SlackIntegrations).onDelete("CASCADE");
|
||||||
|
tb.boolean("isAccessRequestNotificationEnabled").notNullable().defaultTo(false);
|
||||||
|
tb.string("accessRequestChannels").notNullable().defaultTo("");
|
||||||
|
tb.boolean("isSecretRequestNotificationEnabled").notNullable().defaultTo(false);
|
||||||
|
tb.string("secretRequestChannels").notNullable().defaultTo("");
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.ProjectSlackConfigs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const doesSuperAdminHaveSlackClientId = await knex.schema.hasColumn(TableName.SuperAdmin, "encryptedSlackClientId");
|
||||||
|
const doesSuperAdminHaveSlackClientSecret = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedSlackClientSecret"
|
||||||
|
);
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (tb) => {
|
||||||
|
if (!doesSuperAdminHaveSlackClientId) {
|
||||||
|
tb.binary("encryptedSlackClientId");
|
||||||
|
}
|
||||||
|
if (!doesSuperAdminHaveSlackClientSecret) {
|
||||||
|
tb.binary("encryptedSlackClientSecret");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.ProjectSlackConfigs);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.ProjectSlackConfigs);
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SlackIntegrations);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.SlackIntegrations);
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.WorkflowIntegrations);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.WorkflowIntegrations);
|
||||||
|
|
||||||
|
const doesSuperAdminHaveSlackClientId = await knex.schema.hasColumn(TableName.SuperAdmin, "encryptedSlackClientId");
|
||||||
|
const doesSuperAdminHaveSlackClientSecret = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedSlackClientSecret"
|
||||||
|
);
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (tb) => {
|
||||||
|
if (doesSuperAdminHaveSlackClientId) {
|
||||||
|
tb.dropColumn("encryptedSlackClientId");
|
||||||
|
}
|
||||||
|
if (doesSuperAdminHaveSlackClientSecret) {
|
||||||
|
tb.dropColumn("encryptedSlackClientSecret");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.CertificateAuthority)) {
|
||||||
|
const hasRequireTemplateForIssuanceColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.CertificateAuthority,
|
||||||
|
"requireTemplateForIssuance"
|
||||||
|
);
|
||||||
|
if (!hasRequireTemplateForIssuanceColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
|
||||||
|
t.boolean("requireTemplateForIssuance").notNullable().defaultTo(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.CertificateAuthority)) {
|
||||||
|
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
|
||||||
|
t.dropColumn("requireTemplateForIssuance");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
// Certificate template
|
||||||
|
const hasKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "keyUsages");
|
||||||
|
const hasExtendedKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "extendedKeyUsages");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.CertificateTemplate, (tb) => {
|
||||||
|
if (!hasKeyUsagesCol) {
|
||||||
|
tb.specificType("keyUsages", "text[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasExtendedKeyUsagesCol) {
|
||||||
|
tb.specificType("extendedKeyUsages", "text[]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasKeyUsagesCol) {
|
||||||
|
await knex(TableName.CertificateTemplate).update({
|
||||||
|
keyUsages: [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasExtendedKeyUsagesCol) {
|
||||||
|
await knex(TableName.CertificateTemplate).update({
|
||||||
|
extendedKeyUsages: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificate
|
||||||
|
const doesCertTableHaveKeyUsages = await knex.schema.hasColumn(TableName.Certificate, "keyUsages");
|
||||||
|
const doesCertTableHaveExtendedKeyUsages = await knex.schema.hasColumn(TableName.Certificate, "extendedKeyUsages");
|
||||||
|
await knex.schema.alterTable(TableName.Certificate, (tb) => {
|
||||||
|
if (!doesCertTableHaveKeyUsages) {
|
||||||
|
tb.specificType("keyUsages", "text[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doesCertTableHaveExtendedKeyUsages) {
|
||||||
|
tb.specificType("extendedKeyUsages", "text[]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!doesCertTableHaveKeyUsages) {
|
||||||
|
await knex(TableName.Certificate).update({
|
||||||
|
keyUsages: [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doesCertTableHaveExtendedKeyUsages) {
|
||||||
|
await knex(TableName.Certificate).update({
|
||||||
|
extendedKeyUsages: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
// Certificate Template
|
||||||
|
const hasKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "keyUsages");
|
||||||
|
const hasExtendedKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "extendedKeyUsages");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.CertificateTemplate, (t) => {
|
||||||
|
if (hasKeyUsagesCol) {
|
||||||
|
t.dropColumn("keyUsages");
|
||||||
|
}
|
||||||
|
if (hasExtendedKeyUsagesCol) {
|
||||||
|
t.dropColumn("extendedKeyUsages");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Certificate
|
||||||
|
const doesCertTableHaveKeyUsages = await knex.schema.hasColumn(TableName.Certificate, "keyUsages");
|
||||||
|
const doesCertTableHaveExtendedKeyUsages = await knex.schema.hasColumn(TableName.Certificate, "extendedKeyUsages");
|
||||||
|
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||||
|
if (doesCertTableHaveKeyUsages) {
|
||||||
|
t.dropColumn("keyUsages");
|
||||||
|
}
|
||||||
|
if (doesCertTableHaveExtendedKeyUsages) {
|
||||||
|
t.dropColumn("extendedKeyUsages");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasAccessApproverGroupId = await knex.schema.hasColumn(
|
||||||
|
TableName.AccessApprovalPolicyApprover,
|
||||||
|
"approverGroupId"
|
||||||
|
);
|
||||||
|
const hasAccessApproverUserId = await knex.schema.hasColumn(TableName.AccessApprovalPolicyApprover, "approverUserId");
|
||||||
|
const hasSecretApproverGroupId = await knex.schema.hasColumn(
|
||||||
|
TableName.SecretApprovalPolicyApprover,
|
||||||
|
"approverGroupId"
|
||||||
|
);
|
||||||
|
const hasSecretApproverUserId = await knex.schema.hasColumn(TableName.SecretApprovalPolicyApprover, "approverUserId");
|
||||||
|
if (await knex.schema.hasTable(TableName.AccessApprovalPolicyApprover)) {
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (table) => {
|
||||||
|
// add column approverGroupId to AccessApprovalPolicyApprover
|
||||||
|
if (!hasAccessApproverGroupId) {
|
||||||
|
table.uuid("approverGroupId").nullable().references("id").inTable(TableName.Groups).onDelete("CASCADE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// make approverUserId nullable
|
||||||
|
if (hasAccessApproverUserId) {
|
||||||
|
table.uuid("approverUserId").nullable().alter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalPolicyApprover, (table) => {
|
||||||
|
// add column approverGroupId to SecretApprovalPolicyApprover
|
||||||
|
if (!hasSecretApproverGroupId) {
|
||||||
|
table.uuid("approverGroupId").nullable().references("id").inTable(TableName.Groups).onDelete("CASCADE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// make approverUserId nullable
|
||||||
|
if (hasSecretApproverUserId) {
|
||||||
|
table.uuid("approverUserId").nullable().alter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasAccessApproverGroupId = await knex.schema.hasColumn(
|
||||||
|
TableName.AccessApprovalPolicyApprover,
|
||||||
|
"approverGroupId"
|
||||||
|
);
|
||||||
|
const hasAccessApproverUserId = await knex.schema.hasColumn(TableName.AccessApprovalPolicyApprover, "approverUserId");
|
||||||
|
const hasSecretApproverGroupId = await knex.schema.hasColumn(
|
||||||
|
TableName.SecretApprovalPolicyApprover,
|
||||||
|
"approverGroupId"
|
||||||
|
);
|
||||||
|
const hasSecretApproverUserId = await knex.schema.hasColumn(TableName.SecretApprovalPolicyApprover, "approverUserId");
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.AccessApprovalPolicyApprover)) {
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (table) => {
|
||||||
|
if (hasAccessApproverGroupId) {
|
||||||
|
table.dropColumn("approverGroupId");
|
||||||
|
}
|
||||||
|
// make approverUserId not nullable
|
||||||
|
if (hasAccessApproverUserId) {
|
||||||
|
table.uuid("approverUserId").notNullable().alter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalPolicyApprover, (table) => {
|
||||||
|
if (hasSecretApproverGroupId) {
|
||||||
|
table.dropColumn("approverGroupId");
|
||||||
|
}
|
||||||
|
// make approverUserId not nullable
|
||||||
|
if (hasSecretApproverUserId) {
|
||||||
|
table.uuid("approverUserId").notNullable().alter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.IdentityMetadata))) {
|
||||||
|
await knex.schema.createTable(TableName.IdentityMetadata, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
tb.string("key").notNullable();
|
||||||
|
tb.string("value").notNullable();
|
||||||
|
tb.uuid("orgId").notNullable();
|
||||||
|
tb.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
tb.uuid("userId");
|
||||||
|
tb.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
tb.uuid("identityId");
|
||||||
|
tb.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.IdentityMetadata);
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||||
|
const hasEncryptedSecret = await knex.schema.hasColumn(TableName.SecretSharing, "encryptedSecret");
|
||||||
|
const hasIdentifier = await knex.schema.hasColumn(TableName.SecretSharing, "identifier");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||||
|
t.string("iv").nullable().alter();
|
||||||
|
t.string("tag").nullable().alter();
|
||||||
|
t.string("encryptedValue").nullable().alter();
|
||||||
|
|
||||||
|
if (!hasEncryptedSecret) {
|
||||||
|
t.binary("encryptedSecret").nullable();
|
||||||
|
}
|
||||||
|
t.string("hashedHex").nullable().alter();
|
||||||
|
|
||||||
|
if (!hasIdentifier) {
|
||||||
|
t.string("identifier", 64).nullable();
|
||||||
|
t.unique("identifier");
|
||||||
|
t.index("identifier");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasEncryptedSecret = await knex.schema.hasColumn(TableName.SecretSharing, "encryptedSecret");
|
||||||
|
const hasIdentifier = await knex.schema.hasColumn(TableName.SecretSharing, "identifier");
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||||
|
if (hasEncryptedSecret) {
|
||||||
|
t.dropColumn("encryptedSecret");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasIdentifier) {
|
||||||
|
t.dropColumn("identifier");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.OidcConfig, "lastUsed"))) {
|
||||||
|
await knex.schema.alterTable(TableName.OidcConfig, (tb) => {
|
||||||
|
tb.datetime("lastUsed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.OidcConfig, "lastUsed")) {
|
||||||
|
await knex.schema.alterTable(TableName.OidcConfig, (tb) => {
|
||||||
|
tb.dropColumn("lastUsed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
|
||||||
|
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
||||||
|
const hasProjectId = await knex.schema.hasColumn(TableName.KmsKey, "projectId");
|
||||||
|
|
||||||
|
// drop constraint if exists (won't exist if rolled back, see below)
|
||||||
|
await dropConstraintIfExists(TableName.KmsKey, "kms_keys_orgid_slug_unique", knex);
|
||||||
|
|
||||||
|
// projectId for CMEK functionality
|
||||||
|
await knex.schema.alterTable(TableName.KmsKey, (table) => {
|
||||||
|
if (!hasProjectId) {
|
||||||
|
table.string("projectId").nullable().references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasOrgId && hasSlug) {
|
||||||
|
table.unique(["orgId", "projectId", "slug"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSlug) {
|
||||||
|
table.renameColumn("slug", "name");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
|
||||||
|
const hasName = await knex.schema.hasColumn(TableName.KmsKey, "name");
|
||||||
|
const hasProjectId = await knex.schema.hasColumn(TableName.KmsKey, "projectId");
|
||||||
|
|
||||||
|
// remove projectId for CMEK functionality
|
||||||
|
await knex.schema.alterTable(TableName.KmsKey, (table) => {
|
||||||
|
if (hasName) {
|
||||||
|
table.renameColumn("name", "slug");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasOrgId) {
|
||||||
|
table.dropUnique(["orgId", "projectId", "slug"]);
|
||||||
|
}
|
||||||
|
if (hasProjectId) {
|
||||||
|
table.dropColumn("projectId");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
||||||
|
|
||||||
|
if (!hasSlug) {
|
||||||
|
// add slug back temporarily and set value equal to name
|
||||||
|
await knex.schema
|
||||||
|
.alterTable(TableName.KmsKey, (table) => {
|
||||||
|
table.string("slug", 32);
|
||||||
|
})
|
||||||
|
.then(() => knex(TableName.KmsKey).update("slug", knex.ref("name")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
||||||
|
|
||||||
|
if (hasSlug) {
|
||||||
|
await knex.schema.alterTable(TableName.KmsKey, (table) => {
|
||||||
|
table.dropColumn("slug");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||||
|
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
|
||||||
|
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
|
||||||
|
const doesProjectNameExist = await knex.schema.hasColumn(TableName.AuditLog, "projectName");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
if (doesOrgIdExist) {
|
||||||
|
t.dropForeign("orgId");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doesProjectIdExist) {
|
||||||
|
t.dropForeign("projectId");
|
||||||
|
}
|
||||||
|
|
||||||
|
// add normalized field
|
||||||
|
if (!doesProjectNameExist) {
|
||||||
|
t.string("projectName");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
|
||||||
|
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
|
||||||
|
const doesProjectNameExist = await knex.schema.hasColumn(TableName.AuditLog, "projectName");
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
if (doesOrgIdExist) {
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
}
|
||||||
|
if (doesProjectIdExist) {
|
||||||
|
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove normalized field
|
||||||
|
if (doesProjectNameExist) {
|
||||||
|
t.dropColumn("projectName");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
// org default role
|
||||||
|
if (await knex.schema.hasTable(TableName.Organization)) {
|
||||||
|
const hasDefaultRoleCol = await knex.schema.hasColumn(TableName.Organization, "defaultMembershipRole");
|
||||||
|
|
||||||
|
if (!hasDefaultRoleCol) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (tb) => {
|
||||||
|
tb.string("defaultMembershipRole").notNullable().defaultTo("member");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
// org default role
|
||||||
|
if (await knex.schema.hasTable(TableName.Organization)) {
|
||||||
|
const hasDefaultRoleCol = await knex.schema.hasColumn(TableName.Organization, "defaultMembershipRole");
|
||||||
|
|
||||||
|
if (hasDefaultRoleCol) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (tb) => {
|
||||||
|
tb.dropColumn("defaultMembershipRole");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { packRules, unpackRules } from "@casl/ability/extra";
|
||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import {
|
||||||
|
backfillPermissionV1SchemaToV2Schema,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
const CHUNK_SIZE = 1000;
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasVersion = await knex.schema.hasColumn(TableName.ProjectRoles, "version");
|
||||||
|
if (!hasVersion) {
|
||||||
|
await knex.schema.alterTable(TableName.ProjectRoles, (t) => {
|
||||||
|
t.integer("version").defaultTo(1).notNullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
const docs = await knex(TableName.ProjectRoles).select("*");
|
||||||
|
const updatedDocs = docs
|
||||||
|
.filter((i) => {
|
||||||
|
const permissionString = JSON.stringify(i.permissions || []);
|
||||||
|
return (
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretImports) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.DynamicSecrets)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((el) => ({
|
||||||
|
...el,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(unpackRules(el.permissions), true)))
|
||||||
|
}));
|
||||||
|
if (updatedDocs.length) {
|
||||||
|
for (let i = 0; i < updatedDocs.length; i += CHUNK_SIZE) {
|
||||||
|
const chunk = updatedDocs.slice(i, i + CHUNK_SIZE);
|
||||||
|
await knex(TableName.ProjectRoles).insert(chunk).onConflict("id").merge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// secret permission is split into multiple ones like secrets, folders, imports and dynamic-secrets
|
||||||
|
// so we just find all the privileges with respective mapping and map it as needed
|
||||||
|
const identityPrivileges = await knex(TableName.IdentityProjectAdditionalPrivilege).select("*");
|
||||||
|
const updatedIdentityPrivilegesDocs = identityPrivileges
|
||||||
|
.filter((i) => {
|
||||||
|
const permissionString = JSON.stringify(i.permissions || []);
|
||||||
|
return (
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretImports) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.DynamicSecrets) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretFolders)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((el) => ({
|
||||||
|
...el,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(unpackRules(el.permissions))))
|
||||||
|
}));
|
||||||
|
if (updatedIdentityPrivilegesDocs.length) {
|
||||||
|
for (let i = 0; i < updatedIdentityPrivilegesDocs.length; i += CHUNK_SIZE) {
|
||||||
|
const chunk = updatedIdentityPrivilegesDocs.slice(i, i + CHUNK_SIZE);
|
||||||
|
await knex(TableName.IdentityProjectAdditionalPrivilege).insert(chunk).onConflict("id").merge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userPrivileges = await knex(TableName.ProjectUserAdditionalPrivilege).select("*");
|
||||||
|
const updatedUserPrivilegeDocs = userPrivileges
|
||||||
|
.filter((i) => {
|
||||||
|
const permissionString = JSON.stringify(i.permissions || []);
|
||||||
|
return (
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretImports) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.DynamicSecrets) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretFolders)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((el) => ({
|
||||||
|
...el,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(unpackRules(el.permissions))))
|
||||||
|
}));
|
||||||
|
if (docs.length) {
|
||||||
|
for (let i = 0; i < updatedUserPrivilegeDocs.length; i += CHUNK_SIZE) {
|
||||||
|
const chunk = updatedUserPrivilegeDocs.slice(i, i + CHUNK_SIZE);
|
||||||
|
await knex(TableName.ProjectUserAdditionalPrivilege).insert(chunk).onConflict("id").merge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasVersion = await knex.schema.hasColumn(TableName.ProjectRoles, "version");
|
||||||
|
if (hasVersion) {
|
||||||
|
await knex.schema.alterTable(TableName.ProjectRoles, (t) => {
|
||||||
|
t.dropColumn("version");
|
||||||
|
});
|
||||||
|
|
||||||
|
// permission change can be ignored
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
const BATCH_SIZE = 30_000;
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasAuthMethodColumnAccessToken = await knex.schema.hasColumn(TableName.IdentityAccessToken, "authMethod");
|
||||||
|
|
||||||
|
if (!hasAuthMethodColumnAccessToken) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => {
|
||||||
|
t.string("authMethod").nullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
let nullableAccessTokens = await knex(TableName.IdentityAccessToken).whereNull("authMethod").limit(BATCH_SIZE);
|
||||||
|
let totalUpdated = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const batchIds = nullableAccessTokens.map((token) => token.id);
|
||||||
|
|
||||||
|
// ! Update the auth method column in batches for the current batch
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await knex(TableName.IdentityAccessToken)
|
||||||
|
.whereIn("id", batchIds)
|
||||||
|
.update({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore because generate schema happens after this
|
||||||
|
authMethod: knex(TableName.Identity)
|
||||||
|
.select("authMethod")
|
||||||
|
.whereRaw(`${TableName.IdentityAccessToken}."identityId" = ${TableName.Identity}.id`)
|
||||||
|
.whereNotNull("authMethod")
|
||||||
|
.first()
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
nullableAccessTokens = await knex(TableName.IdentityAccessToken).whereNull("authMethod").limit(BATCH_SIZE);
|
||||||
|
|
||||||
|
totalUpdated += batchIds.length;
|
||||||
|
console.log(`Updated ${batchIds.length} access tokens in batch <> Total updated: ${totalUpdated}`);
|
||||||
|
} while (nullableAccessTokens.length > 0);
|
||||||
|
|
||||||
|
// ! We delete all access tokens where the identity has no auth method set!
|
||||||
|
// ! Which means un-configured identities that for some reason have access tokens, will have their access tokens deleted.
|
||||||
|
await knex(TableName.IdentityAccessToken)
|
||||||
|
.whereNotExists((queryBuilder) => {
|
||||||
|
void queryBuilder
|
||||||
|
.select("id")
|
||||||
|
.from(TableName.Identity)
|
||||||
|
.whereRaw(`${TableName.IdentityAccessToken}."identityId" = ${TableName.Identity}.id`)
|
||||||
|
.whereNotNull("authMethod");
|
||||||
|
})
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
// Finally we set the authMethod to notNullable after populating the column.
|
||||||
|
// This will fail if the data is not populated correctly, so it's safe.
|
||||||
|
await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => {
|
||||||
|
t.string("authMethod").notNullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ! We aren't dropping the authMethod column from the Identity itself, because we wan't to be able to easily rollback for the time being.
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasAuthMethodColumnAccessToken = await knex.schema.hasColumn(TableName.IdentityAccessToken, "authMethod");
|
||||||
|
|
||||||
|
if (hasAuthMethodColumnAccessToken) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => {
|
||||||
|
t.dropColumn("authMethod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = { transaction: false };
|
||||||
|
export { config };
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityMetadata, "value")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityMetadata, (t) => {
|
||||||
|
t.string("value", 1020).alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityMetadata, "value")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityMetadata, (t) => {
|
||||||
|
t.string("value", 255).alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "@app/db/utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
// add external group to org role mapping table
|
||||||
|
if (!(await knex.schema.hasTable(TableName.ExternalGroupOrgRoleMapping))) {
|
||||||
|
await knex.schema.createTable(TableName.ExternalGroupOrgRoleMapping, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("groupName").notNullable();
|
||||||
|
t.index("groupName");
|
||||||
|
t.string("role").notNullable();
|
||||||
|
t.uuid("roleId");
|
||||||
|
t.foreign("roleId").references("id").inTable(TableName.OrgRoles);
|
||||||
|
t.uuid("orgId").notNullable();
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.unique(["orgId", "groupName"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.ExternalGroupOrgRoleMapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.ExternalGroupOrgRoleMapping)) {
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.ExternalGroupOrgRoleMapping);
|
||||||
|
|
||||||
|
await knex.schema.dropTable(TableName.ExternalGroupOrgRoleMapping);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.Organization, "enforceMfa"))) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (tb) => {
|
||||||
|
tb.boolean("enforceMfa").defaultTo(false).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.Organization, "enforceMfa")) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (t) => {
|
||||||
|
t.dropColumn("enforceMfa");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.SamlConfig, "orgId")) {
|
||||||
|
await knex.schema.alterTable(TableName.SamlConfig, (t) => {
|
||||||
|
t.dropForeign("orgId");
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.SamlConfig, "orgId")) {
|
||||||
|
await knex.schema.alterTable(TableName.SamlConfig, (t) => {
|
||||||
|
t.dropForeign("orgId");
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export const dropConstraintIfExists = (tableName: TableName, constraintName: string, knex: Knex) =>
|
||||||
|
knex.raw(`ALTER TABLE ${tableName} DROP CONSTRAINT IF EXISTS ${constraintName};`);
|
@ -54,7 +54,7 @@ export const getSecretManagerDataKey = async (knex: Knex, projectId: string) =>
|
|||||||
} else {
|
} else {
|
||||||
const [kmsDoc] = await knex(TableName.KmsKey)
|
const [kmsDoc] = await knex(TableName.KmsKey)
|
||||||
.insert({
|
.insert({
|
||||||
slug: slugify(alphaNumericNanoId(8).toLowerCase()),
|
name: slugify(alphaNumericNanoId(8).toLowerCase()),
|
||||||
orgId: project.orgId,
|
orgId: project.orgId,
|
||||||
isReserved: false
|
isReserved: false
|
||||||
})
|
})
|
||||||
|
@ -12,7 +12,8 @@ export const AccessApprovalPoliciesApproversSchema = z.object({
|
|||||||
policyId: z.string().uuid(),
|
policyId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
approverUserId: z.string().uuid()
|
approverUserId: z.string().uuid().nullable().optional(),
|
||||||
|
approverGroupId: z.string().uuid().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAccessApprovalPoliciesApprovers = z.infer<typeof AccessApprovalPoliciesApproversSchema>;
|
export type TAccessApprovalPoliciesApprovers = z.infer<typeof AccessApprovalPoliciesApproversSchema>;
|
||||||
|
@ -9,6 +9,7 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
|
|
||||||
export const AccessApprovalRequestsReviewersSchema = z.object({
|
export const AccessApprovalRequestsReviewersSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
|
member: z.string().uuid().nullable().optional(),
|
||||||
status: z.string(),
|
status: z.string(),
|
||||||
requestId: z.string().uuid(),
|
requestId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
|
@ -11,6 +11,7 @@ export const AccessApprovalRequestsSchema = z.object({
|
|||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
policyId: z.string().uuid(),
|
policyId: z.string().uuid(),
|
||||||
privilegeId: z.string().uuid().nullable().optional(),
|
privilegeId: z.string().uuid().nullable().optional(),
|
||||||
|
requestedBy: z.string().uuid().nullable().optional(),
|
||||||
isTemporary: z.boolean(),
|
isTemporary: z.boolean(),
|
||||||
temporaryRange: z.string().nullable().optional(),
|
temporaryRange: z.string().nullable().optional(),
|
||||||
permissions: z.unknown(),
|
permissions: z.unknown(),
|
||||||
|
@ -20,7 +20,8 @@ export const AuditLogsSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
orgId: z.string().uuid().nullable().optional(),
|
orgId: z.string().uuid().nullable().optional(),
|
||||||
projectId: z.string().nullable().optional()
|
projectId: z.string().nullable().optional(),
|
||||||
|
projectName: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAuditLogs = z.infer<typeof AuditLogsSchema>;
|
export type TAuditLogs = z.infer<typeof AuditLogsSchema>;
|
||||||
|
@ -28,7 +28,8 @@ export const CertificateAuthoritiesSchema = z.object({
|
|||||||
keyAlgorithm: z.string(),
|
keyAlgorithm: z.string(),
|
||||||
notBefore: z.date().nullable().optional(),
|
notBefore: z.date().nullable().optional(),
|
||||||
notAfter: z.date().nullable().optional(),
|
notAfter: z.date().nullable().optional(),
|
||||||
activeCaCertId: z.string().uuid().nullable().optional()
|
activeCaCertId: z.string().uuid().nullable().optional(),
|
||||||
|
requireTemplateForIssuance: z.boolean().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificateAuthorities = z.infer<typeof CertificateAuthoritiesSchema>;
|
export type TCertificateAuthorities = z.infer<typeof CertificateAuthoritiesSchema>;
|
||||||
|
@ -14,7 +14,8 @@ export const CertificateAuthorityCrlSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
caId: z.string().uuid(),
|
caId: z.string().uuid(),
|
||||||
encryptedCrl: zodBuffer
|
encryptedCrl: zodBuffer,
|
||||||
|
caSecretId: z.string().uuid()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificateAuthorityCrl = z.infer<typeof CertificateAuthorityCrlSchema>;
|
export type TCertificateAuthorityCrl = z.infer<typeof CertificateAuthorityCrlSchema>;
|
||||||
|
29
backend/src/db/schemas/certificate-template-est-configs.ts
Normal file
29
backend/src/db/schemas/certificate-template-est-configs.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const CertificateTemplateEstConfigsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
certificateTemplateId: z.string().uuid(),
|
||||||
|
encryptedCaChain: zodBuffer,
|
||||||
|
hashedPassphrase: z.string(),
|
||||||
|
isEnabled: z.boolean(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TCertificateTemplateEstConfigs = z.infer<typeof CertificateTemplateEstConfigsSchema>;
|
||||||
|
export type TCertificateTemplateEstConfigsInsert = Omit<
|
||||||
|
z.input<typeof CertificateTemplateEstConfigsSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TCertificateTemplateEstConfigsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof CertificateTemplateEstConfigsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
@ -16,7 +16,9 @@ export const CertificateTemplatesSchema = z.object({
|
|||||||
subjectAlternativeName: z.string(),
|
subjectAlternativeName: z.string(),
|
||||||
ttl: z.string(),
|
ttl: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
keyUsages: z.string().array().nullable().optional(),
|
||||||
|
extendedKeyUsages: z.string().array().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificateTemplates = z.infer<typeof CertificateTemplatesSchema>;
|
export type TCertificateTemplates = z.infer<typeof CertificateTemplatesSchema>;
|
||||||
|
@ -22,7 +22,9 @@ export const CertificatesSchema = z.object({
|
|||||||
revocationReason: z.number().nullable().optional(),
|
revocationReason: z.number().nullable().optional(),
|
||||||
altNames: z.string().default("").nullable().optional(),
|
altNames: z.string().default("").nullable().optional(),
|
||||||
caCertId: z.string().uuid(),
|
caCertId: z.string().uuid(),
|
||||||
certificateTemplateId: z.string().uuid().nullable().optional()
|
certificateTemplateId: z.string().uuid().nullable().optional(),
|
||||||
|
keyUsages: z.string().array().nullable().optional(),
|
||||||
|
extendedKeyUsages: z.string().array().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
||||||
|
27
backend/src/db/schemas/external-group-org-role-mappings.ts
Normal file
27
backend/src/db/schemas/external-group-org-role-mappings.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const ExternalGroupOrgRoleMappingsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
groupName: z.string(),
|
||||||
|
role: z.string(),
|
||||||
|
roleId: z.string().uuid().nullable().optional(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TExternalGroupOrgRoleMappings = z.infer<typeof ExternalGroupOrgRoleMappingsSchema>;
|
||||||
|
export type TExternalGroupOrgRoleMappingsInsert = Omit<
|
||||||
|
z.input<typeof ExternalGroupOrgRoleMappingsSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TExternalGroupOrgRoleMappingsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof ExternalGroupOrgRoleMappingsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
@ -20,7 +20,8 @@ export const IdentityAccessTokensSchema = z.object({
|
|||||||
identityId: z.string().uuid(),
|
identityId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
name: z.string().nullable().optional()
|
name: z.string().nullable().optional(),
|
||||||
|
authMethod: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityAccessTokens = z.infer<typeof IdentityAccessTokensSchema>;
|
export type TIdentityAccessTokens = z.infer<typeof IdentityAccessTokensSchema>;
|
||||||
|
23
backend/src/db/schemas/identity-metadata.ts
Normal file
23
backend/src/db/schemas/identity-metadata.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const IdentityMetadataSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
key: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
userId: z.string().uuid().nullable().optional(),
|
||||||
|
identityId: z.string().uuid().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TIdentityMetadata = z.infer<typeof IdentityMetadataSchema>;
|
||||||
|
export type TIdentityMetadataInsert = Omit<z.input<typeof IdentityMetadataSchema>, TImmutableDBKeys>;
|
||||||
|
export type TIdentityMetadataUpdate = Partial<Omit<z.input<typeof IdentityMetadataSchema>, TImmutableDBKeys>>;
|
@ -14,6 +14,7 @@ export * from "./certificate-authority-crl";
|
|||||||
export * from "./certificate-authority-secret";
|
export * from "./certificate-authority-secret";
|
||||||
export * from "./certificate-bodies";
|
export * from "./certificate-bodies";
|
||||||
export * from "./certificate-secrets";
|
export * from "./certificate-secrets";
|
||||||
|
export * from "./certificate-template-est-configs";
|
||||||
export * from "./certificate-templates";
|
export * from "./certificate-templates";
|
||||||
export * from "./certificates";
|
export * from "./certificates";
|
||||||
export * from "./dynamic-secret-leases";
|
export * from "./dynamic-secret-leases";
|
||||||
@ -30,6 +31,7 @@ export * from "./identity-aws-auths";
|
|||||||
export * from "./identity-azure-auths";
|
export * from "./identity-azure-auths";
|
||||||
export * from "./identity-gcp-auths";
|
export * from "./identity-gcp-auths";
|
||||||
export * from "./identity-kubernetes-auths";
|
export * from "./identity-kubernetes-auths";
|
||||||
|
export * from "./identity-metadata";
|
||||||
export * from "./identity-oidc-auths";
|
export * from "./identity-oidc-auths";
|
||||||
export * from "./identity-org-memberships";
|
export * from "./identity-org-memberships";
|
||||||
export * from "./identity-project-additional-privilege";
|
export * from "./identity-project-additional-privilege";
|
||||||
@ -61,6 +63,7 @@ export * from "./project-environments";
|
|||||||
export * from "./project-keys";
|
export * from "./project-keys";
|
||||||
export * from "./project-memberships";
|
export * from "./project-memberships";
|
||||||
export * from "./project-roles";
|
export * from "./project-roles";
|
||||||
|
export * from "./project-slack-configs";
|
||||||
export * from "./project-user-additional-privilege";
|
export * from "./project-user-additional-privilege";
|
||||||
export * from "./project-user-membership-roles";
|
export * from "./project-user-membership-roles";
|
||||||
export * from "./projects";
|
export * from "./projects";
|
||||||
@ -100,6 +103,7 @@ export * from "./secret-versions-v2";
|
|||||||
export * from "./secrets";
|
export * from "./secrets";
|
||||||
export * from "./secrets-v2";
|
export * from "./secrets-v2";
|
||||||
export * from "./service-tokens";
|
export * from "./service-tokens";
|
||||||
|
export * from "./slack-integrations";
|
||||||
export * from "./super-admin";
|
export * from "./super-admin";
|
||||||
export * from "./trusted-ips";
|
export * from "./trusted-ips";
|
||||||
export * from "./user-actions";
|
export * from "./user-actions";
|
||||||
@ -108,3 +112,4 @@ export * from "./user-encryption-keys";
|
|||||||
export * from "./user-group-membership";
|
export * from "./user-group-membership";
|
||||||
export * from "./users";
|
export * from "./users";
|
||||||
export * from "./webhooks";
|
export * from "./webhooks";
|
||||||
|
export * from "./workflow-integrations";
|
||||||
|
@ -13,9 +13,11 @@ export const KmsKeysSchema = z.object({
|
|||||||
isDisabled: z.boolean().default(false).nullable().optional(),
|
isDisabled: z.boolean().default(false).nullable().optional(),
|
||||||
isReserved: z.boolean().default(true).nullable().optional(),
|
isReserved: z.boolean().default(true).nullable().optional(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
slug: z.string(),
|
name: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
projectId: z.string().nullable().optional(),
|
||||||
|
slug: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
||||||
|
@ -3,6 +3,7 @@ import { z } from "zod";
|
|||||||
export enum TableName {
|
export enum TableName {
|
||||||
Users = "users",
|
Users = "users",
|
||||||
CertificateAuthority = "certificate_authorities",
|
CertificateAuthority = "certificate_authorities",
|
||||||
|
CertificateTemplateEstConfig = "certificate_template_est_configs",
|
||||||
CertificateAuthorityCert = "certificate_authority_certs",
|
CertificateAuthorityCert = "certificate_authority_certs",
|
||||||
CertificateAuthoritySecret = "certificate_authority_secret",
|
CertificateAuthoritySecret = "certificate_authority_secret",
|
||||||
CertificateAuthorityCrl = "certificate_authority_crl",
|
CertificateAuthorityCrl = "certificate_authority_crl",
|
||||||
@ -16,6 +17,7 @@ export enum TableName {
|
|||||||
Groups = "groups",
|
Groups = "groups",
|
||||||
GroupProjectMembership = "group_project_memberships",
|
GroupProjectMembership = "group_project_memberships",
|
||||||
GroupProjectMembershipRole = "group_project_membership_roles",
|
GroupProjectMembershipRole = "group_project_membership_roles",
|
||||||
|
ExternalGroupOrgRoleMapping = "external_group_org_role_mappings",
|
||||||
UserGroupMembership = "user_group_membership",
|
UserGroupMembership = "user_group_membership",
|
||||||
UserAliases = "user_aliases",
|
UserAliases = "user_aliases",
|
||||||
UserEncryptionKey = "user_encryption_keys",
|
UserEncryptionKey = "user_encryption_keys",
|
||||||
@ -69,6 +71,8 @@ export enum TableName {
|
|||||||
IdentityProjectMembership = "identity_project_memberships",
|
IdentityProjectMembership = "identity_project_memberships",
|
||||||
IdentityProjectMembershipRole = "identity_project_membership_role",
|
IdentityProjectMembershipRole = "identity_project_membership_role",
|
||||||
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
|
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
|
||||||
|
// used by both identity and users
|
||||||
|
IdentityMetadata = "identity_metadata",
|
||||||
ScimToken = "scim_tokens",
|
ScimToken = "scim_tokens",
|
||||||
AccessApprovalPolicy = "access_approval_policies",
|
AccessApprovalPolicy = "access_approval_policies",
|
||||||
AccessApprovalPolicyApprover = "access_approval_policies_approvers",
|
AccessApprovalPolicyApprover = "access_approval_policies_approvers",
|
||||||
@ -113,7 +117,10 @@ export enum TableName {
|
|||||||
InternalKms = "internal_kms",
|
InternalKms = "internal_kms",
|
||||||
InternalKmsKeyVersion = "internal_kms_key_version",
|
InternalKmsKeyVersion = "internal_kms_key_version",
|
||||||
// @depreciated
|
// @depreciated
|
||||||
KmsKeyVersion = "kms_key_versions"
|
KmsKeyVersion = "kms_key_versions",
|
||||||
|
WorkflowIntegrations = "workflow_integrations",
|
||||||
|
SlackIntegrations = "slack_integrations",
|
||||||
|
ProjectSlackConfigs = "project_slack_configs"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
||||||
@ -182,7 +189,7 @@ export enum ProjectUpgradeStatus {
|
|||||||
|
|
||||||
export enum IdentityAuthMethod {
|
export enum IdentityAuthMethod {
|
||||||
TOKEN_AUTH = "token-auth",
|
TOKEN_AUTH = "token-auth",
|
||||||
Univeral = "universal-auth",
|
UNIVERSAL_AUTH = "universal-auth",
|
||||||
KUBERNETES_AUTH = "kubernetes-auth",
|
KUBERNETES_AUTH = "kubernetes-auth",
|
||||||
GCP_AUTH = "gcp-auth",
|
GCP_AUTH = "gcp-auth",
|
||||||
AWS_AUTH = "aws-auth",
|
AWS_AUTH = "aws-auth",
|
||||||
|
@ -26,7 +26,8 @@ export const OidcConfigsSchema = z.object({
|
|||||||
isActive: z.boolean(),
|
isActive: z.boolean(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
orgId: z.string().uuid()
|
orgId: z.string().uuid(),
|
||||||
|
lastUsed: z.date().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOidcConfigs = z.infer<typeof OidcConfigsSchema>;
|
export type TOidcConfigs = z.infer<typeof OidcConfigsSchema>;
|
||||||
|
@ -19,7 +19,9 @@ export const OrganizationsSchema = z.object({
|
|||||||
authEnforced: z.boolean().default(false).nullable().optional(),
|
authEnforced: z.boolean().default(false).nullable().optional(),
|
||||||
scimEnabled: z.boolean().default(false).nullable().optional(),
|
scimEnabled: z.boolean().default(false).nullable().optional(),
|
||||||
kmsDefaultKeyId: z.string().uuid().nullable().optional(),
|
kmsDefaultKeyId: z.string().uuid().nullable().optional(),
|
||||||
kmsEncryptedDataKey: zodBuffer.nullable().optional()
|
kmsEncryptedDataKey: zodBuffer.nullable().optional(),
|
||||||
|
defaultMembershipRole: z.string().default("member"),
|
||||||
|
enforceMfa: z.boolean().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
||||||
|
24
backend/src/db/schemas/project-slack-configs.ts
Normal file
24
backend/src/db/schemas/project-slack-configs.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const ProjectSlackConfigsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
projectId: z.string(),
|
||||||
|
slackIntegrationId: z.string().uuid(),
|
||||||
|
isAccessRequestNotificationEnabled: z.boolean().default(false),
|
||||||
|
accessRequestChannels: z.string().default(""),
|
||||||
|
isSecretRequestNotificationEnabled: z.boolean().default(false),
|
||||||
|
secretRequestChannels: z.string().default(""),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TProjectSlackConfigs = z.infer<typeof ProjectSlackConfigsSchema>;
|
||||||
|
export type TProjectSlackConfigsInsert = Omit<z.input<typeof ProjectSlackConfigsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TProjectSlackConfigsUpdate = Partial<Omit<z.input<typeof ProjectSlackConfigsSchema>, TImmutableDBKeys>>;
|
@ -10,6 +10,7 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
export const ProjectUserAdditionalPrivilegeSchema = z.object({
|
export const ProjectUserAdditionalPrivilegeSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
|
projectMembershipId: z.string().uuid().nullable().optional(),
|
||||||
isTemporary: z.boolean().default(false),
|
isTemporary: z.boolean().default(false),
|
||||||
temporaryMode: z.string().nullable().optional(),
|
temporaryMode: z.string().nullable().optional(),
|
||||||
temporaryRange: z.string().nullable().optional(),
|
temporaryRange: z.string().nullable().optional(),
|
||||||
|
@ -12,7 +12,8 @@ export const SecretApprovalPoliciesApproversSchema = z.object({
|
|||||||
policyId: z.string().uuid(),
|
policyId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
approverUserId: z.string().uuid()
|
approverUserId: z.string().uuid().nullable().optional(),
|
||||||
|
approverGroupId: z.string().uuid().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSecretApprovalPoliciesApprovers = z.infer<typeof SecretApprovalPoliciesApproversSchema>;
|
export type TSecretApprovalPoliciesApprovers = z.infer<typeof SecretApprovalPoliciesApproversSchema>;
|
||||||
|
@ -5,14 +5,16 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const SecretSharingSchema = z.object({
|
export const SecretSharingSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
encryptedValue: z.string(),
|
encryptedValue: z.string().nullable().optional(),
|
||||||
iv: z.string(),
|
iv: z.string().nullable().optional(),
|
||||||
tag: z.string(),
|
tag: z.string().nullable().optional(),
|
||||||
hashedHex: z.string(),
|
hashedHex: z.string().nullable().optional(),
|
||||||
expiresAt: z.date(),
|
expiresAt: z.date(),
|
||||||
userId: z.string().uuid().nullable().optional(),
|
userId: z.string().uuid().nullable().optional(),
|
||||||
orgId: z.string().uuid().nullable().optional(),
|
orgId: z.string().uuid().nullable().optional(),
|
||||||
@ -21,7 +23,10 @@ export const SecretSharingSchema = z.object({
|
|||||||
expiresAfterViews: z.number().nullable().optional(),
|
expiresAfterViews: z.number().nullable().optional(),
|
||||||
accessType: z.string().default("anyone"),
|
accessType: z.string().default("anyone"),
|
||||||
name: z.string().nullable().optional(),
|
name: z.string().nullable().optional(),
|
||||||
lastViewedAt: z.date().nullable().optional()
|
lastViewedAt: z.date().nullable().optional(),
|
||||||
|
password: z.string().nullable().optional(),
|
||||||
|
encryptedSecret: zodBuffer.nullable().optional(),
|
||||||
|
identifier: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
|
export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
|
||||||
|
27
backend/src/db/schemas/slack-integrations.ts
Normal file
27
backend/src/db/schemas/slack-integrations.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const SlackIntegrationsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
teamId: z.string(),
|
||||||
|
teamName: z.string(),
|
||||||
|
slackUserId: z.string(),
|
||||||
|
slackAppId: z.string(),
|
||||||
|
encryptedBotAccessToken: zodBuffer,
|
||||||
|
slackBotId: z.string(),
|
||||||
|
slackBotUserId: z.string(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSlackIntegrations = z.infer<typeof SlackIntegrationsSchema>;
|
||||||
|
export type TSlackIntegrationsInsert = Omit<z.input<typeof SlackIntegrationsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TSlackIntegrationsUpdate = Partial<Omit<z.input<typeof SlackIntegrationsSchema>, TImmutableDBKeys>>;
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const SuperAdminSchema = z.object({
|
export const SuperAdminSchema = z.object({
|
||||||
@ -19,7 +21,9 @@ export const SuperAdminSchema = z.object({
|
|||||||
trustLdapEmails: z.boolean().default(false).nullable().optional(),
|
trustLdapEmails: z.boolean().default(false).nullable().optional(),
|
||||||
trustOidcEmails: z.boolean().default(false).nullable().optional(),
|
trustOidcEmails: z.boolean().default(false).nullable().optional(),
|
||||||
defaultAuthOrgId: z.string().uuid().nullable().optional(),
|
defaultAuthOrgId: z.string().uuid().nullable().optional(),
|
||||||
enabledLoginMethods: z.string().array().nullable().optional()
|
enabledLoginMethods: z.string().array().nullable().optional(),
|
||||||
|
encryptedSlackClientId: zodBuffer.nullable().optional(),
|
||||||
|
encryptedSlackClientSecret: zodBuffer.nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
||||||
|
22
backend/src/db/schemas/workflow-integrations.ts
Normal file
22
backend/src/db/schemas/workflow-integrations.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const WorkflowIntegrationsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
integration: z.string(),
|
||||||
|
slug: z.string(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TWorkflowIntegrations = z.infer<typeof WorkflowIntegrationsSchema>;
|
||||||
|
export type TWorkflowIntegrationsInsert = Omit<z.input<typeof WorkflowIntegrationsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TWorkflowIntegrationsUpdate = Partial<Omit<z.input<typeof WorkflowIntegrationsSchema>, TImmutableDBKeys>>;
|
@ -16,7 +16,7 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
id: seedData1.machineIdentity.id,
|
id: seedData1.machineIdentity.id,
|
||||||
name: seedData1.machineIdentity.name,
|
name: seedData1.machineIdentity.name,
|
||||||
authMethod: IdentityAuthMethod.Univeral
|
authMethod: IdentityAuthMethod.UNIVERSAL_AUTH
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const identityUa = await knex(TableName.IdentityUniversalAuth)
|
const identityUa = await knex(TableName.IdentityUniversalAuth)
|
||||||
|
173
backend/src/ee/routes/est/certificate-est-router.ts
Normal file
173
backend/src/ee/routes/est/certificate-est-router.ts
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import bcrypt from "bcrypt";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
|
||||||
|
export const registerCertificateEstRouter = async (server: FastifyZodProvider) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
// add support for CSR bodies
|
||||||
|
server.addContentTypeParser("application/pkcs10", { parseAs: "string" }, (_, body, done) => {
|
||||||
|
try {
|
||||||
|
let csrBody = body as string;
|
||||||
|
// some EST clients send CSRs in PEM format and some in base64 format
|
||||||
|
// for CSRs sent in PEM, we leave them as is
|
||||||
|
// for CSRs sent in base64, we preprocess them to remove new lines and spaces
|
||||||
|
if (!csrBody.includes("BEGIN CERTIFICATE REQUEST")) {
|
||||||
|
csrBody = csrBody.replace(/\n/g, "").replace(/ /g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
done(null, csrBody);
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error;
|
||||||
|
done(error, undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Authenticate EST client using Passphrase
|
||||||
|
server.addHook("onRequest", async (req, res) => {
|
||||||
|
const { authorization } = req.headers;
|
||||||
|
const urlFragments = req.url.split("/");
|
||||||
|
|
||||||
|
// cacerts endpoint should not have any authentication
|
||||||
|
if (urlFragments[urlFragments.length - 1] === "cacerts") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authorization) {
|
||||||
|
const wwwAuthenticateHeader = "WWW-Authenticate";
|
||||||
|
const errAuthRequired = "Authentication required";
|
||||||
|
|
||||||
|
await res.hijack();
|
||||||
|
|
||||||
|
// definitive connection timeout to clean-up open connections and prevent memory leak
|
||||||
|
res.raw.setTimeout(10 * 1000, () => {
|
||||||
|
res.raw.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
res.raw.setHeader(wwwAuthenticateHeader, `Basic realm="infisical"`);
|
||||||
|
res.raw.setHeader("Content-Length", 0);
|
||||||
|
res.raw.statusCode = 401;
|
||||||
|
|
||||||
|
// Write the error message to the response without ending the connection
|
||||||
|
res.raw.write(errAuthRequired);
|
||||||
|
|
||||||
|
// flush headers
|
||||||
|
res.raw.flushHeaders();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const certificateTemplateId = urlFragments.slice(-2)[0];
|
||||||
|
const estConfig = await server.services.certificateTemplate.getEstConfiguration({
|
||||||
|
isInternal: true,
|
||||||
|
certificateTemplateId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!estConfig.isEnabled) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "EST is disabled"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawCredential = authorization?.split(" ").pop();
|
||||||
|
if (!rawCredential) {
|
||||||
|
throw new UnauthorizedError({ message: "Missing HTTP credentials" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// expected format is user:password
|
||||||
|
const basicCredential = atob(rawCredential);
|
||||||
|
const password = basicCredential.split(":").pop();
|
||||||
|
if (!password) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "No password provided"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPasswordValid = await bcrypt.compare(password, estConfig.hashedPassphrase);
|
||||||
|
if (!isPasswordValid) {
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: "Invalid credentials"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:certificateTemplateId/simpleenroll",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.string().min(1),
|
||||||
|
params: z.object({
|
||||||
|
certificateTemplateId: z.string().min(1)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req, res) => {
|
||||||
|
void res.header("Content-Type", "application/pkcs7-mime; smime-type=certs-only");
|
||||||
|
void res.header("Content-Transfer-Encoding", "base64");
|
||||||
|
|
||||||
|
return server.services.certificateEst.simpleEnroll({
|
||||||
|
csr: req.body,
|
||||||
|
certificateTemplateId: req.params.certificateTemplateId,
|
||||||
|
sslClientCert: req.headers[appCfg.SSL_CLIENT_CERTIFICATE_HEADER_KEY] as string
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:certificateTemplateId/simplereenroll",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.string().min(1),
|
||||||
|
params: z.object({
|
||||||
|
certificateTemplateId: z.string().min(1)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req, res) => {
|
||||||
|
void res.header("Content-Type", "application/pkcs7-mime; smime-type=certs-only");
|
||||||
|
void res.header("Content-Transfer-Encoding", "base64");
|
||||||
|
|
||||||
|
return server.services.certificateEst.simpleReenroll({
|
||||||
|
csr: req.body,
|
||||||
|
certificateTemplateId: req.params.certificateTemplateId,
|
||||||
|
sslClientCert: req.headers[appCfg.SSL_CLIENT_CERTIFICATE_HEADER_KEY] as string
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:certificateTemplateId/cacerts",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
certificateTemplateId: z.string().min(1)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req, res) => {
|
||||||
|
void res.header("Content-Type", "application/pkcs7-mime; smime-type=certs-only");
|
||||||
|
void res.header("Content-Transfer-Encoding", "base64");
|
||||||
|
|
||||||
|
return server.services.certificateEst.getCaCerts({
|
||||||
|
certificateTemplateId: req.params.certificateTemplateId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -1,7 +1,9 @@
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { ApproverType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
||||||
import { EnforcementLevel } from "@app/lib/types";
|
import { EnforcementLevel } from "@app/lib/types";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@ -10,28 +12,32 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z
|
body: z.object({
|
||||||
.object({
|
projectSlug: z.string().trim(),
|
||||||
projectSlug: z.string().trim(),
|
name: z.string().optional(),
|
||||||
name: z.string().optional(),
|
secretPath: z.string().trim().default("/"),
|
||||||
secretPath: z.string().trim().default("/"),
|
environment: z.string(),
|
||||||
environment: z.string(),
|
approvers: z
|
||||||
approverUserIds: z.string().array().min(1),
|
.discriminatedUnion("type", [
|
||||||
approvals: z.number().min(1).default(1),
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() })
|
||||||
})
|
])
|
||||||
.refine((data) => data.approvals <= data.approverUserIds.length, {
|
.array()
|
||||||
path: ["approvals"],
|
.min(1, { message: "At least one approver should be provided" }),
|
||||||
message: "The number of approvals should be lower than the number of approvers."
|
approvals: z.number().min(1).default(1),
|
||||||
}),
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
||||||
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
approval: sapPubSchema
|
approval: sapPubSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const approval = await server.services.accessApprovalPolicy.createAccessApprovalPolicy({
|
const approval = await server.services.accessApprovalPolicy.createAccessApprovalPolicy({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@ -50,6 +56,9 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
projectSlug: z.string().trim()
|
projectSlug: z.string().trim()
|
||||||
@ -58,14 +67,15 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
approvals: sapPubSchema
|
approvals: sapPubSchema
|
||||||
.extend({
|
.extend({
|
||||||
userApprovers: z
|
approvers: z
|
||||||
.object({
|
.object({ type: z.nativeEnum(ApproverType), id: z.string().nullable().optional() })
|
||||||
userId: z.string()
|
.array()
|
||||||
})
|
.nullable()
|
||||||
.array(),
|
.optional()
|
||||||
secretPath: z.string().optional().nullable()
|
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
|
.nullable()
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -115,33 +125,37 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:policyId",
|
url: "/:policyId",
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
policyId: z.string()
|
policyId: z.string()
|
||||||
}),
|
}),
|
||||||
body: z
|
body: z.object({
|
||||||
.object({
|
name: z.string().optional(),
|
||||||
name: z.string().optional(),
|
secretPath: z
|
||||||
secretPath: z
|
.string()
|
||||||
.string()
|
.trim()
|
||||||
.trim()
|
.optional()
|
||||||
.optional()
|
.transform((val) => (val === "" ? "/" : val)),
|
||||||
.transform((val) => (val === "" ? "/" : val)),
|
approvers: z
|
||||||
approverUserIds: z.string().array().min(1),
|
.discriminatedUnion("type", [
|
||||||
approvals: z.number().min(1).default(1),
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() })
|
||||||
})
|
])
|
||||||
.refine((data) => data.approvals <= data.approverUserIds.length, {
|
.array()
|
||||||
path: ["approvals"],
|
.min(1, { message: "At least one approver should be provided" }),
|
||||||
message: "The number of approvals should be lower than the number of approvers."
|
approvals: z.number().min(1).optional(),
|
||||||
}),
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
||||||
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
approval: sapPubSchema
|
approval: sapPubSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
await server.services.accessApprovalPolicy.updateAccessApprovalPolicy({
|
await server.services.accessApprovalPolicy.updateAccessApprovalPolicy({
|
||||||
policyId: req.params.policyId,
|
policyId: req.params.policyId,
|
||||||
@ -157,6 +171,9 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:policyId",
|
url: "/:policyId",
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
policyId: z.string()
|
policyId: z.string()
|
||||||
@ -167,7 +184,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const approval = await server.services.accessApprovalPolicy.deleteAccessApprovalPolicy({
|
const approval = await server.services.accessApprovalPolicy.deleteAccessApprovalPolicy({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@ -179,4 +196,44 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
return { approval };
|
return { approval };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:policyId",
|
||||||
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
policyId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
approval: sapPubSchema.extend({
|
||||||
|
approvers: z
|
||||||
|
.object({
|
||||||
|
type: z.nativeEnum(ApproverType),
|
||||||
|
id: z.string().nullable().optional(),
|
||||||
|
name: z.string().nullable().optional()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.nullable()
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const approval = await server.services.accessApprovalPolicy.getAccessApprovalPolicyById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.params
|
||||||
|
});
|
||||||
|
|
||||||
|
return { approval };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,86 +1,55 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { CA_CRLS } from "@app/lib/api-docs";
|
||||||
import { CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
|
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
|
||||||
|
|
||||||
export const registerCaCrlRouter = async (server: FastifyZodProvider) => {
|
export const registerCaCrlRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:caId/crl",
|
url: "/:crlId",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
||||||
schema: {
|
schema: {
|
||||||
description: "Get CRL of the CA",
|
description: "Get CRL in DER format (deprecated)",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CRL.caId)
|
crlId: z.string().trim().describe(CA_CRLS.GET.crlId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.instanceof(Buffer)
|
||||||
crl: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CRL.crl)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req, res) => {
|
||||||
const { crl, ca } = await server.services.certificateAuthorityCrl.getCaCrl({
|
const { crl } = await server.services.certificateAuthorityCrl.getCrlById(req.params.crlId);
|
||||||
caId: req.params.caId,
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
res.header("Content-Type", "application/pkix-crl");
|
||||||
...req.auditLogInfo,
|
|
||||||
projectId: ca.projectId,
|
|
||||||
event: {
|
|
||||||
type: EventType.GET_CA_CRL,
|
|
||||||
metadata: {
|
|
||||||
caId: ca.id,
|
|
||||||
dn: ca.dn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return Buffer.from(crl);
|
||||||
crl
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// server.route({
|
server.route({
|
||||||
// method: "GET",
|
method: "GET",
|
||||||
// url: "/:caId/crl/rotate",
|
url: "/:crlId/der",
|
||||||
// config: {
|
config: {
|
||||||
// rateLimit: writeLimit
|
rateLimit: readLimit
|
||||||
// },
|
},
|
||||||
// onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
schema: {
|
||||||
// schema: {
|
description: "Get CRL in DER format",
|
||||||
// description: "Rotate CRL of the CA",
|
params: z.object({
|
||||||
// params: z.object({
|
crlId: z.string().trim().describe(CA_CRLS.GET.crlId)
|
||||||
// caId: z.string().trim()
|
}),
|
||||||
// }),
|
response: {
|
||||||
// response: {
|
200: z.instanceof(Buffer)
|
||||||
// 200: z.object({
|
}
|
||||||
// message: z.string()
|
},
|
||||||
// })
|
handler: async (req, res) => {
|
||||||
// }
|
const { crl } = await server.services.certificateAuthorityCrl.getCrlById(req.params.crlId);
|
||||||
// },
|
|
||||||
// handler: async (req) => {
|
res.header("Content-Type", "application/pkix-crl");
|
||||||
// await server.services.certificateAuthority.rotateCaCrl({
|
|
||||||
// caId: req.params.caId,
|
return Buffer.from(crl);
|
||||||
// actor: req.permission.type,
|
}
|
||||||
// actorId: req.permission.id,
|
});
|
||||||
// actorAuthMethod: req.permission.authMethod,
|
|
||||||
// actorOrgId: req.permission.orgId
|
|
||||||
// });
|
|
||||||
// return {
|
|
||||||
// message: "Successfully rotated CA CRL"
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
@ -77,6 +77,39 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/entra-id/users",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
tenantId: z.string().min(1).describe("The tenant ID of the Azure Entra ID"),
|
||||||
|
applicationId: z.string().min(1).describe("The application ID of the Azure Entra ID App Registration"),
|
||||||
|
clientSecret: z.string().min(1).describe("The client secret of the Azure Entra ID App Registration")
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z
|
||||||
|
.object({
|
||||||
|
name: z.string().min(1).describe("The name of the user"),
|
||||||
|
id: z.string().min(1).describe("The ID of the user"),
|
||||||
|
email: z.string().min(1).describe("The email of the user")
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const data = await server.services.dynamicSecret.fetchAzureEntraIdUsers({
|
||||||
|
tenantId: req.body.tenantId,
|
||||||
|
applicationId: req.body.applicationId,
|
||||||
|
clientSecret: req.body.clientSecret
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/:name",
|
url: "/:name",
|
||||||
@ -237,7 +270,7 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const dynamicSecretCfgs = await server.services.dynamicSecret.list({
|
const dynamicSecretCfgs = await server.services.dynamicSecret.listDynamicSecretsByEnv({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
@ -26,7 +26,7 @@ const sanitizedExternalSchemaForGetAll = KmsKeysSchema.pick({
|
|||||||
isDisabled: true,
|
isDisabled: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
slug: true
|
name: true
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
externalKms: ExternalKmsSchema.pick({
|
externalKms: ExternalKmsSchema.pick({
|
||||||
@ -57,7 +57,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z.string().min(1).trim().toLowerCase(),
|
name: z.string().min(1).trim().toLowerCase(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
provider: ExternalKmsInputSchema
|
provider: ExternalKmsInputSchema
|
||||||
}),
|
}),
|
||||||
@ -74,7 +74,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
slug: req.body.slug,
|
name: req.body.name,
|
||||||
provider: req.body.provider,
|
provider: req.body.provider,
|
||||||
description: req.body.description
|
description: req.body.description
|
||||||
});
|
});
|
||||||
@ -87,7 +87,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
kmsId: externalKms.id,
|
kmsId: externalKms.id,
|
||||||
provider: req.body.provider.type,
|
provider: req.body.provider.type,
|
||||||
slug: req.body.slug,
|
name: req.body.name,
|
||||||
description: req.body.description
|
description: req.body.description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,7 +108,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
id: z.string().trim().min(1)
|
id: z.string().trim().min(1)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z.string().min(1).trim().toLowerCase().optional(),
|
name: z.string().min(1).trim().toLowerCase().optional(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
provider: ExternalKmsInputUpdateSchema
|
provider: ExternalKmsInputUpdateSchema
|
||||||
}),
|
}),
|
||||||
@ -125,7 +125,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
slug: req.body.slug,
|
name: req.body.name,
|
||||||
provider: req.body.provider,
|
provider: req.body.provider,
|
||||||
description: req.body.description,
|
description: req.body.description,
|
||||||
id: req.params.id
|
id: req.params.id
|
||||||
@ -139,7 +139,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
kmsId: externalKms.id,
|
kmsId: externalKms.id,
|
||||||
provider: req.body.provider.type,
|
provider: req.body.provider.type,
|
||||||
slug: req.body.slug,
|
name: req.body.name,
|
||||||
description: req.body.description
|
description: req.body.description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,7 +182,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
type: EventType.DELETE_KMS,
|
type: EventType.DELETE_KMS,
|
||||||
metadata: {
|
metadata: {
|
||||||
kmsId: externalKms.id,
|
kmsId: externalKms.id,
|
||||||
slug: externalKms.slug
|
name: externalKms.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -224,7 +224,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
type: EventType.GET_KMS,
|
type: EventType.GET_KMS,
|
||||||
metadata: {
|
metadata: {
|
||||||
kmsId: externalKms.id,
|
kmsId: externalKms.id,
|
||||||
slug: externalKms.slug
|
name: externalKms.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -260,13 +260,13 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/slug/:slug",
|
url: "/name/:name",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slug: z.string().trim().min(1)
|
name: z.string().trim().min(1)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -276,12 +276,12 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const externalKms = await server.services.externalKms.findBySlug({
|
const externalKms = await server.services.externalKms.findByName({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
slug: req.params.slug
|
name: req.params.name
|
||||||
});
|
});
|
||||||
return { externalKms };
|
return { externalKms };
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().min(1).max(50).describe(GROUPS.CREATE.name),
|
name: z.string().trim().min(1).max(50).describe(GROUPS.CREATE.name),
|
||||||
@ -43,12 +43,59 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:currentSlug",
|
url: "/:id",
|
||||||
method: "PATCH",
|
method: "GET",
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
currentSlug: z.string().trim().describe(GROUPS.UPDATE.currentSlug)
|
id: z.string().trim().describe(GROUPS.GET_BY_ID.id)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: GroupsSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await server.services.group.getGroupById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "GET",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: GroupsSchema.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const groups = await server.services.org.getOrgGroups({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:id",
|
||||||
|
method: "PATCH",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string().trim().describe(GROUPS.UPDATE.id)
|
||||||
}),
|
}),
|
||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
@ -70,7 +117,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const group = await server.services.group.updateGroup({
|
const group = await server.services.group.updateGroup({
|
||||||
currentSlug: req.params.currentSlug,
|
id: req.params.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
@ -83,12 +130,12 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:slug",
|
url: "/:id",
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slug: z.string().trim().describe(GROUPS.DELETE.slug)
|
id: z.string().trim().describe(GROUPS.DELETE.id)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: GroupsSchema
|
200: GroupsSchema
|
||||||
@ -96,7 +143,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const group = await server.services.group.deleteGroup({
|
const group = await server.services.group.deleteGroup({
|
||||||
groupSlug: req.params.slug,
|
id: req.params.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
@ -109,16 +156,17 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:slug/users",
|
url: "/:id/users",
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slug: z.string().trim().describe(GROUPS.LIST_USERS.slug)
|
id: z.string().trim().describe(GROUPS.LIST_USERS.id)
|
||||||
}),
|
}),
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset),
|
offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset),
|
||||||
limit: z.coerce.number().min(1).max(100).default(10).describe(GROUPS.LIST_USERS.limit),
|
limit: z.coerce.number().min(1).max(100).default(10).describe(GROUPS.LIST_USERS.limit),
|
||||||
username: z.string().optional().describe(GROUPS.LIST_USERS.username)
|
username: z.string().trim().optional().describe(GROUPS.LIST_USERS.username),
|
||||||
|
search: z.string().trim().optional().describe(GROUPS.LIST_USERS.search)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -141,24 +189,25 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { users, totalCount } = await server.services.group.listGroupUsers({
|
const { users, totalCount } = await server.services.group.listGroupUsers({
|
||||||
groupSlug: req.params.slug,
|
id: req.params.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.query
|
...req.query
|
||||||
});
|
});
|
||||||
|
|
||||||
return { users, totalCount };
|
return { users, totalCount };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/:slug/users/:username",
|
url: "/:id/users/:username",
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slug: z.string().trim().describe(GROUPS.ADD_USER.slug),
|
id: z.string().trim().describe(GROUPS.ADD_USER.id),
|
||||||
username: z.string().trim().describe(GROUPS.ADD_USER.username)
|
username: z.string().trim().describe(GROUPS.ADD_USER.username)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@ -173,7 +222,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const user = await server.services.group.addUserToGroup({
|
const user = await server.services.group.addUserToGroup({
|
||||||
groupSlug: req.params.slug,
|
id: req.params.id,
|
||||||
username: req.params.username,
|
username: req.params.username,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@ -187,11 +236,11 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/:slug/users/:username",
|
url: "/:id/users/:username",
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slug: z.string().trim().describe(GROUPS.DELETE_USER.slug),
|
id: z.string().trim().describe(GROUPS.DELETE_USER.id),
|
||||||
username: z.string().trim().describe(GROUPS.DELETE_USER.username)
|
username: z.string().trim().describe(GROUPS.DELETE_USER.username)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@ -206,7 +255,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const user = await server.services.group.removeUserFromGroup({
|
const user = await server.services.group.removeUserFromGroup({
|
||||||
groupSlug: req.params.slug,
|
id: req.params.id,
|
||||||
username: req.params.username,
|
username: req.params.username,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { packRules } from "@casl/ability/extra";
|
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
|
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
|
||||||
|
import { backfillPermissionV1SchemaToV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { UnauthorizedError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@ -61,7 +61,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { permissions, privilegePermission } = req.body;
|
const { permissions, privilegePermission } = req.body;
|
||||||
if (!permissions && !privilegePermission) {
|
if (!permissions && !privilegePermission) {
|
||||||
throw new BadRequestError({ message: "Permission or privilegePermission must be provided" });
|
throw new UnauthorizedError({ message: "Permission or privilegePermission must be provided" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const permission = privilegePermission
|
const permission = privilegePermission
|
||||||
@ -79,7 +79,9 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
...req.body,
|
...req.body,
|
||||||
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||||
isTemporary: false,
|
isTemporary: false,
|
||||||
permissions: JSON.stringify(packRules(permission))
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: backfillPermissionV1SchemaToV2Schema(permission)
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@ -140,7 +142,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { permissions, privilegePermission } = req.body;
|
const { permissions, privilegePermission } = req.body;
|
||||||
if (!permissions && !privilegePermission) {
|
if (!permissions && !privilegePermission) {
|
||||||
throw new BadRequestError({ message: "Permission or privilegePermission must be provided" });
|
throw new UnauthorizedError({ message: "Permission or privilegePermission must be provided" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const permission = privilegePermission
|
const permission = privilegePermission
|
||||||
@ -159,7 +161,9 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
...req.body,
|
...req.body,
|
||||||
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||||
isTemporary: true,
|
isTemporary: true,
|
||||||
permissions: JSON.stringify(packRules(permission))
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: backfillPermissionV1SchemaToV2Schema(permission)
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@ -224,7 +228,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { permissions, privilegePermission, ...updatedInfo } = req.body.privilegeDetails;
|
const { permissions, privilegePermission, ...updatedInfo } = req.body.privilegeDetails;
|
||||||
if (!permissions && !privilegePermission) {
|
if (!permissions && !privilegePermission) {
|
||||||
throw new BadRequestError({ message: "Permission or privilegePermission must be provided" });
|
throw new UnauthorizedError({ message: "Permission or privilegePermission must be provided" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const permission = privilegePermission
|
const permission = privilegePermission
|
||||||
@ -244,7 +248,13 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
projectSlug: req.body.projectSlug,
|
projectSlug: req.body.projectSlug,
|
||||||
data: {
|
data: {
|
||||||
...updatedInfo,
|
...updatedInfo,
|
||||||
permissions: permission ? JSON.stringify(packRules(permission)) : undefined
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: permission
|
||||||
|
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
backfillPermissionV1SchemaToV2Schema(permission)
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
|
@ -61,7 +61,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
await server.register(
|
await server.register(
|
||||||
async (pkiRouter) => {
|
async (pkiRouter) => {
|
||||||
await pkiRouter.register(registerCaCrlRouter, { prefix: "/ca" });
|
await pkiRouter.register(registerCaCrlRouter, { prefix: "/crl" });
|
||||||
},
|
},
|
||||||
{ prefix: "/pki" }
|
{ prefix: "/pki" }
|
||||||
);
|
);
|
||||||
@ -81,9 +81,9 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
|
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
|
||||||
await server.register(registerGroupRouter, { prefix: "/groups" });
|
await server.register(registerGroupRouter, { prefix: "/groups" });
|
||||||
await server.register(registerAuditLogStreamRouter, { prefix: "/audit-log-streams" });
|
await server.register(registerAuditLogStreamRouter, { prefix: "/audit-log-streams" });
|
||||||
|
await server.register(registerUserAdditionalPrivilegeRouter, { prefix: "/user-project-additional-privilege" });
|
||||||
await server.register(
|
await server.register(
|
||||||
async (privilegeRouter) => {
|
async (privilegeRouter) => {
|
||||||
await privilegeRouter.register(registerUserAdditionalPrivilegeRouter, { prefix: "/users" });
|
|
||||||
await privilegeRouter.register(registerIdentityProjectAdditionalPrivilegeRouter, { prefix: "/identity" });
|
await privilegeRouter.register(registerIdentityProjectAdditionalPrivilegeRouter, { prefix: "/identity" });
|
||||||
},
|
},
|
||||||
{ prefix: "/additional-privilege" }
|
{ prefix: "/additional-privilege" }
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
// TODO(akhilmhdh): Fix this when licence service gets it type
|
// TODO(akhilmhdh): Fix this when license service gets it type
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
@ -3,11 +3,16 @@ import slugify from "@sindresorhus/slugify";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
backfillPermissionV1SchemaToV2Schema,
|
||||||
|
ProjectPermissionV1Schema
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { PROJECT_ROLE } from "@app/lib/api-docs";
|
import { PROJECT_ROLE } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { ProjectPermissionSchema, SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas";
|
import { SanitizedRoleSchemaV1 } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { ProjectRoleServiceIdentifierType } from "@app/services/project-role/project-role-types";
|
||||||
|
|
||||||
export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@ -42,11 +47,11 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
.describe(PROJECT_ROLE.CREATE.slug),
|
.describe(PROJECT_ROLE.CREATE.slug),
|
||||||
name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name),
|
name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name),
|
||||||
description: z.string().trim().optional().describe(PROJECT_ROLE.CREATE.description),
|
description: z.string().trim().optional().describe(PROJECT_ROLE.CREATE.description),
|
||||||
permissions: ProjectPermissionSchema.array().describe(PROJECT_ROLE.CREATE.permissions)
|
permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.CREATE.permissions)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
role: SanitizedRoleSchema
|
role: SanitizedRoleSchemaV1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -57,12 +62,16 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
projectSlug: req.params.projectSlug,
|
filter: {
|
||||||
|
type: ProjectRoleServiceIdentifierType.SLUG,
|
||||||
|
projectSlug: req.params.projectSlug
|
||||||
|
},
|
||||||
data: {
|
data: {
|
||||||
...req.body,
|
...req.body,
|
||||||
permissions: JSON.stringify(packRules(req.body.permissions))
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true)))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { role };
|
return { role };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -101,11 +110,12 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
message: "Slug must be a valid"
|
message: "Slug must be a valid"
|
||||||
}),
|
}),
|
||||||
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
||||||
permissions: ProjectPermissionSchema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
description: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.description),
|
||||||
|
permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
role: SanitizedRoleSchema
|
role: SanitizedRoleSchemaV1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -116,11 +126,12 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
projectSlug: req.params.projectSlug,
|
|
||||||
roleId: req.params.roleId,
|
roleId: req.params.roleId,
|
||||||
data: {
|
data: {
|
||||||
...req.body,
|
...req.body,
|
||||||
permissions: req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined
|
permissions: req.body.permissions
|
||||||
|
? JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true)))
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return { role };
|
return { role };
|
||||||
@ -146,7 +157,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
role: SanitizedRoleSchema
|
role: SanitizedRoleSchemaV1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -157,7 +168,6 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
projectSlug: req.params.projectSlug,
|
|
||||||
roleId: req.params.roleId
|
roleId: req.params.roleId
|
||||||
});
|
});
|
||||||
return { role };
|
return { role };
|
||||||
@ -193,7 +203,10 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
projectSlug: req.params.projectSlug
|
filter: {
|
||||||
|
type: ProjectRoleServiceIdentifierType.SLUG,
|
||||||
|
projectSlug: req.params.projectSlug
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return { roles };
|
return { roles };
|
||||||
}
|
}
|
||||||
@ -212,7 +225,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
role: SanitizedRoleSchema
|
role: SanitizedRoleSchemaV1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -223,9 +236,13 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
projectSlug: req.params.projectSlug,
|
filter: {
|
||||||
|
type: ProjectRoleServiceIdentifierType.SLUG,
|
||||||
|
projectSlug: req.params.projectSlug
|
||||||
|
},
|
||||||
roleSlug: req.params.slug
|
roleSlug: req.params.slug
|
||||||
});
|
});
|
||||||
|
|
||||||
return { role };
|
return { role };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -87,6 +87,12 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Daniel: This endpoint is no longer is use.
|
||||||
|
* We are keeping it for now because it has been exposed in our public api docs for a while, so by removing it we are likely to break users workflows.
|
||||||
|
*
|
||||||
|
* Please refer to the new endpoint, GET /api/v1/organization/audit-logs, for the same (and more) functionality.
|
||||||
|
*/
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:workspaceId/audit-logs",
|
url: "/:workspaceId/audit-logs",
|
||||||
@ -101,7 +107,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.workspaceId)
|
workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.projectId)
|
||||||
}),
|
}),
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType),
|
eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType),
|
||||||
@ -122,6 +128,12 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
.merge(
|
.merge(
|
||||||
z.object({
|
z.object({
|
||||||
|
project: z
|
||||||
|
.object({
|
||||||
|
name: z.string(),
|
||||||
|
slug: z.string()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
event: z.object({
|
event: z.object({
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
metadata: z.any()
|
metadata: z.any()
|
||||||
@ -138,16 +150,20 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const auditLogs = await server.services.auditLog.listProjectAuditLogs({
|
const auditLogs = await server.services.auditLog.listAuditLogs({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId: req.params.workspaceId,
|
actor: req.permission.type,
|
||||||
...req.query,
|
|
||||||
endDate: req.query.endDate,
|
filter: {
|
||||||
startDate: req.query.startDate || getLastMidnightDateISO(),
|
...req.query,
|
||||||
auditLogActor: req.query.actor,
|
projectId: req.params.workspaceId,
|
||||||
actor: req.permission.type
|
endDate: req.query.endDate,
|
||||||
|
startDate: req.query.startDate || getLastMidnightDateISO(),
|
||||||
|
auditLogActorId: req.query.actor,
|
||||||
|
eventType: req.query.eventType ? [req.query.eventType] : undefined
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return { auditLogs };
|
return { auditLogs };
|
||||||
}
|
}
|
||||||
@ -187,7 +203,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
secretManagerKmsKey: z.object({
|
secretManagerKmsKey: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
slug: z.string(),
|
name: z.string(),
|
||||||
isExternal: z.boolean()
|
isExternal: z.boolean()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -227,7 +243,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
secretManagerKmsKey: z.object({
|
secretManagerKmsKey: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
slug: z.string(),
|
name: z.string(),
|
||||||
isExternal: z.boolean()
|
isExternal: z.boolean()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -252,7 +268,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
secretManagerKmsKey: {
|
secretManagerKmsKey: {
|
||||||
id: secretManagerKmsKey.id,
|
id: secretManagerKmsKey.id,
|
||||||
slug: secretManagerKmsKey.slug
|
name: secretManagerKmsKey.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -320,7 +336,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
secretManagerKmsKey: z.object({
|
secretManagerKmsKey: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
slug: z.string(),
|
name: z.string(),
|
||||||
isExternal: z.boolean()
|
isExternal: z.boolean()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { RateLimitSchema } from "@app/db/schemas";
|
import { RateLimitSchema } from "@app/db/schemas";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
|
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@ -29,7 +29,7 @@ export const registerRateLimitRouter = async (server: FastifyZodProvider) => {
|
|||||||
handler: async () => {
|
handler: async () => {
|
||||||
const rateLimit = await server.services.rateLimit.getRateLimits();
|
const rateLimit = await server.services.rateLimit.getRateLimits();
|
||||||
if (!rateLimit) {
|
if (!rateLimit) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
name: "Get Rate Limit Error",
|
name: "Get Rate Limit Error",
|
||||||
message: "Rate limit configuration does not exist."
|
message: "Rate limit configuration does not exist."
|
||||||
});
|
});
|
||||||
|
@ -61,7 +61,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
id: samlConfigId
|
id: samlConfigId
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
throw new BadRequestError({ message: "Missing sso identitier or org slug" });
|
throw new BadRequestError({ message: "Missing sso identifier or org slug" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const ssoConfig = await server.services.saml.getSaml(ssoLookupDetails);
|
const ssoConfig = await server.services.saml.getSaml(ssoLookupDetails);
|
||||||
@ -100,25 +100,55 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
async (req, profile, cb) => {
|
async (req, profile, cb) => {
|
||||||
try {
|
try {
|
||||||
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
||||||
const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved
|
|
||||||
|
|
||||||
if (!email || !profile.firstName) {
|
const email =
|
||||||
throw new BadRequestError({ message: "Invalid request. Missing email or first name" });
|
profile?.email ??
|
||||||
|
// entra sends data in this format
|
||||||
|
(profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email"] as string) ??
|
||||||
|
(profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved\
|
||||||
|
|
||||||
|
const firstName = (profile.firstName ??
|
||||||
|
// entra sends data in this format
|
||||||
|
profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/firstName"]) as string;
|
||||||
|
|
||||||
|
const lastName =
|
||||||
|
profile.lastName ?? profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/lastName"];
|
||||||
|
|
||||||
|
if (!email || !firstName) {
|
||||||
|
logger.info(
|
||||||
|
{
|
||||||
|
err: new Error("Invalid saml request. Missing email or first name"),
|
||||||
|
profile
|
||||||
|
},
|
||||||
|
`email: ${email} firstName: ${profile.firstName as string}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userMetadata = Object.keys(profile.attributes || {})
|
||||||
|
.map((key) => {
|
||||||
|
// for the ones like in format: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email
|
||||||
|
const formatedKey = key.startsWith("http") ? key.split("/").at(-1) || "" : key;
|
||||||
|
return {
|
||||||
|
key: formatedKey,
|
||||||
|
value: String((profile.attributes as Record<string, string>)[key]).substring(0, 1020)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((el) => el.key && !["email", "firstName", "lastName"].includes(el.key));
|
||||||
|
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
|
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
|
||||||
externalId: profile.nameID,
|
externalId: profile.nameID,
|
||||||
email,
|
email,
|
||||||
firstName: profile.firstName as string,
|
firstName,
|
||||||
lastName: profile.lastName as string,
|
lastName: lastName as string,
|
||||||
relayState: (req.body as { RelayState?: string }).RelayState,
|
relayState: (req.body as { RelayState?: string }).RelayState,
|
||||||
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
|
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
|
||||||
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string
|
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string,
|
||||||
|
metadata: userMetadata
|
||||||
});
|
});
|
||||||
cb(null, { isUserCompleted, providerAuthToken });
|
cb(null, { isUserCompleted, providerAuthToken });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
cb(null, {});
|
cb(error as Error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => {}
|
() => {}
|
||||||
|
@ -5,19 +5,47 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
const ScimUserSchema = z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
id: z.string().trim(),
|
||||||
|
userName: z.string().trim(),
|
||||||
|
name: z
|
||||||
|
.object({
|
||||||
|
familyName: z.string().trim().optional(),
|
||||||
|
givenName: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
emails: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
primary: z.boolean(),
|
||||||
|
value: z.string().email(),
|
||||||
|
type: z.string().trim().default("work")
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
active: z.boolean()
|
||||||
|
});
|
||||||
|
|
||||||
|
const ScimGroupSchema = z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
id: z.string().trim(),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
members: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
value: z.string(),
|
||||||
|
display: z.string().optional()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
meta: z.object({
|
||||||
|
resourceType: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
export const registerScimRouter = async (server: FastifyZodProvider) => {
|
export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||||
server.addContentTypeParser("application/scim+json", { parseAs: "string" }, (_, body, done) => {
|
|
||||||
try {
|
|
||||||
const strBody = body instanceof Buffer ? body.toString() : body;
|
|
||||||
|
|
||||||
const json: unknown = JSON.parse(strBody);
|
|
||||||
done(null, json);
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
done(error, undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/scim-tokens",
|
url: "/scim-tokens",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -124,25 +152,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
Resources: z.array(
|
Resources: z.array(ScimUserSchema),
|
||||||
z.object({
|
|
||||||
id: z.string().trim(),
|
|
||||||
userName: z.string().trim(),
|
|
||||||
name: z.object({
|
|
||||||
familyName: z.string().trim(),
|
|
||||||
givenName: z.string().trim()
|
|
||||||
}),
|
|
||||||
emails: z.array(
|
|
||||||
z.object({
|
|
||||||
primary: z.boolean(),
|
|
||||||
value: z.string(),
|
|
||||||
type: z.string().trim()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
active: z.boolean()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
itemsPerPage: z.number(),
|
itemsPerPage: z.number(),
|
||||||
schemas: z.array(z.string()),
|
schemas: z.array(z.string()),
|
||||||
startIndex: z.number(),
|
startIndex: z.number(),
|
||||||
@ -170,30 +180,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
orgMembershipId: z.string().trim()
|
orgMembershipId: z.string().trim()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
201: z.object({
|
200: ScimUserSchema
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
userName: z.string().trim(),
|
|
||||||
name: z.object({
|
|
||||||
familyName: z.string().trim(),
|
|
||||||
givenName: z.string().trim()
|
|
||||||
}),
|
|
||||||
emails: z.array(
|
|
||||||
z.object({
|
|
||||||
primary: z.boolean(),
|
|
||||||
value: z.string(),
|
|
||||||
type: z.string().trim()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
active: z.boolean(),
|
|
||||||
groups: z.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string().trim(),
|
|
||||||
display: z.string().trim()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
@ -213,41 +200,24 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
schemas: z.array(z.string()),
|
schemas: z.array(z.string()),
|
||||||
userName: z.string().trim(),
|
userName: z.string().trim(),
|
||||||
name: z.object({
|
name: z
|
||||||
familyName: z.string().trim(),
|
.object({
|
||||||
givenName: z.string().trim()
|
familyName: z.string().trim().optional(),
|
||||||
}),
|
givenName: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
emails: z
|
emails: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
primary: z.boolean(),
|
primary: z.boolean(),
|
||||||
value: z.string().email(),
|
value: z.string().email()
|
||||||
type: z.string().trim()
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
// displayName: z.string().trim(),
|
active: z.boolean().default(true)
|
||||||
active: z.boolean()
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: ScimUserSchema
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
userName: z.string().trim(),
|
|
||||||
name: z.object({
|
|
||||||
familyName: z.string().trim(),
|
|
||||||
givenName: z.string().trim()
|
|
||||||
}),
|
|
||||||
emails: z.array(
|
|
||||||
z.object({
|
|
||||||
primary: z.boolean(),
|
|
||||||
value: z.string().email(),
|
|
||||||
type: z.string().trim()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
active: z.boolean()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
@ -257,8 +227,8 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
const user = await req.server.services.scim.createScimUser({
|
const user = await req.server.services.scim.createScimUser({
|
||||||
externalId: req.body.userName,
|
externalId: req.body.userName,
|
||||||
email: primaryEmail,
|
email: primaryEmail,
|
||||||
firstName: req.body.name.givenName,
|
firstName: req.body?.name?.givenName,
|
||||||
lastName: req.body.name.familyName,
|
lastName: req.body?.name?.familyName,
|
||||||
orgId: req.permission.orgId
|
orgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -288,6 +258,115 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Users/:orgMembershipId",
|
||||||
|
method: "PUT",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
orgMembershipId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
id: z.string().trim(),
|
||||||
|
userName: z.string().trim(),
|
||||||
|
name: z
|
||||||
|
.object({
|
||||||
|
familyName: z.string().trim().optional(),
|
||||||
|
givenName: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
emails: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
primary: z.boolean(),
|
||||||
|
value: z.string().email()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
active: z.boolean()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
id: z.string().trim(),
|
||||||
|
userName: z.string().trim(),
|
||||||
|
name: z.object({
|
||||||
|
familyName: z.string().trim(),
|
||||||
|
givenName: z.string().trim()
|
||||||
|
}),
|
||||||
|
emails: z.array(
|
||||||
|
z.object({
|
||||||
|
primary: z.boolean(),
|
||||||
|
value: z.string().email(),
|
||||||
|
type: z.string().trim().default("work")
|
||||||
|
})
|
||||||
|
),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
active: z.boolean()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const primaryEmail = req.body.emails?.find((email) => email.primary)?.value;
|
||||||
|
const user = await req.server.services.scim.replaceScimUser({
|
||||||
|
orgMembershipId: req.params.orgMembershipId,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
firstName: req.body?.name?.givenName,
|
||||||
|
lastName: req.body?.name?.familyName,
|
||||||
|
active: req.body?.active,
|
||||||
|
email: primaryEmail,
|
||||||
|
externalId: req.body.userName
|
||||||
|
});
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Users/:orgMembershipId",
|
||||||
|
method: "PATCH",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
orgMembershipId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
Operations: z.array(
|
||||||
|
z.union([
|
||||||
|
z.object({
|
||||||
|
op: z.union([z.literal("remove"), z.literal("Remove")]),
|
||||||
|
path: z.string().trim(),
|
||||||
|
value: z
|
||||||
|
.object({
|
||||||
|
value: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
op: z.union([z.literal("add"), z.literal("Add"), z.literal("replace"), z.literal("Replace")]),
|
||||||
|
path: z.string().trim().optional(),
|
||||||
|
value: z.any().optional()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: ScimUserSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const user = await req.server.services.scim.updateScimUser({
|
||||||
|
orgMembershipId: req.params.orgMembershipId,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
operations: req.body.Operations
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
});
|
||||||
server.route({
|
server.route({
|
||||||
url: "/Groups",
|
url: "/Groups",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -302,25 +381,10 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
display: z.string()
|
display: z.string()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.optional() // okta-specific
|
.optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: ScimGroupSchema
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
members: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
display: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
meta: z.object({
|
|
||||||
resourceType: z.string().trim()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
@ -341,26 +405,12 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
startIndex: z.coerce.number().default(1),
|
startIndex: z.coerce.number().default(1),
|
||||||
count: z.coerce.number().default(20),
|
count: z.coerce.number().default(20),
|
||||||
filter: z.string().trim().optional()
|
filter: z.string().trim().optional(),
|
||||||
|
excludedAttributes: z.string().trim().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
Resources: z.array(
|
Resources: z.array(ScimGroupSchema),
|
||||||
z.object({
|
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
members: z.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
display: z.string()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
meta: z.object({
|
|
||||||
resourceType: z.string().trim()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
),
|
|
||||||
itemsPerPage: z.number(),
|
itemsPerPage: z.number(),
|
||||||
schemas: z.array(z.string()),
|
schemas: z.array(z.string()),
|
||||||
startIndex: z.number(),
|
startIndex: z.number(),
|
||||||
@ -374,7 +424,8 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
orgId: req.permission.orgId,
|
orgId: req.permission.orgId,
|
||||||
startIndex: req.query.startIndex,
|
startIndex: req.query.startIndex,
|
||||||
filter: req.query.filter,
|
filter: req.query.filter,
|
||||||
limit: req.query.count
|
limit: req.query.count,
|
||||||
|
isMembersExcluded: req.query.excludedAttributes === "members"
|
||||||
});
|
});
|
||||||
|
|
||||||
return groups;
|
return groups;
|
||||||
@ -389,20 +440,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
groupId: z.string().trim()
|
groupId: z.string().trim()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: ScimGroupSchema
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
members: z.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
display: z.string()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
meta: z.object({
|
|
||||||
resourceType: z.string().trim()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
@ -411,6 +449,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
groupId: req.params.groupId,
|
groupId: req.params.groupId,
|
||||||
orgId: req.permission.orgId
|
orgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -434,25 +473,12 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: ScimGroupSchema
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
members: z.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
display: z.string()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
meta: z.object({
|
|
||||||
resourceType: z.string().trim()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const group = await req.server.services.scim.updateScimGroupNamePut({
|
const group = await req.server.services.scim.replaceScimGroup({
|
||||||
groupId: req.params.groupId,
|
groupId: req.params.groupId,
|
||||||
orgId: req.permission.orgId,
|
orgId: req.permission.orgId,
|
||||||
...req.body
|
...req.body
|
||||||
@ -474,54 +500,34 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
Operations: z.array(
|
Operations: z.array(
|
||||||
z.union([
|
z.union([
|
||||||
z.object({
|
z.object({
|
||||||
op: z.literal("replace"),
|
op: z.union([z.literal("remove"), z.literal("Remove")]),
|
||||||
value: z.object({
|
|
||||||
id: z.string().trim(),
|
|
||||||
displayName: z.string().trim()
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
op: z.literal("remove"),
|
|
||||||
path: z.string().trim()
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
op: z.literal("add"),
|
|
||||||
path: z.string().trim(),
|
path: z.string().trim(),
|
||||||
value: z.array(
|
value: z
|
||||||
z.object({
|
.object({
|
||||||
value: z.string().trim(),
|
value: z.string()
|
||||||
display: z.string().trim().optional()
|
|
||||||
})
|
})
|
||||||
)
|
.array()
|
||||||
|
.optional()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
op: z.union([z.literal("add"), z.literal("Add"), z.literal("replace"), z.literal("Replace")]),
|
||||||
|
path: z.string().trim().optional(),
|
||||||
|
value: z.any()
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: ScimGroupSchema
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
members: z.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
display: z.string()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
meta: z.object({
|
|
||||||
resourceType: z.string().trim()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const group = await req.server.services.scim.updateScimGroupNamePatch({
|
const group = await req.server.services.scim.updateScimGroup({
|
||||||
groupId: req.params.groupId,
|
groupId: req.params.groupId,
|
||||||
orgId: req.permission.orgId,
|
orgId: req.permission.orgId,
|
||||||
operations: req.body.Operations
|
operations: req.body.Operations
|
||||||
});
|
});
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -547,60 +553,4 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
|
||||||
url: "/Users/:orgMembershipId",
|
|
||||||
method: "PUT",
|
|
||||||
schema: {
|
|
||||||
params: z.object({
|
|
||||||
orgMembershipId: z.string().trim()
|
|
||||||
}),
|
|
||||||
body: z.object({
|
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
userName: z.string().trim(),
|
|
||||||
name: z.object({
|
|
||||||
familyName: z.string().trim(),
|
|
||||||
givenName: z.string().trim()
|
|
||||||
}),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
active: z.boolean()
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
schemas: z.array(z.string()),
|
|
||||||
id: z.string().trim(),
|
|
||||||
userName: z.string().trim(),
|
|
||||||
name: z.object({
|
|
||||||
familyName: z.string().trim(),
|
|
||||||
givenName: z.string().trim()
|
|
||||||
}),
|
|
||||||
emails: z.array(
|
|
||||||
z.object({
|
|
||||||
primary: z.boolean(),
|
|
||||||
value: z.string().email(),
|
|
||||||
type: z.string().trim()
|
|
||||||
})
|
|
||||||
),
|
|
||||||
displayName: z.string().trim(),
|
|
||||||
active: z.boolean(),
|
|
||||||
groups: z.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string().trim(),
|
|
||||||
display: z.string().trim()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const user = await req.server.services.scim.replaceScimUser({
|
|
||||||
orgMembershipId: req.params.orgMembershipId,
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
active: req.body.active
|
|
||||||
});
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { ApproverType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { EnforcementLevel } from "@app/lib/types";
|
import { EnforcementLevel } from "@app/lib/types";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
@ -16,32 +17,33 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z
|
body: z.object({
|
||||||
.object({
|
workspaceId: z.string(),
|
||||||
workspaceId: z.string(),
|
name: z.string().optional(),
|
||||||
name: z.string().optional(),
|
environment: z.string(),
|
||||||
environment: z.string(),
|
secretPath: z
|
||||||
secretPath: z
|
.string()
|
||||||
.string()
|
.optional()
|
||||||
.optional()
|
.nullable()
|
||||||
.nullable()
|
.default("/")
|
||||||
.default("/")
|
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
||||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
approvers: z
|
||||||
approvers: z.string().array().min(1),
|
.discriminatedUnion("type", [
|
||||||
approvals: z.number().min(1).default(1),
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() })
|
||||||
})
|
])
|
||||||
.refine((data) => data.approvals <= data.approvers.length, {
|
.array()
|
||||||
path: ["approvals"],
|
.min(1, { message: "At least one approver should be provided" }),
|
||||||
message: "The number of approvals should be lower than the number of approvers."
|
approvals: z.number().min(1).default(1),
|
||||||
}),
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
||||||
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
approval: sapPubSchema
|
approval: sapPubSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const approval = await server.services.secretApprovalPolicy.createSecretApprovalPolicy({
|
const approval = await server.services.secretApprovalPolicy.createSecretApprovalPolicy({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@ -67,30 +69,31 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
sapId: z.string()
|
sapId: z.string()
|
||||||
}),
|
}),
|
||||||
body: z
|
body: z.object({
|
||||||
.object({
|
name: z.string().optional(),
|
||||||
name: z.string().optional(),
|
approvers: z
|
||||||
approvers: z.string().array().min(1),
|
.discriminatedUnion("type", [
|
||||||
approvals: z.number().min(1).default(1),
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
secretPath: z
|
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() })
|
||||||
.string()
|
])
|
||||||
.optional()
|
.array()
|
||||||
.nullable()
|
.min(1, { message: "At least one approver should be provided" }),
|
||||||
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
approvals: z.number().min(1).default(1),
|
||||||
.transform((val) => (val === "" ? "/" : val)),
|
secretPath: z
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).optional()
|
.string()
|
||||||
})
|
.optional()
|
||||||
.refine((data) => data.approvals <= data.approvers.length, {
|
.nullable()
|
||||||
path: ["approvals"],
|
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
||||||
message: "The number of approvals should be lower than the number of approvers."
|
.transform((val) => (val === "" ? "/" : val)),
|
||||||
}),
|
enforcementLevel: z.nativeEnum(EnforcementLevel).optional()
|
||||||
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
approval: sapPubSchema
|
approval: sapPubSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({
|
const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@ -120,7 +123,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({
|
const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@ -147,9 +150,10 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
approvals: sapPubSchema
|
approvals: sapPubSchema
|
||||||
.extend({
|
.extend({
|
||||||
userApprovers: z
|
approvers: z
|
||||||
.object({
|
.object({
|
||||||
userId: z.string()
|
id: z.string().nullable().optional(),
|
||||||
|
type: z.nativeEnum(ApproverType)
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
})
|
})
|
||||||
@ -170,6 +174,44 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:sapId",
|
||||||
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
sapId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
approval: sapPubSchema.extend({
|
||||||
|
approvers: z
|
||||||
|
.object({
|
||||||
|
id: z.string().nullable().optional(),
|
||||||
|
type: z.nativeEnum(ApproverType),
|
||||||
|
name: z.string().nullable().optional()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const approval = await server.services.secretApprovalPolicy.getSecretApprovalPolicyById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.params
|
||||||
|
});
|
||||||
|
|
||||||
|
return { approval };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/board",
|
url: "/board",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@ -186,7 +228,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
policy: sapPubSchema
|
policy: sapPubSchema
|
||||||
.extend({
|
.extend({
|
||||||
userApprovers: z.object({ userId: z.string() }).array()
|
userApprovers: z.object({ userId: z.string().nullable().optional() }).array()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
|
@ -13,7 +13,7 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
|||||||
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
const approvalRequestUser = z.object({ userId: z.string() }).merge(
|
const approvalRequestUser = z.object({ userId: z.string().nullable().optional() }).merge(
|
||||||
UsersSchema.pick({
|
UsersSchema.pick({
|
||||||
email: true,
|
email: true,
|
||||||
firstName: true,
|
firstName: true,
|
||||||
@ -46,7 +46,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
approvals: z.number(),
|
approvals: z.number(),
|
||||||
approvers: z.string().array(),
|
approvers: z
|
||||||
|
.object({
|
||||||
|
userId: z.string().nullable().optional()
|
||||||
|
})
|
||||||
|
.array(),
|
||||||
secretPath: z.string().optional().nullable(),
|
secretPath: z.string().optional().nullable(),
|
||||||
enforcementLevel: z.string()
|
enforcementLevel: z.string()
|
||||||
}),
|
}),
|
||||||
@ -54,7 +58,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
|
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
reviewers: z.object({ userId: z.string(), status: z.string() }).array(),
|
reviewers: z.object({ userId: z.string(), status: z.string() }).array(),
|
||||||
approvers: z.string().array()
|
approvers: z
|
||||||
|
.object({
|
||||||
|
userId: z.string().nullable().optional()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
}).array()
|
}).array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
||||||
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
|
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@ -23,6 +25,13 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
if (!appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(req.auth.orgId)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Secret scanning is temporarily unavailable."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const session = await server.services.secretScanning.createInstallationSession({
|
const session = await server.services.secretScanning.createInstallationSession({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@ -30,6 +39,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.body.organizationId
|
orgId: req.body.organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user