mirror of
https://github.com/Infisical/infisical.git
synced 2025-04-09 22:20:16 +00:00
Compare commits
517 Commits
ldap
...
rate-limit
Author | SHA1 | Date | |
---|---|---|---|
d3cdaa8449 | |||
1017707642 | |||
5639306303 | |||
72f50ec399 | |||
effc7a3627 | |||
510c91cef1 | |||
9be5d89fcf | |||
94f4497903 | |||
b5af5646ee | |||
1554618167 | |||
5fbfcdda30 | |||
cdbb3b9c47 | |||
0042a95b21 | |||
53233e05d4 | |||
4f15f9c8d3 | |||
97223fabe6 | |||
04b312cbe4 | |||
97e5069cf5 | |||
93146fcd96 | |||
87d98de4c1 | |||
26f647b948 | |||
80b3cdd128 | |||
8dd85a0d65 | |||
17995d301a | |||
094b48a2b1 | |||
7b8bfe38f0 | |||
9903f7c4a0 | |||
42cd98d4d9 | |||
4b203e9ad3 | |||
36bf1b2abc | |||
42fb732955 | |||
da2dcb347a | |||
b9482966cf | |||
1e4b4591ed | |||
4a325d6d96 | |||
5e20573110 | |||
f623c8159d | |||
4323407da7 | |||
4c496d5e3d | |||
d68dc4c3e0 | |||
e64c579dfd | |||
d0c0d5835c | |||
af2dcdd0c7 | |||
6c628a7265 | |||
00f2d40803 | |||
0a66cbe729 | |||
7fec7c9bf5 | |||
d1afec4f9a | |||
31ad6b0c86 | |||
e46256f45b | |||
64e868a151 | |||
c8cbcaf10c | |||
51716336c2 | |||
6b51c7269a | |||
f551a4158d | |||
e850b82fb3 | |||
8f85f292db | |||
5f84de039f | |||
8529fac098 | |||
81cf19cb4a | |||
edbe1c8eae | |||
a5039494cd | |||
a908471e66 | |||
84204c3c37 | |||
4931e8579c | |||
20dc243fd9 | |||
785a1389d9 | |||
5a3fc3568a | |||
497601e398 | |||
8db019d2fe | |||
07d1d91110 | |||
bb506fff9f | |||
7a561bcbdf | |||
8784f80fc1 | |||
0793e70c26 | |||
99f8799ff4 | |||
3f05c8b7ae | |||
6bd624a0f6 | |||
4a11096ea8 | |||
1589eb8e03 | |||
b370d6e415 | |||
65937d6a17 | |||
d20bc1b38a | |||
882ad8729c | |||
0fdf5032f9 | |||
75d9463ceb | |||
e258b84796 | |||
1ab6b21b25 | |||
775037539e | |||
4f05e4ce93 | |||
2e8680c5d4 | |||
e5136c9ef5 | |||
812fe5cf31 | |||
50082e192c | |||
1e1b5d655e | |||
3befd90723 | |||
88549f4030 | |||
46a638cc63 | |||
566f7e4c61 | |||
9ff3210ed6 | |||
f91a6683c2 | |||
c29cb667d7 | |||
7c623562e1 | |||
aef8d79101 | |||
d735ec71b8 | |||
84651d473b | |||
9501386882 | |||
d11f958443 | |||
087a4bb7d2 | |||
750210e6c3 | |||
90cf4e9137 | |||
17bb2e8a7d | |||
b912cd585c | |||
282434de8e | |||
1f939a5e58 | |||
ac0f5369de | |||
6eba64c975 | |||
12515c1866 | |||
c882da2e1a | |||
8a7774f9ac | |||
a7d2ec80c6 | |||
494543ec53 | |||
b7b875b6a7 | |||
3ddd06a3d1 | |||
a1a8364cd1 | |||
3e51fcb546 | |||
c52a16cc47 | |||
f91c77baa3 | |||
e7c2f6f88c | |||
f7c2d38aef | |||
cfb497dd58 | |||
f7122c21fd | |||
b23deca8e4 | |||
b606990dfb | |||
2240277243 | |||
c8c5caba62 | |||
f408a6f60c | |||
391ed0ed74 | |||
aef40212d2 | |||
5aa7cd46c1 | |||
6c0b916ad8 | |||
d7bc80308d | |||
b7c7b242e8 | |||
b592f4cb6d | |||
cd0e1a87cf | |||
b5d7699b8d | |||
69297bc16e | |||
37827367ed | |||
403b1ce993 | |||
c3c0006a25 | |||
2241908d0a | |||
59b822510c | |||
d1408aff35 | |||
c67084f08d | |||
a280e002ed | |||
76c4a8660f | |||
8c54dd611e | |||
98ea2c1828 | |||
5c75f526e7 | |||
113e777b25 | |||
2a93449ffe | |||
1ef1c042da | |||
b64672a921 | |||
227e013502 | |||
88f7e4255e | |||
44ca8c315e | |||
7766a7f4dd | |||
3cb150a749 | |||
9e9ce261c8 | |||
fab7167850 | |||
c7de9aab4e | |||
3560346f85 | |||
f0bf2f8dd0 | |||
2a6216b8fc | |||
a07d055347 | |||
c05230f667 | |||
d68055a264 | |||
e3e62430ba | |||
dc6056b564 | |||
94f0811661 | |||
7b84ae6173 | |||
5710a304f8 | |||
91e3bbba34 | |||
02112ede07 | |||
08cfbf64e4 | |||
18da522b45 | |||
8cf68fbd9c | |||
d6b82dfaa4 | |||
7bd4eed328 | |||
0341c32da0 | |||
caea055281 | |||
c08c78de8d | |||
3765a14246 | |||
c5a11e839b | |||
93bd3d8270 | |||
b9601dd418 | |||
ae3bc04b07 | |||
11edefa66f | |||
f71459ede0 | |||
33324a5a3c | |||
5c6781a705 | |||
71e31518d7 | |||
f6f6db2898 | |||
55780b65d3 | |||
83bbf9599d | |||
f8f2b2574d | |||
318d12addd | |||
872a28d02a | |||
6f53a5631c | |||
ff2098408d | |||
9e85d9bbf0 | |||
0f3a48bb32 | |||
f869def8ea | |||
378bc57a88 | |||
242179598b | |||
70fe80414d | |||
e201e80a06 | |||
177cd385cc | |||
ab48c3b4fe | |||
69f36d1df6 | |||
11c7b5c674 | |||
ee29577e6d | |||
e3e049b66c | |||
878e4a79e7 | |||
609ce8e5cc | |||
04c1ea9b11 | |||
3baca73e53 | |||
36adf6863b | |||
6363e7d30a | |||
f9621fad8e | |||
90be28b87a | |||
671adee4d7 | |||
c9cb90c98e | |||
9f691df395 | |||
d702a61586 | |||
1c16f406a7 | |||
90f739caa6 | |||
ede8b6f286 | |||
232c547d75 | |||
fe08bbb691 | |||
2bd06ecde4 | |||
08b79d65ea | |||
4e1733ba6c | |||
a4e495ea1c | |||
a750d68363 | |||
d7161a353d | |||
12c414817f | |||
e5e494d0ee | |||
5a21b85e9e | |||
348fdf6429 | |||
88e609cb66 | |||
78058d691a | |||
1d465a50c3 | |||
ffc7249c7c | |||
90bcf23097 | |||
5fa4d9029d | |||
7160cf58ee | |||
6b2d757e39 | |||
c075fcceca | |||
e25f5dd65f | |||
3eef023c30 | |||
e63deb0860 | |||
02b2851990 | |||
cb828200e1 | |||
77d068ae2c | |||
8702af671d | |||
31c0fd96ea | |||
2c539697df | |||
ae97b74933 | |||
3e6af2dae5 | |||
3c91e1127f | |||
0e31a9146a | |||
d2a93eb1d2 | |||
fa1b28b33f | |||
415cf31b2d | |||
9002e6cb33 | |||
1ede551c3e | |||
b7b43858f6 | |||
c91789e6d0 | |||
db0ba4be10 | |||
f73c807aa0 | |||
d1dacd81aa | |||
e8b635ce37 | |||
1d3e03e308 | |||
88e2eff7eb | |||
cd192ee228 | |||
1e657968f6 | |||
b8ba51512c | |||
1ac8ddbd92 | |||
a257743fa5 | |||
b5a7240375 | |||
5c2a108c52 | |||
b78d8d28ed | |||
9c9ade52db | |||
4d229ec745 | |||
605dad29ca | |||
bebdad8159 | |||
b547309ae4 | |||
d1ebdbcc03 | |||
c94caa6fb5 | |||
f53fa46c51 | |||
c42d407cda | |||
80b4bc18ec | |||
1dbf80d4e6 | |||
700a072ec5 | |||
8f42914df5 | |||
831da10073 | |||
6904cd3bda | |||
52fd09b87b | |||
0081bbdf9e | |||
c9e5f2bb75 | |||
73cc97cf17 | |||
0c1d37cc75 | |||
60fbd8ac44 | |||
36efa6ba63 | |||
961a73f712 | |||
6e2f3800d4 | |||
258c9e45d4 | |||
8573263379 | |||
9a724db6ab | |||
60a37e784b | |||
14c60bd075 | |||
de715c03ad | |||
ddb1d5a1ab | |||
41323f205d | |||
771498b817 | |||
22b2fb4c98 | |||
9bbba92768 | |||
46eea972f7 | |||
9eb2a74bdf | |||
b80579fdef | |||
214894c88b | |||
8ff37e3ec9 | |||
926f719967 | |||
c3a56f469a | |||
2bd9914373 | |||
354bac486a | |||
ba22a7fca6 | |||
4aef8ab8ee | |||
e89503f00f | |||
f5f20fbdca | |||
4d4887059a | |||
c11c5ec85e | |||
f0e3c9a4b2 | |||
eace4f1bdc | |||
0bd3f32c6e | |||
ad0504e957 | |||
1e20d780ec | |||
7e2685d604 | |||
92fd2d080d | |||
6d60413593 | |||
f59a75d790 | |||
835c36d161 | |||
e4dba6d5c8 | |||
b9986be387 | |||
5f5d62a285 | |||
667fa7a9e3 | |||
27dcb06083 | |||
9b1a15331a | |||
65776b7ab9 | |||
a9c1f278a1 | |||
900facdb36 | |||
fe638ce2c1 | |||
750a43c978 | |||
08b5975f26 | |||
885d1fbd7f | |||
bb2413d659 | |||
dac5529b6c | |||
bd92e35729 | |||
5b7562a76d | |||
edbf459d04 | |||
560274bde8 | |||
7df614a018 | |||
47287be5bf | |||
6e96f2338c | |||
7fd6b63b5d | |||
995777d76f | |||
2a6032a8cf | |||
ec4d1dd1b2 | |||
143de12d67 | |||
52cf937826 | |||
dbd7561037 | |||
d287c3e152 | |||
8fc081973d | |||
c42bbbea8b | |||
29b2b12ec7 | |||
4f80234afa | |||
a1fa0c652d | |||
8327f41b8e | |||
c2bfeb89e8 | |||
4a0668e92e | |||
716e705c2a | |||
f860fd3abe | |||
30e7fe8a45 | |||
307b89e799 | |||
dbf498b44a | |||
5eb3258311 | |||
bd3cbb3c7b | |||
96abbd9f80 | |||
92441e018f | |||
a9bba02f44 | |||
aaca3ac229 | |||
f0383dd55c | |||
a766329de5 | |||
c0b0c0754b | |||
34618041ca | |||
f36a056c62 | |||
e7b11eac2b | |||
0f14fab915 | |||
12a6fba645 | |||
ce057f44ac | |||
2032063c24 | |||
bbceb37d06 | |||
e917b744f4 | |||
7438c114dd | |||
8e3fc044ca | |||
9473de2212 | |||
744c510a51 | |||
f845749a4d | |||
203e00216f | |||
56fc5a2a8c | |||
7edebbabaa | |||
0e698e9355 | |||
ee215bccfa | |||
00b99e7255 | |||
2b7784718d | |||
5f99e58674 | |||
f77942c702 | |||
2d3fddd0e9 | |||
519b92d592 | |||
c3d5e882f8 | |||
4c354eb3ea | |||
97eff2b480 | |||
c621592807 | |||
bd400a6196 | |||
a93c2d9236 | |||
11dfeda501 | |||
70bd64d54b | |||
0c88a5466c | |||
36266b30d5 | |||
288577b455 | |||
5194be14fd | |||
bab8f95fde | |||
b4f372f883 | |||
b13365ecf5 | |||
bb6e09a895 | |||
715b193a8e | |||
57be493da8 | |||
cc731fe031 | |||
7a3a6663f1 | |||
70618420d7 | |||
7feb7ef9c6 | |||
ab1b9fb164 | |||
8c028889a6 | |||
7dc366baf0 | |||
2124d2669f | |||
af83fbea14 | |||
9657b64ab2 | |||
90b55a94e1 | |||
d83d249f29 | |||
151787c60a | |||
ce443b114c | |||
2ca03abec2 | |||
c8bb690736 | |||
6efbdaef9c | |||
7e90493cce | |||
1330c0455a | |||
407248c616 | |||
a6d7d32156 | |||
0f0e2b360c | |||
47906c4dd4 | |||
fc57884035 | |||
4152b3a524 | |||
f1f18e81cd | |||
929f91a738 | |||
fa41b8bb47 | |||
edbb7e2b1e | |||
1d53e0f21b | |||
a232450f20 | |||
6f65f2a63d | |||
9545960e6f | |||
cfa42017b1 | |||
1b74fdb232 | |||
ad1cae6aac | |||
e5d4328e2a | |||
635948c4f4 | |||
d6231d4649 | |||
041535bb47 | |||
3f0c4f0ca9 | |||
624fb3d46a | |||
8a27b1b5e6 | |||
972b80e790 | |||
6cc0d79d8a | |||
f9b6f78e8d | |||
2852a495c8 | |||
8c491668dc | |||
6305300b12 | |||
b4ae1e8f3b | |||
c873e2cba8 | |||
1bc045a7fa | |||
533de93199 | |||
115b4664bf | |||
db9f21be87 | |||
449617d271 | |||
6b92a5f4db | |||
81e961e8bc | |||
6a7a6ce942 | |||
1695412278 | |||
b4fa07334d | |||
29c244c635 | |||
b80a5989a8 | |||
dc696f8932 | |||
c8f0796952 | |||
9282dd08d9 | |||
df459d456a | |||
c8cfb43316 | |||
541fa10964 |
.env.exampletsup.config.jsorganization-members.png
.github/workflows
Dockerfile.standalone-infisicalREADME.mdbackend
e2e-test
package-lock.jsonpackage.jsonscripts
src
@types
db
instance.tsknexfile.ts
migrations
20240312162549_temp-roles.ts20240312162556_temp-role-identity.ts20240318164718_dynamic-secret.ts20240326172010_project-user-additional-privilege.ts20240326172011_machine-identity-additional-privilege.ts
schemas
dynamic-secret-leases.tsdynamic-secrets.tsidentity-project-additional-privilege.tsidentity-project-membership-role.tsindex.tsmodels.tsproject-user-additional-privilege.tsproject-user-membership-roles.ts
seeds
ee
routes/v1
dynamic-secret-lease-router.tsdynamic-secret-router.tsidentity-project-additional-privilege-router.tsindex.tsldap-router.tslicense-router.tsorg-role-router.tsproject-role-router.tsproject-router.tssaml-router.tsscim-router.tssecret-approval-policy-router.tssecret-approval-request-router.tssecret-rotation-provider-router.tssecret-rotation-router.tssecret-scanning-router.tssecret-version-router.tssnapshot-router.tstrusted-ip-router.tsuser-additional-privilege-router.ts
services
audit-log
dynamic-secret-lease
dynamic-secret-lease-dal.tsdynamic-secret-lease-queue.tsdynamic-secret-lease-service.tsdynamic-secret-lease-types.ts
dynamic-secret
identity-project-additional-privilege
identity-project-additional-privilege-dal.tsidentity-project-additional-privilege-service.tsidentity-project-additional-privilege-types.ts
ldap-config
license
permission
project-user-additional-privilege
project-user-additional-privilege-dal.tsproject-user-additional-privilege-service.tsproject-user-additional-privilege-types.ts
saml-config
scim
secret-approval-policy
secret-approval-request
secret-rotation
secret-scanning
secret-snapshot
trusted-ip
lib
api-docs
config
crypto
errors
knex
types
queue
server
app.ts
config
plugins
routes
index.tssanitizedSchemas.ts
v1
admin-router.tsauth-router.tsbot-router.tsidentity-access-token-router.tsidentity-router.tsidentity-ua.tsintegration-auth-router.tsintegration-router.tsinvite-org-router.tsorganization-router.tspassword-router.tsproject-env-router.tsproject-key-router.tsproject-membership-router.tsproject-router.tssecret-folder-router.tssecret-import-router.tssecret-tag-router.tsuser-action-router.tsuser-router.tswebhook-router.ts
v2
identity-org-router.tsidentity-project-router.tsmfa-router.tsorganization-router.tsproject-membership-router.tsproject-router.tsservice-token-router.tsuser-router.ts
v3
services
auth
identity-access-token
identity-project
identity-project-dal.tsidentity-project-membership-role-dal.tsidentity-project-service.tsidentity-project-types.ts
identity-ua
identity
integration-auth
integration-app-list.tsintegration-auth-service.tsintegration-auth-types.tsintegration-sync-secret.ts
integration
org
project-bot
project-env
project-key
project-membership
project-membership-dal.tsproject-membership-service.tsproject-membership-types.tsproject-user-membership-role-dal.ts
project-role
project
secret-blind-index
secret-folder
secret-import
secret-tag
secret
service-token
super-admin
webhook
cli
.gitignore
packages
docs
api-reference
endpoints
folders
identity-specific-privilege
integrations
create-auth.mdxcreate.mdxdelete-auth-by-id.mdxdelete-auth.mdxdelete.mdxfind-auth.mdxlist-auth.mdxupdate.mdx
secret-tags
secrets
overview
cli
documentation
getting-started
guides
platform
access-controls
access-requests.mdxadditional-privileges.mdxoverview.mdxrole-based-access-controls.mdxtemporary-access.mdx
audit-logs.mdxauth-methods
dynamic-secrets
folder.mdxidentities
ip-allowlisting.mdxldap.mdxldap
mfa.mdxorganization.mdxpit-recovery.mdxpr-workflows.mdxproject.mdxrole-based-access-controls.mdxscim
secret-reference.mdxsecret-rotation
secret-versioning.mdxsso
azure.mdxgithub.mdxgitlab.mdxgoogle-saml.mdxgoogle.mdxjumpcloud.mdxkeycloak-saml.mdxokta.mdxoverview.mdx
token.mdxwebhooks.mdximages
agent
auth-methods
integrations
aws
github
platform
access-controls
access-request-policies.pngaccess-requests-completed.pngaccess-requests-pending.pngadd-additional-privileges.pngadditional-privileges.pngconfigure-temporary-access.pngconfirm-additional-privileges.pngcreate-access-request-policy.pngedit-role.pngrbac.pngrequest-access.pngreview-access-request.pngtemporary-access.png
dynamic-secrets
add-dynamic-secret-button.pngdynamic-secret-modal.pngdynamic-secret-setup-modal.pngdynamic-secret.pnglease-data.pnglease-values.pngmodify-sql-statements.pngprovision-lease.png
organization
organization-machine-identities.pngorganization-members-roles.pngorganization-members.pngorganization-settings-auth.png
project
secret-rotation/aws-iam
rotation-config-1.pngrotation-config-2.pngrotation-config-secrets.pngrotation-manager-access-key-third-party.pngrotation-manager-access-keys.pngrotation-manager-attach-policy.pngrotation-manager-create-access-key.pngrotation-manager-create-policy.pngrotation-manager-create-user.pngrotation-manager-policy-review.pngrotation-manager-user-review.pngrotation-manager-username.pngrotations-aws-iam-user.pngrotations-select-aws-iam-user.png
secret-versioning.pngsecret-rotation
sso/keycloak
client-mappers-by-configuration.pngclient-mappers-completed.pngclient-mappers-email.pngclient-mappers-empty.pngclient-mappers-id.pngclient-mappers-predefined.pngclient-mappers-user-property.pngclient-mappers-username.pngclient-saml-capabilities.pngclient-scopes-list.pngclient-signature-encryption.pngclients-list.pngcreate-client-general-settings.pngcreate-client-login-settings.pngenable-saml.pngidp-values.pnginit-config.pngorg-security-section.pngrealm-saml-metadata.pngrealm-settings-keys.png
integrations
cicd
cloud
frameworks
platforms
internals
mint.jsonsdks
self-hosting
style.cssfrontend
.eslintrc.jsindex.tsuseTimedReset.tsxi18n.tsindex.tsx
.storybook
package-lock.jsonpackage.jsonpublic/images/secretRotation
src
components
analytics
basic
Error.tsxInputField.tsxListbox.tsx
buttons
dialog
AddProjectMemberDialog.tsxAddUpdateEnvironmentDialog.tsxAddUserDialog.tsxAddWorkspaceDialog.tsxDeleteActionModal.tsxDeleteEnvVar.tsxDeleteUserDialog.tsx
popups
table
context/Notifications
dashboard
ConfirmEnvOverwriteModal.tsxDashboardInputField.tsxDeleteActionButton.tsxDownloadSecretsMenu.tsxDropZone.tsx
features
navigation
notifications
permissions
signup
tags/CreateTagModal
utilities
SecurityClient.tsattemptChangePassword.tsattemptCliLogin.tsattemptCliLoginMfa.tsattemptLoginMfa.ts
checks/password
cryptography
intercom
isValidHexColor.tssaveTokenToLocalStorage.tstelemetry
v2
Accordion
Button
Card
Checkbox
DeleteActionModal
Drawer
Dropdown
EmailServiceSetupModal
FormControl
HoverCard
HoverCardv2
Menu
Modal
Pagination
Popover
Popoverv2
RadioGroup
SecretInput
Select
Spinner
Stepper
Table
Tabs
Tag
Tooltip
UpgradeProjectAlert
config
context
ee
helpers
hoc/withPermission
hooks
api
apiKeys
auditLogs
auth
bots
dynamicSecret
dynamicSecretLease
identities
identityProjectAdditionalPrivilege
index.tsxintegrationAuth
integrations
ldapConfig
organization
projectUserAdditionalPrivilege
scim
secretImports
serverDetails
serviceTokens
ssoConfig
subscriptions
trustedIps
types.tsusers
workspace
layouts
lib/fn
pages
404.tsx_app.tsx
reactQuery.tsxadmin
api
cli-redirect.tsxemail-not-verified.tsxindex.tsxintegrations
aws-parameter-store
aws-secret-manager
checkly
cloudflare-pages
cloudflare-workers
flyio
github
heroku
qovery
railway
render
terraform-cloud
login
org
password-reset.tsxpersonal-settings.tsxproject/[id]
requestnewinvite.tsxsecret-scanning.tsxsignup/sso
signupinvite.tsxverify-email.tsxservices
styles
views
IntegrationsPage
Login
Login.tsxLogin.utils.tsxLoginSSO.tsx
components
Org
MembersPage/components
OrgIdentityTab
OrgIdentityTab.tsxindex.tsx
components
IdentitySection
IdentityAuthMethodModal.tsxIdentityModal.tsxIdentitySection.tsxIdentityTable.tsxIdentityUniversalAuthClientSecretModal.tsxIdentityUniversalAuthForm.tsxindex.tsx
index.tsxOrgMembersTab
OrgRoleTabSection
OrgRoleModifySection
OrgRoleTable.tsxNonePage
components
Project
AuditLogsPage
IPAllowListPage
MembersPage
MembersPage.tsx
components
IdentityTab
MemberListTab
MemberListTab.tsx
MemberRoleForm
ProjectRoleListTab/components
ProjectRoleList
ProjectRoleModifySection
ServiceTokenTab
index.tsxSecretApprovalPage/components
SecretApprovalPolicyList
SecretApprovalRequest/components
SecretMainPage
SecretMainPage.store.tsxSecretMainPage.tsx
components
ActionBar
CreateSecretForm
DynamicSecretListView
CreateDynamicSecretLease.tsxDynamicSecretLease.tsxDynamicSecretListView.tsx
EditDynamicSecretForm
RenewDynamicSecretLease.tsxindex.tsxFolderListView
SecretDropzone
SecretImportListView
SecretListView
SnapshotView
SecretOverviewPage
SecretOverviewPage.tsx
components
CreateSecretForm
ProjectIndexSecretsSection
SecretOverviewDynamicSecretRow
SecretOverviewFolderRow
SecretOverviewTableRow
SecretRotationPage
SecretScanning/components
Settings
BillingSettingsPage
BillingSettingsPage.tsxindex.tsx
components
BillingCloudTab
BillingDetailsTab
BillingDetailsTab.tsxCompanyNameSection.tsxInvoiceEmailSection.tsxTaxIDModal.tsxTaxIDSection.tsxindex.tsx
BillingReceiptsTab
BillingSelfHostedTab
BillingTabGroup
index.tsxOrgSettingsPage
OrgSettingsPage.tsx
components
OrgAuthTab
LDAPModal.tsxOrgAuthTab.tsxOrgGeneralAuthSection.tsxOrgLDAPSection.tsxOrgSCIMSection.tsxOrgSSOSection.tsxSSOModal.tsxScimTokenModal.tsxindex.tsx
OrgDeleteSection
OrgGeneralTab
OrgIncidentContactsSection
OrgNameChangeSection
OrgSlugChangeSection
OrgTabGroup
index.tsxPersonalSettingsPage
APIKeySection
APIKeyV2Section
AuthMethodSection
ChangeLanguageSection
ChangePasswordSection
DeleteAccountSection
EmergencyKitSection
PersonalAPIKeyTab
PersonalAuthTab
PersonalGeneralTab
PersonalSettingsPage.tsxPersonalTabGroup
SecuritySection
SessionsSection
UserNameSection
index.tsxProjectSettingsPage
ProjectSettingsPage.tsx
components
AutoCapitalizationSection
DeleteProjectSection
E2EESection
EnvironmentSection
ProjectGeneralTab
ProjectNameChangeSection
SecretTagsSection
WebhooksTab
index.tsxSignup
admin
DashboardPage
SignUpPage
helm-charts
infisical-standalone-postgres
secrets-operator
k8-operator
@ -3,9 +3,6 @@
|
||||
# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NEVER BE USED FOR PRODUCTION
|
||||
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
|
||||
|
||||
# Required
|
||||
DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
||||
|
||||
# JWT
|
||||
# Required secrets to sign JWT tokens
|
||||
# THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION
|
||||
@ -16,6 +13,9 @@ POSTGRES_PASSWORD=infisical
|
||||
POSTGRES_USER=infisical
|
||||
POSTGRES_DB=infisical
|
||||
|
||||
# Required
|
||||
DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://redis:6379
|
||||
|
||||
|
@ -41,6 +41,7 @@ jobs:
|
||||
load: true
|
||||
context: backend
|
||||
tags: infisical/infisical:test
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- name: ⏻ Spawn backend container and dependencies
|
||||
run: |
|
||||
docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
|
||||
@ -92,6 +93,7 @@ jobs:
|
||||
project: 64mmf0n610
|
||||
context: frontend
|
||||
tags: infisical/frontend:test
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||
NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
|
||||
|
140
.github/workflows/build-staging-and-deploy-aws.yml
vendored
Normal file
140
.github/workflows/build-staging-and-deploy-aws.yml
vendored
Normal file
@ -0,0 +1,140 @@
|
||||
name: Deployment pipeline
|
||||
on: [workflow_dispatch]
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
infisical-image:
|
||||
name: Build backend image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
- name: 📦 Install dependencies to test all dependencies
|
||||
run: npm ci --only-production
|
||||
working-directory: backend
|
||||
- name: Save commit hashes for tag
|
||||
id: commit
|
||||
uses: pr-mpt/actions-commit-hash@v2
|
||||
- name: 🔧 Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: 🐋 Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Set up Depot CLI
|
||||
uses: depot/setup-action@v1
|
||||
- name: 🏗️ Build backend and push to docker hub
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
project: 64mmf0n610
|
||||
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
||||
push: true
|
||||
context: .
|
||||
file: Dockerfile.standalone-infisical
|
||||
tags: |
|
||||
infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||
infisical/staging_infisical:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||
INFISICAL_PLATFORM_VERSION=${{ steps.commit.outputs.short }}
|
||||
|
||||
gamma-deployment:
|
||||
name: Deploy to gamma
|
||||
runs-on: ubuntu-latest
|
||||
needs: [infisical-image]
|
||||
environment:
|
||||
name: Gamma
|
||||
steps:
|
||||
- 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: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
audience: sts.amazonaws.com
|
||||
aws-region: us-east-1
|
||||
role-to-assume: arn:aws:iam::905418227878:role/deploy-new-ecs-img
|
||||
- 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-prod-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-prod-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-prod-platform
|
||||
cluster: infisical-prod-platform
|
||||
wait-for-service-stability: true
|
||||
|
||||
production-postgres-deployment:
|
||||
name: Deploy to production
|
||||
runs-on: ubuntu-latest
|
||||
needs: [gamma-deployment]
|
||||
environment:
|
||||
name: Production
|
||||
steps:
|
||||
- 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: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
audience: sts.amazonaws.com
|
||||
aws-region: us-east-1
|
||||
role-to-assume: arn:aws:iam::381492033652:role/gha-make-prod-deployment
|
||||
- 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-prod-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-prod-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-prod-platform
|
||||
cluster: infisical-prod-platform
|
||||
wait-for-service-stability: true
|
120
.github/workflows/build-staging-and-deploy.yml
vendored
120
.github/workflows/build-staging-and-deploy.yml
vendored
@ -1,120 +0,0 @@
|
||||
name: Build, Publish and Deploy to Gamma
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
infisical-image:
|
||||
name: Build backend image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
- name: 📦 Install dependencies to test all dependencies
|
||||
run: npm ci --only-production
|
||||
working-directory: backend
|
||||
# - name: 🧪 Run tests
|
||||
# run: npm run test:ci
|
||||
# working-directory: backend
|
||||
- name: Save commit hashes for tag
|
||||
id: commit
|
||||
uses: pr-mpt/actions-commit-hash@v2
|
||||
- name: 🔧 Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: 🐋 Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Set up Depot CLI
|
||||
uses: depot/setup-action@v1
|
||||
- name: 📦 Build backend and export to Docker
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
project: 64mmf0n610
|
||||
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
||||
load: true
|
||||
context: .
|
||||
file: Dockerfile.standalone-infisical
|
||||
tags: infisical/infisical:test
|
||||
# - name: ⏻ Spawn backend container and dependencies
|
||||
# run: |
|
||||
# docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
|
||||
# - name: 🧪 Test backend image
|
||||
# run: |
|
||||
# ./.github/resources/healthcheck.sh infisical-backend-test
|
||||
# - name: ⏻ Shut down backend container and dependencies
|
||||
# run: |
|
||||
# docker compose -f .github/resources/docker-compose.be-test.yml down
|
||||
- name: 🏗️ Build backend and push
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
project: 64mmf0n610
|
||||
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
||||
push: true
|
||||
context: .
|
||||
file: Dockerfile.standalone-infisical
|
||||
tags: |
|
||||
infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||
infisical/staging_infisical:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||
INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
|
||||
postgres-migration:
|
||||
name: Run latest migration files
|
||||
runs-on: ubuntu-latest
|
||||
needs: [infisical-image]
|
||||
steps:
|
||||
- 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: Run postgres DB migration files
|
||||
# env:
|
||||
# DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
|
||||
# run: npm run migration:latest
|
||||
gamma-deployment:
|
||||
name: Deploy to gamma
|
||||
runs-on: ubuntu-latest
|
||||
needs: [postgres-migration]
|
||||
steps:
|
||||
- name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v3
|
||||
with:
|
||||
version: v3.10.0
|
||||
- name: Install infisical helm chart
|
||||
run: |
|
||||
helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
|
||||
helm repo update
|
||||
- name: Install kubectl
|
||||
uses: azure/setup-kubectl@v3
|
||||
- name: Install doctl
|
||||
uses: digitalocean/action-doctl@v2
|
||||
with:
|
||||
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||
- name: Save DigitalOcean kubeconfig with short-lived credentials
|
||||
run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 infisical-gamma-postgres
|
||||
- name: switch to gamma namespace
|
||||
run: kubectl config set-context --current --namespace=gamma
|
||||
- name: test kubectl
|
||||
run: kubectl get ingress
|
||||
- name: Download helm values to file and upgrade gamma deploy
|
||||
run: |
|
||||
wget https://raw.githubusercontent.com/Infisical/infisical/main/.github/values.yaml
|
||||
helm upgrade infisical infisical-helm-charts/infisical-standalone --values values.yaml --wait --install
|
||||
if [[ $(helm status infisical) == *"FAILED"* ]]; then
|
||||
echo "Helm upgrade failed"
|
||||
exit 1
|
||||
else
|
||||
echo "Helm upgrade was successful"
|
||||
fi
|
@ -118,9 +118,6 @@ WORKDIR /backend
|
||||
|
||||
ENV TELEMETRY_ENABLED true
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
||||
CMD node healthcheck.js
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 443
|
||||
|
||||
|
@ -10,7 +10,8 @@
|
||||
<a href="https://infisical.com/">Infisical Cloud</a> |
|
||||
<a href="https://infisical.com/docs/self-hosting/overview">Self-Hosting</a> |
|
||||
<a href="https://infisical.com/docs/documentation/getting-started/introduction">Docs</a> |
|
||||
<a href="https://www.infisical.com">Website</a>
|
||||
<a href="https://www.infisical.com">Website</a> |
|
||||
<a href="https://infisical.com/careers">Hiring (Remote/SF)</a>
|
||||
</h4>
|
||||
|
||||
<p align="center">
|
||||
|
@ -10,7 +10,7 @@ import { seedData1 } from "@app/db/seed-data";
|
||||
import { initEnvConfig } from "@app/lib/config/env";
|
||||
import { initLogger } from "@app/lib/logger";
|
||||
import { main } from "@app/server/app";
|
||||
import { 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";
|
||||
@ -52,6 +52,8 @@ export default {
|
||||
authTokenType: AuthTokenType.ACCESS_TOKEN,
|
||||
userId: seedData1.id,
|
||||
tokenVersionId: seedData1.token.id,
|
||||
authMethod: AuthMethod.EMAIL,
|
||||
organizationId: seedData1.organization.id,
|
||||
accessVersion: 1
|
||||
},
|
||||
cfg.AUTH_SECRET,
|
||||
|
867
backend/package-lock.json
generated
867
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -70,6 +70,7 @@
|
||||
"vitest": "^1.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-iam": "^3.525.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.504.0",
|
||||
"@casl/ability": "^6.5.0",
|
||||
"@fastify/cookie": "^9.3.1",
|
||||
@ -106,6 +107,7 @@
|
||||
"knex": "^3.0.1",
|
||||
"libsodium-wrappers": "^0.7.13",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"ms": "^2.1.3",
|
||||
"mysql2": "^3.9.1",
|
||||
"nanoid": "^5.0.4",
|
||||
"nodemailer": "^6.9.9",
|
||||
@ -115,6 +117,7 @@
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
"passport-ldapauth": "^3.0.1",
|
||||
"pg": "^8.11.3",
|
||||
"pg-query-stream": "^4.5.3",
|
||||
"picomatch": "^3.0.1",
|
||||
"pino": "^8.16.2",
|
||||
"posthog-node": "^3.6.2",
|
||||
|
@ -103,11 +103,15 @@ export const ${dalName} = (db: TDbClient) => {
|
||||
`import { z } from "zod";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { readLimit } from "@app/server/config/rateLimiter";
|
||||
|
||||
export const register${pascalCase}Router = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/",
|
||||
method: "GET",
|
||||
url: "/",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({}),
|
||||
response: {
|
||||
|
13
backend/src/@types/fastify.d.ts
vendored
13
backend/src/@types/fastify.d.ts
vendored
@ -3,9 +3,13 @@ import "fastify";
|
||||
import { TUsers } from "@app/db/schemas";
|
||||
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
||||
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 { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
||||
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service";
|
||||
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
||||
import { TScimServiceFactory } from "@app/ee/services/scim/scim-service";
|
||||
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||
@ -19,7 +23,7 @@ import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
||||
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
|
||||
import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
|
||||
import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
||||
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||
@ -59,9 +63,10 @@ declare module "fastify" {
|
||||
// identity injection. depending on which kinda of token the information is filled in auth
|
||||
auth: TAuthMode;
|
||||
permission: {
|
||||
authMethod: ActorAuthMethod;
|
||||
type: ActorType;
|
||||
id: string;
|
||||
orgId?: string;
|
||||
orgId: string;
|
||||
};
|
||||
// passport data
|
||||
passportUser: {
|
||||
@ -116,6 +121,10 @@ declare module "fastify" {
|
||||
trustedIp: TTrustedIpServiceFactory;
|
||||
secretBlindIndex: TSecretBlindIndexServiceFactory;
|
||||
telemetry: TTelemetryServiceFactory;
|
||||
dynamicSecret: TDynamicSecretServiceFactory;
|
||||
dynamicSecretLease: TDynamicSecretLeaseServiceFactory;
|
||||
projectUserAdditionalPrivilege: TProjectUserAdditionalPrivilegeServiceFactory;
|
||||
identityProjectAdditionalPrivilege: TIdentityProjectAdditionalPrivilegeServiceFactory;
|
||||
};
|
||||
// this is exclusive use for middlewares in which we need to inject data
|
||||
// everywhere else access using service layer
|
||||
|
44
backend/src/@types/knex.d.ts
vendored
44
backend/src/@types/knex.d.ts
vendored
@ -17,6 +17,12 @@ import {
|
||||
TBackupPrivateKey,
|
||||
TBackupPrivateKeyInsert,
|
||||
TBackupPrivateKeyUpdate,
|
||||
TDynamicSecretLeases,
|
||||
TDynamicSecretLeasesInsert,
|
||||
TDynamicSecretLeasesUpdate,
|
||||
TDynamicSecrets,
|
||||
TDynamicSecretsInsert,
|
||||
TDynamicSecretsUpdate,
|
||||
TGitAppInstallSessions,
|
||||
TGitAppInstallSessionsInsert,
|
||||
TGitAppInstallSessionsUpdate,
|
||||
@ -32,6 +38,12 @@ import {
|
||||
TIdentityOrgMemberships,
|
||||
TIdentityOrgMembershipsInsert,
|
||||
TIdentityOrgMembershipsUpdate,
|
||||
TIdentityProjectAdditionalPrivilege,
|
||||
TIdentityProjectAdditionalPrivilegeInsert,
|
||||
TIdentityProjectAdditionalPrivilegeUpdate,
|
||||
TIdentityProjectMembershipRole,
|
||||
TIdentityProjectMembershipRoleInsert,
|
||||
TIdentityProjectMembershipRoleUpdate,
|
||||
TIdentityProjectMemberships,
|
||||
TIdentityProjectMembershipsInsert,
|
||||
TIdentityProjectMembershipsUpdate,
|
||||
@ -83,6 +95,12 @@ import {
|
||||
TProjects,
|
||||
TProjectsInsert,
|
||||
TProjectsUpdate,
|
||||
TProjectUserAdditionalPrivilege,
|
||||
TProjectUserAdditionalPrivilegeInsert,
|
||||
TProjectUserAdditionalPrivilegeUpdate,
|
||||
TProjectUserMembershipRoles,
|
||||
TProjectUserMembershipRolesInsert,
|
||||
TProjectUserMembershipRolesUpdate,
|
||||
TSamlConfigs,
|
||||
TSamlConfigsInsert,
|
||||
TSamlConfigsUpdate,
|
||||
@ -221,7 +239,17 @@ declare module "knex/types/tables" {
|
||||
TProjectEnvironmentsUpdate
|
||||
>;
|
||||
[TableName.ProjectBot]: Knex.CompositeTableType<TProjectBots, TProjectBotsInsert, TProjectBotsUpdate>;
|
||||
[TableName.ProjectUserMembershipRole]: Knex.CompositeTableType<
|
||||
TProjectUserMembershipRoles,
|
||||
TProjectUserMembershipRolesInsert,
|
||||
TProjectUserMembershipRolesUpdate
|
||||
>;
|
||||
[TableName.ProjectRoles]: Knex.CompositeTableType<TProjectRoles, TProjectRolesInsert, TProjectRolesUpdate>;
|
||||
[TableName.ProjectUserAdditionalPrivilege]: Knex.CompositeTableType<
|
||||
TProjectUserAdditionalPrivilege,
|
||||
TProjectUserAdditionalPrivilegeInsert,
|
||||
TProjectUserAdditionalPrivilegeUpdate
|
||||
>;
|
||||
[TableName.ProjectKeys]: Knex.CompositeTableType<TProjectKeys, TProjectKeysInsert, TProjectKeysUpdate>;
|
||||
[TableName.Secret]: Knex.CompositeTableType<TSecrets, TSecretsInsert, TSecretsUpdate>;
|
||||
[TableName.SecretBlindIndex]: Knex.CompositeTableType<
|
||||
@ -272,6 +300,16 @@ declare module "knex/types/tables" {
|
||||
TIdentityProjectMembershipsInsert,
|
||||
TIdentityProjectMembershipsUpdate
|
||||
>;
|
||||
[TableName.IdentityProjectMembershipRole]: Knex.CompositeTableType<
|
||||
TIdentityProjectMembershipRole,
|
||||
TIdentityProjectMembershipRoleInsert,
|
||||
TIdentityProjectMembershipRoleUpdate
|
||||
>;
|
||||
[TableName.IdentityProjectAdditionalPrivilege]: Knex.CompositeTableType<
|
||||
TIdentityProjectAdditionalPrivilege,
|
||||
TIdentityProjectAdditionalPrivilegeInsert,
|
||||
TIdentityProjectAdditionalPrivilegeUpdate
|
||||
>;
|
||||
[TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>;
|
||||
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
|
||||
TSecretApprovalPolicies,
|
||||
@ -324,6 +362,12 @@ declare module "knex/types/tables" {
|
||||
TSecretSnapshotFoldersInsert,
|
||||
TSecretSnapshotFoldersUpdate
|
||||
>;
|
||||
[TableName.DynamicSecret]: Knex.CompositeTableType<TDynamicSecrets, TDynamicSecretsInsert, TDynamicSecretsUpdate>;
|
||||
[TableName.DynamicSecretLease]: Knex.CompositeTableType<
|
||||
TDynamicSecretLeases,
|
||||
TDynamicSecretLeasesInsert,
|
||||
TDynamicSecretLeasesUpdate
|
||||
>;
|
||||
[TableName.SamlConfig]: Knex.CompositeTableType<TSamlConfigs, TSamlConfigsInsert, TSamlConfigsUpdate>;
|
||||
[TableName.LdapConfig]: Knex.CompositeTableType<TLdapConfigs, TLdapConfigsInsert, TLdapConfigsUpdate>;
|
||||
[TableName.OrgBot]: Knex.CompositeTableType<TOrgBots, TOrgBotsInsert, TOrgBotsUpdate>;
|
||||
|
@ -6,6 +6,13 @@ export const initDbConnection = ({ dbConnectionUri, dbRootCert }: { dbConnection
|
||||
client: "pg",
|
||||
connection: {
|
||||
connectionString: dbConnectionUri,
|
||||
host: process.env.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.DB_PORT,
|
||||
user: process.env.DB_USER,
|
||||
database: process.env.DB_NAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
ssl: dbRootCert
|
||||
? {
|
||||
rejectUnauthorized: true,
|
||||
|
@ -7,18 +7,22 @@ import path from "path";
|
||||
|
||||
// Update with your config settings. .
|
||||
dotenv.config({
|
||||
path: path.join(__dirname, "../../../.env.migration"),
|
||||
debug: true
|
||||
path: path.join(__dirname, "../../../.env.migration")
|
||||
});
|
||||
dotenv.config({
|
||||
path: path.join(__dirname, "../../../.env"),
|
||||
debug: true
|
||||
path: path.join(__dirname, "../../../.env")
|
||||
});
|
||||
|
||||
export default {
|
||||
development: {
|
||||
client: "postgres",
|
||||
connection: {
|
||||
connectionString: process.env.DB_CONNECTION_URI,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT,
|
||||
user: process.env.DB_USER,
|
||||
database: process.env.DB_NAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
ssl: process.env.DB_ROOT_CERT
|
||||
? {
|
||||
rejectUnauthorized: true,
|
||||
@ -41,6 +45,11 @@ export default {
|
||||
client: "postgres",
|
||||
connection: {
|
||||
connectionString: process.env.DB_CONNECTION_URI,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT,
|
||||
user: process.env.DB_USER,
|
||||
database: process.env.DB_NAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
ssl: process.env.DB_ROOT_CERT
|
||||
? {
|
||||
rejectUnauthorized: true,
|
||||
|
50
backend/src/db/migrations/20240312162549_temp-roles.ts
Normal file
50
backend/src/db/migrations/20240312162549_temp-roles.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const doesTableExist = await knex.schema.hasTable(TableName.ProjectUserMembershipRole);
|
||||
if (!doesTableExist) {
|
||||
await knex.schema.createTable(TableName.ProjectUserMembershipRole, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.string("role").notNullable();
|
||||
t.uuid("projectMembershipId").notNullable();
|
||||
t.foreign("projectMembershipId").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
|
||||
// until role is changed/removed the role should not deleted
|
||||
t.uuid("customRoleId");
|
||||
t.foreign("customRoleId").references("id").inTable(TableName.ProjectRoles);
|
||||
t.boolean("isTemporary").notNullable().defaultTo(false);
|
||||
t.string("temporaryMode");
|
||||
t.string("temporaryRange"); // could be cron or relative time like 1H or 1minute etc
|
||||
t.datetime("temporaryAccessStartTime");
|
||||
t.datetime("temporaryAccessEndTime");
|
||||
t.timestamps(true, true, true);
|
||||
});
|
||||
}
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.ProjectUserMembershipRole);
|
||||
|
||||
const projectMemberships = await knex(TableName.ProjectMembership).select(
|
||||
"id",
|
||||
"role",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
knex.ref("roleId").withSchema(TableName.ProjectMembership).as("customRoleId")
|
||||
);
|
||||
if (projectMemberships.length)
|
||||
await knex.batchInsert(
|
||||
TableName.ProjectUserMembershipRole,
|
||||
projectMemberships.map((data) => ({ ...data, projectMembershipId: data.id }))
|
||||
);
|
||||
// will be dropped later
|
||||
// await knex.schema.alterTable(TableName.ProjectMembership, (t) => {
|
||||
// t.dropColumn("roleId");
|
||||
// t.dropColumn("role");
|
||||
// });
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.dropTableIfExists(TableName.ProjectUserMembershipRole);
|
||||
await dropOnUpdateTrigger(knex, TableName.ProjectUserMembershipRole);
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const doesTableExist = await knex.schema.hasTable(TableName.IdentityProjectMembershipRole);
|
||||
if (!doesTableExist) {
|
||||
await knex.schema.createTable(TableName.IdentityProjectMembershipRole, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.string("role").notNullable();
|
||||
t.uuid("projectMembershipId").notNullable();
|
||||
t.foreign("projectMembershipId")
|
||||
.references("id")
|
||||
.inTable(TableName.IdentityProjectMembership)
|
||||
.onDelete("CASCADE");
|
||||
// until role is changed/removed the role should not deleted
|
||||
t.uuid("customRoleId");
|
||||
t.foreign("customRoleId").references("id").inTable(TableName.ProjectRoles);
|
||||
t.boolean("isTemporary").notNullable().defaultTo(false);
|
||||
t.string("temporaryMode");
|
||||
t.string("temporaryRange"); // could be cron or relative time like 1H or 1minute etc
|
||||
t.datetime("temporaryAccessStartTime");
|
||||
t.datetime("temporaryAccessEndTime");
|
||||
t.timestamps(true, true, true);
|
||||
});
|
||||
}
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.IdentityProjectMembershipRole);
|
||||
|
||||
const identityMemberships = await knex(TableName.IdentityProjectMembership).select(
|
||||
"id",
|
||||
"role",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
knex.ref("roleId").withSchema(TableName.IdentityProjectMembership).as("customRoleId")
|
||||
);
|
||||
if (identityMemberships.length)
|
||||
await knex.batchInsert(
|
||||
TableName.IdentityProjectMembershipRole,
|
||||
identityMemberships.map((data) => ({ ...data, projectMembershipId: data.id }))
|
||||
);
|
||||
// await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => {
|
||||
// t.dropColumn("roleId");
|
||||
// t.dropColumn("role");
|
||||
// });
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.dropTableIfExists(TableName.IdentityProjectMembershipRole);
|
||||
await dropOnUpdateTrigger(knex, TableName.IdentityProjectMembershipRole);
|
||||
}
|
58
backend/src/db/migrations/20240318164718_dynamic-secret.ts
Normal file
58
backend/src/db/migrations/20240318164718_dynamic-secret.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
|
||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const doesTableExist = await knex.schema.hasTable(TableName.DynamicSecret);
|
||||
if (!doesTableExist) {
|
||||
await knex.schema.createTable(TableName.DynamicSecret, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.string("name").notNullable();
|
||||
t.integer("version").notNullable();
|
||||
t.string("type").notNullable();
|
||||
t.string("defaultTTL").notNullable();
|
||||
t.string("maxTTL");
|
||||
t.string("inputIV").notNullable();
|
||||
t.text("inputCiphertext").notNullable();
|
||||
t.string("inputTag").notNullable();
|
||||
t.string("algorithm").notNullable().defaultTo(SecretEncryptionAlgo.AES_256_GCM);
|
||||
t.string("keyEncoding").notNullable().defaultTo(SecretKeyEncoding.UTF8);
|
||||
t.uuid("folderId").notNullable();
|
||||
// for background process communication
|
||||
t.string("status");
|
||||
t.string("statusDetails");
|
||||
t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("CASCADE");
|
||||
t.unique(["name", "folderId"]);
|
||||
t.timestamps(true, true, true);
|
||||
});
|
||||
}
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.DynamicSecret);
|
||||
|
||||
const doesTableDynamicSecretLease = await knex.schema.hasTable(TableName.DynamicSecretLease);
|
||||
if (!doesTableDynamicSecretLease) {
|
||||
await knex.schema.createTable(TableName.DynamicSecretLease, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.integer("version").notNullable();
|
||||
t.string("externalEntityId").notNullable();
|
||||
t.datetime("expireAt").notNullable();
|
||||
// for background process communication
|
||||
t.string("status");
|
||||
t.string("statusDetails");
|
||||
t.uuid("dynamicSecretId").notNullable();
|
||||
t.foreign("dynamicSecretId").references("id").inTable(TableName.DynamicSecret).onDelete("CASCADE");
|
||||
t.timestamps(true, true, true);
|
||||
});
|
||||
}
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.DynamicSecretLease);
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await dropOnUpdateTrigger(knex, TableName.DynamicSecretLease);
|
||||
await knex.schema.dropTableIfExists(TableName.DynamicSecretLease);
|
||||
|
||||
await dropOnUpdateTrigger(knex, TableName.DynamicSecret);
|
||||
await knex.schema.dropTableIfExists(TableName.DynamicSecret);
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
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.ProjectUserAdditionalPrivilege))) {
|
||||
await knex.schema.createTable(TableName.ProjectUserAdditionalPrivilege, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.string("slug", 60).notNullable();
|
||||
t.uuid("projectMembershipId").notNullable();
|
||||
t.foreign("projectMembershipId").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
|
||||
t.boolean("isTemporary").notNullable().defaultTo(false);
|
||||
t.string("temporaryMode");
|
||||
t.string("temporaryRange"); // could be cron or relative time like 1H or 1minute etc
|
||||
t.datetime("temporaryAccessStartTime");
|
||||
t.datetime("temporaryAccessEndTime");
|
||||
t.jsonb("permissions").notNullable();
|
||||
t.timestamps(true, true, true);
|
||||
});
|
||||
}
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.ProjectUserAdditionalPrivilege);
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await dropOnUpdateTrigger(knex, TableName.ProjectUserAdditionalPrivilege);
|
||||
await knex.schema.dropTableIfExists(TableName.ProjectUserAdditionalPrivilege);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
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.IdentityProjectAdditionalPrivilege))) {
|
||||
await knex.schema.createTable(TableName.IdentityProjectAdditionalPrivilege, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.string("slug", 60).notNullable();
|
||||
t.uuid("projectMembershipId").notNullable();
|
||||
t.foreign("projectMembershipId")
|
||||
.references("id")
|
||||
.inTable(TableName.IdentityProjectMembership)
|
||||
.onDelete("CASCADE");
|
||||
t.boolean("isTemporary").notNullable().defaultTo(false);
|
||||
t.string("temporaryMode");
|
||||
t.string("temporaryRange"); // could be cron or relative time like 1H or 1minute etc
|
||||
t.datetime("temporaryAccessStartTime");
|
||||
t.datetime("temporaryAccessEndTime");
|
||||
t.jsonb("permissions").notNullable();
|
||||
t.timestamps(true, true, true);
|
||||
});
|
||||
}
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.IdentityProjectAdditionalPrivilege);
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await dropOnUpdateTrigger(knex, TableName.IdentityProjectAdditionalPrivilege);
|
||||
await knex.schema.dropTableIfExists(TableName.IdentityProjectAdditionalPrivilege);
|
||||
}
|
24
backend/src/db/schemas/dynamic-secret-leases.ts
Normal file
24
backend/src/db/schemas/dynamic-secret-leases.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 DynamicSecretLeasesSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
version: z.number(),
|
||||
externalEntityId: z.string(),
|
||||
expireAt: z.date(),
|
||||
status: z.string().nullable().optional(),
|
||||
statusDetails: z.string().nullable().optional(),
|
||||
dynamicSecretId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
});
|
||||
|
||||
export type TDynamicSecretLeases = z.infer<typeof DynamicSecretLeasesSchema>;
|
||||
export type TDynamicSecretLeasesInsert = Omit<z.input<typeof DynamicSecretLeasesSchema>, TImmutableDBKeys>;
|
||||
export type TDynamicSecretLeasesUpdate = Partial<Omit<z.input<typeof DynamicSecretLeasesSchema>, TImmutableDBKeys>>;
|
31
backend/src/db/schemas/dynamic-secrets.ts
Normal file
31
backend/src/db/schemas/dynamic-secrets.ts
Normal file
@ -0,0 +1,31 @@
|
||||
// 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 DynamicSecretsSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string(),
|
||||
version: z.number(),
|
||||
type: z.string(),
|
||||
defaultTTL: z.string(),
|
||||
maxTTL: z.string().nullable().optional(),
|
||||
inputIV: z.string(),
|
||||
inputCiphertext: z.string(),
|
||||
inputTag: z.string(),
|
||||
algorithm: z.string().default("aes-256-gcm"),
|
||||
keyEncoding: z.string().default("utf8"),
|
||||
folderId: z.string().uuid(),
|
||||
status: z.string().nullable().optional(),
|
||||
statusDetails: z.string().nullable().optional(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
});
|
||||
|
||||
export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;
|
||||
export type TDynamicSecretsInsert = Omit<z.input<typeof DynamicSecretsSchema>, TImmutableDBKeys>;
|
||||
export type TDynamicSecretsUpdate = Partial<Omit<z.input<typeof DynamicSecretsSchema>, TImmutableDBKeys>>;
|
@ -0,0 +1,31 @@
|
||||
// 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 IdentityProjectAdditionalPrivilegeSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
slug: z.string(),
|
||||
projectMembershipId: z.string().uuid(),
|
||||
isTemporary: z.boolean().default(false),
|
||||
temporaryMode: z.string().nullable().optional(),
|
||||
temporaryRange: z.string().nullable().optional(),
|
||||
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||
temporaryAccessEndTime: z.date().nullable().optional(),
|
||||
permissions: z.unknown(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
});
|
||||
|
||||
export type TIdentityProjectAdditionalPrivilege = z.infer<typeof IdentityProjectAdditionalPrivilegeSchema>;
|
||||
export type TIdentityProjectAdditionalPrivilegeInsert = Omit<
|
||||
z.input<typeof IdentityProjectAdditionalPrivilegeSchema>,
|
||||
TImmutableDBKeys
|
||||
>;
|
||||
export type TIdentityProjectAdditionalPrivilegeUpdate = Partial<
|
||||
Omit<z.input<typeof IdentityProjectAdditionalPrivilegeSchema>, TImmutableDBKeys>
|
||||
>;
|
31
backend/src/db/schemas/identity-project-membership-role.ts
Normal file
31
backend/src/db/schemas/identity-project-membership-role.ts
Normal file
@ -0,0 +1,31 @@
|
||||
// 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 IdentityProjectMembershipRoleSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
role: z.string(),
|
||||
projectMembershipId: z.string().uuid(),
|
||||
customRoleId: z.string().uuid().nullable().optional(),
|
||||
isTemporary: z.boolean().default(false),
|
||||
temporaryMode: z.string().nullable().optional(),
|
||||
temporaryRange: z.string().nullable().optional(),
|
||||
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||
temporaryAccessEndTime: z.date().nullable().optional(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
});
|
||||
|
||||
export type TIdentityProjectMembershipRole = z.infer<typeof IdentityProjectMembershipRoleSchema>;
|
||||
export type TIdentityProjectMembershipRoleInsert = Omit<
|
||||
z.input<typeof IdentityProjectMembershipRoleSchema>,
|
||||
TImmutableDBKeys
|
||||
>;
|
||||
export type TIdentityProjectMembershipRoleUpdate = Partial<
|
||||
Omit<z.input<typeof IdentityProjectMembershipRoleSchema>, TImmutableDBKeys>
|
||||
>;
|
@ -3,11 +3,15 @@ export * from "./audit-logs";
|
||||
export * from "./auth-token-sessions";
|
||||
export * from "./auth-tokens";
|
||||
export * from "./backup-private-key";
|
||||
export * from "./dynamic-secret-leases";
|
||||
export * from "./dynamic-secrets";
|
||||
export * from "./git-app-install-sessions";
|
||||
export * from "./git-app-org";
|
||||
export * from "./identities";
|
||||
export * from "./identity-access-tokens";
|
||||
export * from "./identity-org-memberships";
|
||||
export * from "./identity-project-additional-privilege";
|
||||
export * from "./identity-project-membership-role";
|
||||
export * from "./identity-project-memberships";
|
||||
export * from "./identity-ua-client-secrets";
|
||||
export * from "./identity-universal-auths";
|
||||
@ -25,6 +29,8 @@ export * from "./project-environments";
|
||||
export * from "./project-keys";
|
||||
export * from "./project-memberships";
|
||||
export * from "./project-roles";
|
||||
export * from "./project-user-additional-privilege";
|
||||
export * from "./project-user-membership-roles";
|
||||
export * from "./projects";
|
||||
export * from "./saml-configs";
|
||||
export * from "./scim-tokens";
|
||||
|
@ -20,6 +20,8 @@ export enum TableName {
|
||||
Environment = "project_environments",
|
||||
ProjectMembership = "project_memberships",
|
||||
ProjectRoles = "project_roles",
|
||||
ProjectUserAdditionalPrivilege = "project_user_additional_privilege",
|
||||
ProjectUserMembershipRole = "project_user_membership_roles",
|
||||
ProjectKeys = "project_keys",
|
||||
Secret = "secrets",
|
||||
SecretBlindIndex = "secret_blind_indexes",
|
||||
@ -41,6 +43,8 @@ export enum TableName {
|
||||
IdentityUaClientSecret = "identity_ua_client_secrets",
|
||||
IdentityOrgMembership = "identity_org_memberships",
|
||||
IdentityProjectMembership = "identity_project_memberships",
|
||||
IdentityProjectMembershipRole = "identity_project_membership_role",
|
||||
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
|
||||
ScimToken = "scim_tokens",
|
||||
SecretApprovalPolicy = "secret_approval_policies",
|
||||
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
||||
@ -57,6 +61,8 @@ export enum TableName {
|
||||
GitAppOrg = "git_app_org",
|
||||
SecretScanningGitRisk = "secret_scanning_git_risks",
|
||||
TrustedIps = "trusted_ips",
|
||||
DynamicSecret = "dynamic_secrets",
|
||||
DynamicSecretLease = "dynamic_secret_leases",
|
||||
// junction tables with tags
|
||||
JnSecretTag = "secret_tag_junction",
|
||||
SecretVersionTag = "secret_version_tag_junction"
|
||||
|
31
backend/src/db/schemas/project-user-additional-privilege.ts
Normal file
31
backend/src/db/schemas/project-user-additional-privilege.ts
Normal file
@ -0,0 +1,31 @@
|
||||
// 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 ProjectUserAdditionalPrivilegeSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
slug: z.string(),
|
||||
projectMembershipId: z.string().uuid(),
|
||||
isTemporary: z.boolean().default(false),
|
||||
temporaryMode: z.string().nullable().optional(),
|
||||
temporaryRange: z.string().nullable().optional(),
|
||||
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||
temporaryAccessEndTime: z.date().nullable().optional(),
|
||||
permissions: z.unknown(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
});
|
||||
|
||||
export type TProjectUserAdditionalPrivilege = z.infer<typeof ProjectUserAdditionalPrivilegeSchema>;
|
||||
export type TProjectUserAdditionalPrivilegeInsert = Omit<
|
||||
z.input<typeof ProjectUserAdditionalPrivilegeSchema>,
|
||||
TImmutableDBKeys
|
||||
>;
|
||||
export type TProjectUserAdditionalPrivilegeUpdate = Partial<
|
||||
Omit<z.input<typeof ProjectUserAdditionalPrivilegeSchema>, TImmutableDBKeys>
|
||||
>;
|
31
backend/src/db/schemas/project-user-membership-roles.ts
Normal file
31
backend/src/db/schemas/project-user-membership-roles.ts
Normal file
@ -0,0 +1,31 @@
|
||||
// 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 ProjectUserMembershipRolesSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
role: z.string(),
|
||||
projectMembershipId: z.string().uuid(),
|
||||
customRoleId: z.string().uuid().nullable().optional(),
|
||||
isTemporary: z.boolean().default(false),
|
||||
temporaryMode: z.string().nullable().optional(),
|
||||
temporaryRange: z.string().nullable().optional(),
|
||||
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||
temporaryAccessEndTime: z.date().nullable().optional(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
});
|
||||
|
||||
export type TProjectUserMembershipRoles = z.infer<typeof ProjectUserMembershipRolesSchema>;
|
||||
export type TProjectUserMembershipRolesInsert = Omit<
|
||||
z.input<typeof ProjectUserMembershipRolesSchema>,
|
||||
TImmutableDBKeys
|
||||
>;
|
||||
export type TProjectUserMembershipRolesUpdate = Partial<
|
||||
Omit<z.input<typeof ProjectUserMembershipRolesSchema>, TImmutableDBKeys>
|
||||
>;
|
@ -4,7 +4,7 @@ import { Knex } from "knex";
|
||||
|
||||
import { encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||
|
||||
import { OrgMembershipRole, SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
|
||||
import { ProjectMembershipRole, SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
|
||||
import { buildUserProjectKey, getUserPrivateKey, seedData1 } from "../seed-data";
|
||||
|
||||
export const DEFAULT_PROJECT_ENVS = [
|
||||
@ -30,10 +30,16 @@ export async function seed(knex: Knex): Promise<void> {
|
||||
})
|
||||
.returning("*");
|
||||
|
||||
await knex(TableName.ProjectMembership).insert({
|
||||
projectId: project.id,
|
||||
role: OrgMembershipRole.Admin,
|
||||
userId: seedData1.id
|
||||
const projectMembership = await knex(TableName.ProjectMembership)
|
||||
.insert({
|
||||
projectId: project.id,
|
||||
userId: seedData1.id,
|
||||
role: ProjectMembershipRole.Admin
|
||||
})
|
||||
.returning("*");
|
||||
await knex(TableName.ProjectUserMembershipRole).insert({
|
||||
role: ProjectMembershipRole.Admin,
|
||||
projectMembershipId: projectMembership[0].id
|
||||
});
|
||||
|
||||
const user = await knex(TableName.UserEncryptionKey).where({ userId: seedData1.id }).first();
|
||||
|
@ -75,9 +75,16 @@ export async function seed(knex: Knex): Promise<void> {
|
||||
}
|
||||
]);
|
||||
|
||||
await knex(TableName.IdentityProjectMembership).insert({
|
||||
identityId: seedData1.machineIdentity.id,
|
||||
const identityProjectMembership = await knex(TableName.IdentityProjectMembership)
|
||||
.insert({
|
||||
identityId: seedData1.machineIdentity.id,
|
||||
projectId: seedData1.project.id,
|
||||
role: ProjectMembershipRole.Admin
|
||||
})
|
||||
.returning("*");
|
||||
|
||||
await knex(TableName.IdentityProjectMembershipRole).insert({
|
||||
role: ProjectMembershipRole.Admin,
|
||||
projectId: seedData1.project.id
|
||||
projectMembershipId: identityProjectMembership[0].id
|
||||
});
|
||||
}
|
||||
|
197
backend/src/ee/routes/v1/dynamic-secret-lease-router.ts
Normal file
197
backend/src/ee/routes/v1/dynamic-secret-lease-router.ts
Normal file
@ -0,0 +1,197 @@
|
||||
import ms from "ms";
|
||||
import { z } from "zod";
|
||||
|
||||
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
|
||||
import { DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs";
|
||||
import { daysToMillisecond } from "@app/lib/dates";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
dynamicSecretName: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.dynamicSecretName).toLowerCase(),
|
||||
projectSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.projectSlug),
|
||||
ttl: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(DYNAMIC_SECRET_LEASES.CREATE.ttl)
|
||||
.superRefine((val, ctx) => {
|
||||
if (!val) return;
|
||||
const valMs = ms(val);
|
||||
if (valMs < 60 * 1000)
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||
if (valMs > daysToMillisecond(1))
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||
}),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRET_LEASES.CREATE.path),
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.path)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
lease: DynamicSecretLeasesSchema,
|
||||
dynamicSecret: SanitizedDynamicSecretSchema,
|
||||
data: z.unknown()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const { data, lease, dynamicSecret } = await server.services.dynamicSecretLease.create({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
name: req.body.dynamicSecretName,
|
||||
...req.body
|
||||
});
|
||||
return { lease, data, dynamicSecret };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/:leaseId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.DELETE.leaseId)
|
||||
}),
|
||||
body: z.object({
|
||||
projectSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.DELETE.projectSlug),
|
||||
path: z
|
||||
.string()
|
||||
.min(1)
|
||||
.trim()
|
||||
.default("/")
|
||||
.transform(removeTrailingSlash)
|
||||
.describe(DYNAMIC_SECRET_LEASES.DELETE.path),
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.DELETE.environmentSlug),
|
||||
isForced: z.boolean().default(false).describe(DYNAMIC_SECRET_LEASES.DELETE.isForced)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
lease: DynamicSecretLeasesSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const lease = await server.services.dynamicSecretLease.revokeLease({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
leaseId: req.params.leaseId,
|
||||
...req.body
|
||||
});
|
||||
return { lease };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:leaseId/renew",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.leaseId)
|
||||
}),
|
||||
body: z.object({
|
||||
ttl: z
|
||||
.string()
|
||||
.describe(DYNAMIC_SECRET_LEASES.RENEW.ttl)
|
||||
.optional()
|
||||
.superRefine((val, ctx) => {
|
||||
if (!val) return;
|
||||
const valMs = ms(val);
|
||||
if (valMs < 60 * 1000)
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||
if (valMs > daysToMillisecond(1))
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||
}),
|
||||
projectSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.projectSlug),
|
||||
path: z
|
||||
.string()
|
||||
.min(1)
|
||||
.trim()
|
||||
.default("/")
|
||||
.transform(removeTrailingSlash)
|
||||
.describe(DYNAMIC_SECRET_LEASES.RENEW.path),
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.ttl)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
lease: DynamicSecretLeasesSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const lease = await server.services.dynamicSecretLease.renewLease({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
leaseId: req.params.leaseId,
|
||||
...req.body
|
||||
});
|
||||
return { lease };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:leaseId",
|
||||
method: "GET",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.GET_BY_LEASEID.leaseId)
|
||||
}),
|
||||
querystring: z.object({
|
||||
projectSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.GET_BY_LEASEID.projectSlug),
|
||||
path: z
|
||||
.string()
|
||||
.trim()
|
||||
.default("/")
|
||||
.transform(removeTrailingSlash)
|
||||
.describe(DYNAMIC_SECRET_LEASES.GET_BY_LEASEID.path),
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.GET_BY_LEASEID.environmentSlug)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
lease: DynamicSecretLeasesSchema.extend({
|
||||
dynamicSecret: SanitizedDynamicSecretSchema
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const lease = await server.services.dynamicSecretLease.getLeaseDetails({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
leaseId: req.params.leaseId,
|
||||
...req.query
|
||||
});
|
||||
return { lease };
|
||||
}
|
||||
});
|
||||
};
|
290
backend/src/ee/routes/v1/dynamic-secret-router.ts
Normal file
290
backend/src/ee/routes/v1/dynamic-secret-router.ts
Normal file
@ -0,0 +1,290 @@
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import ms from "ms";
|
||||
import { z } from "zod";
|
||||
|
||||
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
|
||||
import { DynamicSecretProviderSchema } from "@app/ee/services/dynamic-secret/providers/models";
|
||||
import { DYNAMIC_SECRETS } from "@app/lib/api-docs";
|
||||
import { daysToMillisecond } from "@app/lib/dates";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.CREATE.projectSlug),
|
||||
provider: DynamicSecretProviderSchema.describe(DYNAMIC_SECRETS.CREATE.provider),
|
||||
defaultTTL: z
|
||||
.string()
|
||||
.describe(DYNAMIC_SECRETS.CREATE.defaultTTL)
|
||||
.superRefine((val, ctx) => {
|
||||
const valMs = ms(val);
|
||||
if (valMs < 60 * 1000)
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||
if (valMs > daysToMillisecond(1))
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||
}),
|
||||
maxTTL: z
|
||||
.string()
|
||||
.describe(DYNAMIC_SECRETS.CREATE.maxTTL)
|
||||
.optional()
|
||||
.superRefine((val, ctx) => {
|
||||
if (!val) return;
|
||||
const valMs = ms(val);
|
||||
if (valMs < 60 * 1000)
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||
if (valMs > daysToMillisecond(1))
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||
})
|
||||
.nullable(),
|
||||
path: z.string().describe(DYNAMIC_SECRETS.CREATE.path).trim().default("/").transform(removeTrailingSlash),
|
||||
environmentSlug: z.string().describe(DYNAMIC_SECRETS.CREATE.environmentSlug).min(1),
|
||||
name: z
|
||||
.string()
|
||||
.describe(DYNAMIC_SECRETS.CREATE.name)
|
||||
.min(1)
|
||||
.toLowerCase()
|
||||
.max(64)
|
||||
.refine((v) => slugify(v) === v, {
|
||||
message: "Slug must be a valid"
|
||||
})
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
dynamicSecret: SanitizedDynamicSecretSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const dynamicSecretCfg = await server.services.dynamicSecret.create({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.body
|
||||
});
|
||||
return { dynamicSecret: dynamicSecretCfg };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "PATCH",
|
||||
url: "/:name",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.UPDATE.name)
|
||||
}),
|
||||
body: z.object({
|
||||
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.UPDATE.projectSlug),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRETS.UPDATE.path),
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRETS.UPDATE.environmentSlug),
|
||||
data: z.object({
|
||||
inputs: z.any().optional().describe(DYNAMIC_SECRETS.UPDATE.inputs),
|
||||
defaultTTL: z
|
||||
.string()
|
||||
.describe(DYNAMIC_SECRETS.UPDATE.defaultTTL)
|
||||
.optional()
|
||||
.superRefine((val, ctx) => {
|
||||
if (!val) return;
|
||||
const valMs = ms(val);
|
||||
if (valMs < 60 * 1000)
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||
if (valMs > daysToMillisecond(1))
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||
}),
|
||||
maxTTL: z
|
||||
.string()
|
||||
.describe(DYNAMIC_SECRETS.UPDATE.maxTTL)
|
||||
.optional()
|
||||
.superRefine((val, ctx) => {
|
||||
if (!val) return;
|
||||
const valMs = ms(val);
|
||||
if (valMs < 60 * 1000)
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||
if (valMs > daysToMillisecond(1))
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||
})
|
||||
.nullable(),
|
||||
newName: z.string().describe(DYNAMIC_SECRETS.UPDATE.newName).optional()
|
||||
})
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
dynamicSecret: SanitizedDynamicSecretSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const dynamicSecretCfg = await server.services.dynamicSecret.updateByName({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
name: req.params.name,
|
||||
path: req.body.path,
|
||||
projectSlug: req.body.projectSlug,
|
||||
environmentSlug: req.body.environmentSlug,
|
||||
...req.body.data
|
||||
});
|
||||
return { dynamicSecret: dynamicSecretCfg };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/:name",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.DELETE.name)
|
||||
}),
|
||||
body: z.object({
|
||||
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.DELETE.projectSlug),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRETS.DELETE.path),
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRETS.DELETE.environmentSlug),
|
||||
isForced: z.boolean().default(false).describe(DYNAMIC_SECRETS.DELETE.isForced)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
dynamicSecret: SanitizedDynamicSecretSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const dynamicSecretCfg = await server.services.dynamicSecret.deleteByName({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
name: req.params.name,
|
||||
...req.body
|
||||
});
|
||||
return { dynamicSecret: dynamicSecretCfg };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:name",
|
||||
method: "GET",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
name: z.string().min(1).describe(DYNAMIC_SECRETS.GET_BY_NAME.name)
|
||||
}),
|
||||
querystring: z.object({
|
||||
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.GET_BY_NAME.projectSlug),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRETS.GET_BY_NAME.path),
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRETS.GET_BY_NAME.environmentSlug)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
dynamicSecret: SanitizedDynamicSecretSchema.extend({
|
||||
inputs: z.unknown()
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const dynamicSecretCfg = await server.services.dynamicSecret.getDetails({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
name: req.params.name,
|
||||
...req.query
|
||||
});
|
||||
return { dynamicSecret: dynamicSecretCfg };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/",
|
||||
method: "GET",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST.projectSlug),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRETS.LIST.path),
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST.environmentSlug)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
dynamicSecrets: SanitizedDynamicSecretSchema.array()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const dynamicSecretCfgs = await server.services.dynamicSecret.list({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.query
|
||||
});
|
||||
return { dynamicSecrets: dynamicSecretCfgs };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:name/leases",
|
||||
method: "GET",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
name: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.name)
|
||||
}),
|
||||
querystring: z.object({
|
||||
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.projectSlug),
|
||||
path: z
|
||||
.string()
|
||||
.trim()
|
||||
.default("/")
|
||||
.transform(removeTrailingSlash)
|
||||
.describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.path),
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.environmentSlug)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
leases: DynamicSecretLeasesSchema.array()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const leases = await server.services.dynamicSecretLease.listLeases({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
name: req.params.name,
|
||||
...req.query
|
||||
});
|
||||
return { leases };
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,329 @@
|
||||
import { MongoAbility, RawRuleOf } from "@casl/ability";
|
||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import ms from "ms";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IdentityProjectAdditionalPrivilegeSchema } from "@app/db/schemas";
|
||||
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
|
||||
import { ProjectPermissionSet } from "@app/ee/services/permission/project-permission";
|
||||
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/permanent",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Create a permanent or a non expiry specific privilege for identity.",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId),
|
||||
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug),
|
||||
slug: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(60)
|
||||
.trim()
|
||||
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||
.refine((v) => slugify(v) === v, {
|
||||
message: "Slug must be a valid slug"
|
||||
})
|
||||
.optional()
|
||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||
permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const privilege = await server.services.identityProjectAdditionalPrivilege.create({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
...req.body,
|
||||
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||
isTemporary: false,
|
||||
permissions: JSON.stringify(packRules(req.body.permissions))
|
||||
});
|
||||
return { privilege };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/temporary",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Create a temporary or a expiring specific privilege for identity.",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId),
|
||||
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug),
|
||||
slug: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(60)
|
||||
.trim()
|
||||
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||
.refine((v) => slugify(v) === v, {
|
||||
message: "Slug must be a valid slug"
|
||||
})
|
||||
.optional()
|
||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||
permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
||||
temporaryMode: z
|
||||
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
|
||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
|
||||
temporaryRange: z
|
||||
.string()
|
||||
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryRange),
|
||||
temporaryAccessStartTime: z
|
||||
.string()
|
||||
.datetime()
|
||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryAccessStartTime)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const privilege = await server.services.identityProjectAdditionalPrivilege.create({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
...req.body,
|
||||
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||
isTemporary: true,
|
||||
permissions: JSON.stringify(packRules(req.body.permissions))
|
||||
});
|
||||
return { privilege };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "PATCH",
|
||||
url: "/",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Update a specific privilege of an identity.",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
// disallow empty string
|
||||
privilegeSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
||||
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.identityId),
|
||||
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.projectSlug),
|
||||
privilegeDetails: z
|
||||
.object({
|
||||
slug: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(60)
|
||||
.trim()
|
||||
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||
.refine((v) => slugify(v) === v, {
|
||||
message: "Slug must be a valid slug"
|
||||
})
|
||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.newSlug),
|
||||
permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
||||
isTemporary: z.boolean().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
|
||||
temporaryMode: z
|
||||
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
|
||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
|
||||
temporaryRange: z
|
||||
.string()
|
||||
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.temporaryRange),
|
||||
temporaryAccessStartTime: z
|
||||
.string()
|
||||
.datetime()
|
||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.temporaryAccessStartTime)
|
||||
})
|
||||
.partial()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const updatedInfo = req.body.privilegeDetails;
|
||||
const privilege = await server.services.identityProjectAdditionalPrivilege.updateBySlug({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
slug: req.body.privilegeSlug,
|
||||
identityId: req.body.identityId,
|
||||
projectSlug: req.body.projectSlug,
|
||||
data: {
|
||||
...updatedInfo,
|
||||
permissions: updatedInfo?.permissions ? JSON.stringify(packRules(updatedInfo.permissions)) : undefined
|
||||
}
|
||||
});
|
||||
return { privilege };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Delete a specific privilege of an identity.",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
privilegeSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.DELETE.slug),
|
||||
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.DELETE.identityId),
|
||||
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.DELETE.projectSlug)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const privilege = await server.services.identityProjectAdditionalPrivilege.deleteBySlug({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
slug: req.body.privilegeSlug,
|
||||
identityId: req.body.identityId,
|
||||
projectSlug: req.body.projectSlug
|
||||
});
|
||||
return { privilege };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:privilegeSlug",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Retrieve details of a specific privilege by privilege slug.",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
privilegeSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.GET_BY_SLUG.slug)
|
||||
}),
|
||||
querystring: z.object({
|
||||
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.GET_BY_SLUG.identityId),
|
||||
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.GET_BY_SLUG.projectSlug)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const privilege = await server.services.identityProjectAdditionalPrivilege.getPrivilegeDetailsBySlug({
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
slug: req.params.privilegeSlug,
|
||||
...req.query
|
||||
});
|
||||
return { privilege };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
description: "List of a specific privilege of an identity in a project.",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.identityId),
|
||||
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.projectSlug),
|
||||
unpacked: z
|
||||
.enum(["false", "true"])
|
||||
.transform((el) => el === "true")
|
||||
.default("true")
|
||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.unpacked)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
privileges: IdentityProjectAdditionalPrivilegeSchema.array()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const privileges = await server.services.identityProjectAdditionalPrivilege.listIdentityProjectPrivileges({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.query
|
||||
});
|
||||
if (req.query.unpacked) {
|
||||
return {
|
||||
privileges: privileges.map(({ permissions, ...el }) => ({
|
||||
...el,
|
||||
permissions: unpackRules(permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
|
||||
}))
|
||||
};
|
||||
}
|
||||
return { privileges };
|
||||
}
|
||||
});
|
||||
};
|
@ -1,3 +1,6 @@
|
||||
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
||||
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
||||
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
|
||||
import { registerLdapRouter } from "./ldap-router";
|
||||
import { registerLicenseRouter } from "./license-router";
|
||||
import { registerOrgRoleRouter } from "./org-role-router";
|
||||
@ -13,6 +16,7 @@ import { registerSecretScanningRouter } from "./secret-scanning-router";
|
||||
import { registerSecretVersionRouter } from "./secret-version-router";
|
||||
import { registerSnapshotRouter } from "./snapshot-router";
|
||||
import { registerTrustedIpRouter } from "./trusted-ip-router";
|
||||
import { registerUserAdditionalPrivilegeRouter } from "./user-additional-privilege-router";
|
||||
|
||||
export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||
// org role starts with organization
|
||||
@ -34,10 +38,26 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||
await server.register(registerSecretRotationProviderRouter, {
|
||||
prefix: "/secret-rotation-providers"
|
||||
});
|
||||
|
||||
await server.register(
|
||||
async (dynamicSecretRouter) => {
|
||||
await dynamicSecretRouter.register(registerDynamicSecretRouter);
|
||||
await dynamicSecretRouter.register(registerDynamicSecretLeaseRouter, { prefix: "/leases" });
|
||||
},
|
||||
{ prefix: "/dynamic-secrets" }
|
||||
);
|
||||
|
||||
await server.register(registerSamlRouter, { prefix: "/sso" });
|
||||
await server.register(registerScimRouter, { prefix: "/scim" });
|
||||
await server.register(registerLdapRouter, { prefix: "/ldap" });
|
||||
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
|
||||
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
|
||||
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
|
||||
await server.register(
|
||||
async (privilegeRouter) => {
|
||||
await privilegeRouter.register(registerUserAdditionalPrivilegeRouter, { prefix: "/users" });
|
||||
await privilegeRouter.register(registerIdentityProjectAdditionalPrivilegeRouter, { prefix: "/identity" });
|
||||
},
|
||||
{ prefix: "/additional-privilege" }
|
||||
);
|
||||
};
|
||||
|
@ -17,6 +17,7 @@ import { z } from "zod";
|
||||
import { LdapConfigsSchema } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
@ -97,8 +98,11 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/config",
|
||||
method: "GET",
|
||||
url: "/config",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
@ -122,6 +126,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
orgId: req.query.organizationId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId
|
||||
});
|
||||
return ldap;
|
||||
@ -129,8 +134,11 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/config",
|
||||
method: "POST",
|
||||
url: "/config",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
body: z.object({
|
||||
@ -151,6 +159,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
orgId: req.body.organizationId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.body
|
||||
});
|
||||
@ -162,17 +171,22 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/config",
|
||||
method: "PATCH",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
body: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
isActive: z.boolean().optional(),
|
||||
url: z.string().trim().optional(),
|
||||
bindDN: z.string().trim().optional(),
|
||||
bindPass: z.string().trim().optional(),
|
||||
searchBase: z.string().trim().optional(),
|
||||
caCert: z.string().trim().optional()
|
||||
}),
|
||||
body: z
|
||||
.object({
|
||||
isActive: z.boolean(),
|
||||
url: z.string().trim(),
|
||||
bindDN: z.string().trim(),
|
||||
bindPass: z.string().trim(),
|
||||
searchBase: z.string().trim(),
|
||||
caCert: z.string().trim()
|
||||
})
|
||||
.partial()
|
||||
.merge(z.object({ organizationId: z.string() })),
|
||||
response: {
|
||||
200: LdapConfigsSchema
|
||||
}
|
||||
@ -182,6 +196,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
orgId: req.body.organizationId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.body
|
||||
});
|
||||
|
@ -3,13 +3,17 @@
|
||||
// TODO(akhilmhdh): Fix this when licence service gets it type
|
||||
import { z } from "zod";
|
||||
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/:organizationId/plans/table",
|
||||
method: "GET",
|
||||
url: "/:organizationId/plans/table",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
querystring: z.object({ billingCycle: z.enum(["monthly", "yearly"]) }),
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
@ -24,6 +28,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.params.organizationId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
billingCycle: req.query.billingCycle
|
||||
});
|
||||
return data;
|
||||
@ -31,8 +36,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/plan",
|
||||
method: "GET",
|
||||
url: "/:organizationId/plan",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
@ -45,6 +53,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return { plan };
|
||||
@ -52,8 +61,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/plans",
|
||||
method: "GET",
|
||||
url: "/:organizationId/plans",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
querystring: z.object({ workspaceId: z.string().trim().optional() }),
|
||||
@ -66,6 +78,8 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
const data = await server.services.license.getOrgPlan({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
@ -73,8 +87,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/session/trial",
|
||||
method: "POST",
|
||||
url: "/:organizationId/session/trial",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({ success_url: z.string().trim() }),
|
||||
@ -89,6 +106,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.params.organizationId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
success_url: req.body.success_url
|
||||
});
|
||||
return data;
|
||||
@ -98,6 +116,9 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/:organizationId/customer-portal-session",
|
||||
method: "POST",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
@ -110,6 +131,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
@ -117,8 +139,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/plan/billing",
|
||||
method: "GET",
|
||||
url: "/:organizationId/plan/billing",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
@ -131,6 +156,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
@ -138,8 +164,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/plan/table",
|
||||
method: "GET",
|
||||
url: "/:organizationId/plan/table",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
@ -152,6 +181,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
@ -159,8 +189,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details",
|
||||
method: "GET",
|
||||
url: "/:organizationId/billing-details",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
@ -173,6 +206,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
@ -180,8 +214,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details",
|
||||
method: "PATCH",
|
||||
url: "/:organizationId/billing-details",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({
|
||||
@ -198,6 +235,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId,
|
||||
name: req.body.name,
|
||||
email: req.body.email
|
||||
@ -207,8 +245,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details/payment-methods",
|
||||
method: "GET",
|
||||
url: "/:organizationId/billing-details/payment-methods",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
@ -221,6 +262,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
@ -228,8 +270,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details/payment-methods",
|
||||
method: "POST",
|
||||
url: "/:organizationId/billing-details/payment-methods",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({
|
||||
@ -246,6 +291,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId,
|
||||
success_url: req.body.success_url,
|
||||
cancel_url: req.body.cancel_url
|
||||
@ -255,8 +301,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details/payment-methods/:pmtMethodId",
|
||||
method: "DELETE",
|
||||
url: "/:organizationId/billing-details/payment-methods/:pmtMethodId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
@ -271,6 +320,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
const data = await server.services.license.delOrgPmtMethods({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.params.organizationId,
|
||||
pmtMethodId: req.params.pmtMethodId
|
||||
@ -280,8 +330,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details/tax-ids",
|
||||
method: "GET",
|
||||
url: "/:organizationId/billing-details/tax-ids",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
@ -295,6 +348,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
const data = await server.services.license.getOrgTaxIds({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
@ -303,8 +357,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details/tax-ids",
|
||||
method: "POST",
|
||||
url: "/:organizationId/billing-details/tax-ids",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
@ -322,6 +379,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
const data = await server.services.license.addOrgTaxId({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.params.organizationId,
|
||||
type: req.body.type,
|
||||
@ -332,8 +390,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details/tax-ids/:taxId",
|
||||
method: "DELETE",
|
||||
url: "/:organizationId/billing-details/tax-ids/:taxId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
@ -348,6 +409,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
const data = await server.services.license.delOrgTaxId({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.params.organizationId,
|
||||
taxId: req.params.taxId
|
||||
@ -357,8 +419,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/invoices",
|
||||
method: "GET",
|
||||
url: "/:organizationId/invoices",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
@ -373,15 +438,19 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.params.organizationId
|
||||
orgId: req.params.organizationId,
|
||||
actorAuthMethod: req.permission.authMethod
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/licenses",
|
||||
method: "GET",
|
||||
url: "/:organizationId/licenses",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
@ -396,6 +465,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
|
@ -1,6 +1,8 @@
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { z } from "zod";
|
||||
|
||||
import { OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
||||
import { OrgMembershipRole, OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
@ -8,12 +10,25 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:organizationId/roles",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
slug: z.string().trim(),
|
||||
slug: z
|
||||
.string()
|
||||
.min(1)
|
||||
.trim()
|
||||
.refine(
|
||||
(val) => !Object.keys(OrgMembershipRole).includes(val),
|
||||
"Please choose a different slug, the slug you have entered is reserved"
|
||||
)
|
||||
.refine((v) => slugify(v) === v, {
|
||||
message: "Slug must be a valid"
|
||||
}),
|
||||
name: z.string().trim(),
|
||||
description: z.string().trim().optional(),
|
||||
permissions: z.any().array()
|
||||
@ -30,6 +45,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
req.permission.id,
|
||||
req.params.organizationId,
|
||||
req.body,
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
return { role };
|
||||
@ -39,13 +55,26 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "PATCH",
|
||||
url: "/:organizationId/roles/:roleId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
roleId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
slug: z.string().trim().optional(),
|
||||
slug: z
|
||||
.string()
|
||||
.trim()
|
||||
.optional()
|
||||
.refine(
|
||||
(val) => typeof val === "undefined" || Object.keys(OrgMembershipRole).includes(val),
|
||||
"Please choose a different slug, the slug you have entered is reserved."
|
||||
)
|
||||
.refine((val) => typeof val === "undefined" || slugify(val) === val, {
|
||||
message: "Slug must be a valid"
|
||||
}),
|
||||
name: z.string().trim().optional(),
|
||||
description: z.string().trim().optional(),
|
||||
permissions: z.any().array()
|
||||
@ -63,6 +92,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
req.params.organizationId,
|
||||
req.params.roleId,
|
||||
req.body,
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
return { role };
|
||||
@ -72,6 +102,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/:organizationId/roles/:roleId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
@ -89,6 +122,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
req.permission.id,
|
||||
req.params.organizationId,
|
||||
req.params.roleId,
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
return { role };
|
||||
@ -98,6 +132,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:organizationId/roles",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
@ -117,6 +154,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
const roles = await server.services.orgRole.listRoles(
|
||||
req.permission.id,
|
||||
req.params.organizationId,
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
return { data: { roles } };
|
||||
@ -126,6 +164,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:organizationId/permissions",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
@ -142,6 +183,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
const { permissions, membership } = await server.services.orgRole.getUserPermission(
|
||||
req.permission.id,
|
||||
req.params.organizationId,
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
return { permissions, membership };
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
@ -8,6 +9,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:projectId/roles",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
projectId: z.string().trim()
|
||||
@ -31,6 +35,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
req.permission.id,
|
||||
req.params.projectId,
|
||||
req.body,
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
return { role };
|
||||
@ -40,6 +45,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "PATCH",
|
||||
url: "/:projectId/roles/:roleId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
projectId: z.string().trim(),
|
||||
@ -65,6 +73,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
req.params.projectId,
|
||||
req.params.roleId,
|
||||
req.body,
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
return { role };
|
||||
@ -74,6 +83,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/:projectId/roles/:roleId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
projectId: z.string().trim(),
|
||||
@ -92,6 +104,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
req.permission.id,
|
||||
req.params.projectId,
|
||||
req.params.roleId,
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
return { role };
|
||||
@ -101,6 +114,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:projectId/roles",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
projectId: z.string().trim()
|
||||
@ -121,6 +137,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
req.permission.type,
|
||||
req.permission.id,
|
||||
req.params.projectId,
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
return { data: { roles } };
|
||||
@ -130,6 +147,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:projectId/permissions",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
projectId: z.string().trim()
|
||||
@ -148,6 +168,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
const { permissions, membership } = await server.services.projectRole.getUserPermission(
|
||||
req.permission.id,
|
||||
req.params.projectId,
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
return { data: { permissions, membership } };
|
||||
|
@ -2,7 +2,9 @@ import { z } from "zod";
|
||||
|
||||
import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas";
|
||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { readLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
@ -10,6 +12,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:workspaceId/secret-snapshots",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Return project secret snapshots ids",
|
||||
security: [
|
||||
@ -19,13 +24,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
workspaceId: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.workspaceId)
|
||||
}),
|
||||
querystring: z.object({
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
offset: z.coerce.number().default(0),
|
||||
limit: z.coerce.number().default(20)
|
||||
environment: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.environment),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(PROJECTS.GET_SNAPSHOTS.path),
|
||||
offset: z.coerce.number().default(0).describe(PROJECTS.GET_SNAPSHOTS.offset),
|
||||
limit: z.coerce.number().default(20).describe(PROJECTS.GET_SNAPSHOTS.limit)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -37,6 +42,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
handler: async (req) => {
|
||||
const secretSnapshots = await server.services.snapshot.listSnapshots({
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorId: req.permission.id,
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.params.workspaceId,
|
||||
@ -49,6 +55,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:workspaceId/secret-snapshots/count",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
@ -68,6 +77,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
const count = await server.services.snapshot.projectSecretSnapshotCount({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.params.workspaceId,
|
||||
environment: req.query.environment,
|
||||
@ -80,6 +90,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:workspaceId/audit-logs",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Return audit logs",
|
||||
security: [
|
||||
@ -89,16 +102,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.workspaceId)
|
||||
}),
|
||||
querystring: z.object({
|
||||
eventType: z.nativeEnum(EventType).optional(),
|
||||
userAgentType: z.nativeEnum(UserAgentType).optional(),
|
||||
startDate: z.string().datetime().optional(),
|
||||
endDate: z.string().datetime().optional(),
|
||||
offset: z.coerce.number().default(0),
|
||||
limit: z.coerce.number().default(20),
|
||||
actor: z.string().optional()
|
||||
eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType),
|
||||
userAgentType: z.nativeEnum(UserAgentType).optional().describe(AUDIT_LOGS.EXPORT.userAgentType),
|
||||
startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate),
|
||||
endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate),
|
||||
offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset),
|
||||
limit: z.coerce.number().default(20).describe(AUDIT_LOGS.EXPORT.limit),
|
||||
actor: z.string().optional().describe(AUDIT_LOGS.EXPORT.actor)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -129,6 +142,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
const auditLogs = await server.services.auditLog.listProjectAuditLogs({
|
||||
actorId: req.permission.id,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
projectId: req.params.workspaceId,
|
||||
...req.query,
|
||||
auditLogActor: req.query.actor,
|
||||
@ -141,6 +155,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:workspaceId/audit-logs/filters/actors",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
|
@ -17,6 +17,7 @@ import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
@ -203,8 +204,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/config",
|
||||
method: "GET",
|
||||
url: "/config",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
@ -231,6 +235,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.query.organizationId,
|
||||
type: "org"
|
||||
});
|
||||
@ -239,8 +244,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/config",
|
||||
method: "POST",
|
||||
url: "/config",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
body: z.object({
|
||||
@ -259,6 +267,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
const saml = await server.services.saml.createSamlCfg({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.body.organizationId,
|
||||
...req.body
|
||||
@ -268,8 +277,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/config",
|
||||
method: "PATCH",
|
||||
url: "/config",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
body: z
|
||||
@ -290,6 +302,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
const saml = await server.services.saml.updateSamlCfg({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.body.organizationId,
|
||||
...req.body
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ScimTokensSchema } from "@app/db/schemas";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
@ -20,6 +21,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/scim-tokens",
|
||||
method: "POST",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
body: z.object({
|
||||
@ -39,6 +43,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
actorId: req.permission.id,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.body.organizationId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
description: req.body.description,
|
||||
ttlDays: req.body.ttlDays
|
||||
});
|
||||
@ -50,6 +55,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/scim-tokens",
|
||||
method: "GET",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
@ -65,6 +73,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
const scimTokens = await server.services.scim.listScimTokens({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.query.organizationId
|
||||
});
|
||||
@ -76,6 +85,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/scim-tokens/:scimTokenId",
|
||||
method: "DELETE",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
params: z.object({
|
||||
@ -92,6 +104,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
scimTokenId: req.params.scimTokenId,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId
|
||||
});
|
||||
|
||||
@ -143,7 +156,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
offset: req.query.startIndex,
|
||||
limit: req.query.count,
|
||||
filter: req.query.filter,
|
||||
orgId: req.permission.orgId as string
|
||||
orgId: req.permission.orgId
|
||||
});
|
||||
return users;
|
||||
}
|
||||
@ -181,7 +194,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
handler: async (req) => {
|
||||
const user = await req.server.services.scim.getScimUser({
|
||||
userId: req.params.userId,
|
||||
orgId: req.permission.orgId as string
|
||||
orgId: req.permission.orgId
|
||||
});
|
||||
return user;
|
||||
}
|
||||
@ -240,7 +253,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
email: primaryEmail,
|
||||
firstName: req.body.name.givenName,
|
||||
lastName: req.body.name.familyName,
|
||||
orgId: req.permission.orgId as string
|
||||
orgId: req.permission.orgId
|
||||
});
|
||||
|
||||
return user;
|
||||
@ -277,7 +290,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
handler: async (req) => {
|
||||
const user = await req.server.services.scim.updateScimUser({
|
||||
userId: req.params.userId,
|
||||
orgId: req.permission.orgId as string,
|
||||
orgId: req.permission.orgId,
|
||||
operations: req.body.Operations
|
||||
});
|
||||
return user;
|
||||
@ -327,7 +340,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
handler: async (req) => {
|
||||
const user = await req.server.services.scim.replaceScimUser({
|
||||
userId: req.params.userId,
|
||||
orgId: req.permission.orgId as string,
|
||||
orgId: req.permission.orgId,
|
||||
active: req.body.active
|
||||
});
|
||||
return user;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@ -9,6 +10,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
server.route({
|
||||
url: "/",
|
||||
method: "POST",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
body: z
|
||||
.object({
|
||||
@ -34,6 +38,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
const approval = await server.services.secretApprovalPolicy.createSecretApprovalPolicy({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.body.workspaceId,
|
||||
...req.body,
|
||||
@ -46,6 +51,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
server.route({
|
||||
url: "/:sapId",
|
||||
method: "PATCH",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
sapId: z.string()
|
||||
@ -72,6 +80,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.body,
|
||||
secretPolicyId: req.params.sapId
|
||||
@ -83,6 +92,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
server.route({
|
||||
url: "/:sapId",
|
||||
method: "DELETE",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
sapId: z.string()
|
||||
@ -98,6 +110,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
secretPolicyId: req.params.sapId
|
||||
});
|
||||
@ -108,6 +121,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
server.route({
|
||||
url: "/",
|
||||
method: "GET",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
@ -123,6 +139,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
const approvals = await server.services.secretApprovalPolicy.getSecretApprovalPolicyByProjectId({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.query.workspaceId
|
||||
});
|
||||
@ -133,6 +150,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
server.route({
|
||||
url: "/board",
|
||||
method: "GET",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
@ -150,6 +170,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.query.workspaceId,
|
||||
...req.query
|
||||
|
@ -10,13 +10,17 @@ import {
|
||||
} from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { ApprovalStatus, RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerSecretApprovalRequestRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/",
|
||||
method: "GET",
|
||||
url: "/",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
@ -52,6 +56,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
const approvals = await server.services.secretApprovalRequest.getSecretApprovals({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.query,
|
||||
projectId: req.query.workspaceId
|
||||
@ -61,8 +66,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/count",
|
||||
method: "GET",
|
||||
url: "/count",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
@ -81,6 +89,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
const approvals = await server.services.secretApprovalRequest.requestCount({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.query.workspaceId
|
||||
});
|
||||
@ -91,6 +100,9 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
server.route({
|
||||
url: "/:id/merge",
|
||||
method: "POST",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
id: z.string()
|
||||
@ -106,6 +118,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
const { approval } = await server.services.secretApprovalRequest.mergeSecretApprovalRequest({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
approvalId: req.params.id
|
||||
});
|
||||
@ -114,8 +127,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:id/review",
|
||||
method: "POST",
|
||||
url: "/:id/review",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
id: z.string()
|
||||
@ -134,6 +150,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
const review = await server.services.secretApprovalRequest.reviewApproval({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
approvalId: req.params.id,
|
||||
status: req.body.status
|
||||
@ -143,8 +160,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:id/status",
|
||||
method: "POST",
|
||||
url: "/:id/status",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
id: z.string()
|
||||
@ -163,6 +183,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
const approval = await server.services.secretApprovalRequest.updateApprovalStatus({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
approvalId: req.params.id,
|
||||
status: req.body.status
|
||||
@ -198,8 +219,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
.array()
|
||||
.optional();
|
||||
server.route({
|
||||
url: "/:id",
|
||||
method: "GET",
|
||||
url: "/:id",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
id: z.string()
|
||||
@ -271,6 +295,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
const approval = await server.services.secretApprovalRequest.getSecretApprovalDetails({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
id: req.params.id
|
||||
});
|
||||
|
@ -1,12 +1,16 @@
|
||||
import { z } from "zod";
|
||||
|
||||
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 registerSecretRotationProviderRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/:workspaceId",
|
||||
method: "GET",
|
||||
url: "/:workspaceId",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
@ -30,6 +34,7 @@ export const registerSecretRotationProviderRouter = async (server: FastifyZodPro
|
||||
const providers = await server.services.secretRotation.getProviderTemplates({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.params.workspaceId
|
||||
});
|
||||
|
@ -2,13 +2,17 @@ import { z } from "zod";
|
||||
|
||||
import { SecretRotationOutputsSchema, SecretRotationsSchema, SecretsSchema } from "@app/db/schemas";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerSecretRotationRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/",
|
||||
method: "POST",
|
||||
url: "/",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
@ -39,6 +43,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
||||
handler: async (req) => {
|
||||
const secretRotation = await server.services.secretRotation.createRotation({
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorId: req.permission.id,
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.body,
|
||||
@ -51,6 +56,9 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
||||
server.route({
|
||||
url: "/restart",
|
||||
method: "POST",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
id: z.string().trim()
|
||||
@ -74,6 +82,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
||||
const secretRotation = await server.services.secretRotation.restartById({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
rotationId: req.body.id
|
||||
});
|
||||
@ -84,6 +93,9 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
||||
server.route({
|
||||
url: "/",
|
||||
method: "GET",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
@ -125,6 +137,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
||||
const secretRotations = await server.services.secretRotation.getByProjectId({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.query.workspaceId
|
||||
});
|
||||
@ -133,8 +146,11 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:id",
|
||||
method: "DELETE",
|
||||
url: "/:id",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
id: z.string().trim()
|
||||
@ -158,6 +174,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
||||
const secretRotation = await server.services.secretRotation.deleteById({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
rotationId: req.params.id
|
||||
});
|
||||
|
@ -2,13 +2,17 @@ import { z } from "zod";
|
||||
|
||||
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
||||
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerSecretScanningRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/create-installation-session/organization",
|
||||
method: "POST",
|
||||
url: "/create-installation-session/organization",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
@ -22,6 +26,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
||||
const session = await server.services.secretScanning.createInstallationSession({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.body.organizationId
|
||||
});
|
||||
@ -30,8 +35,11 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/link-installation",
|
||||
method: "POST",
|
||||
url: "/link-installation",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
installationId: z.string(),
|
||||
@ -46,6 +54,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
||||
const { installatedApp } = await server.services.secretScanning.linkInstallationToOrg({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.body
|
||||
});
|
||||
@ -54,8 +63,11 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/installation-status/organization/:organizationId",
|
||||
method: "GET",
|
||||
url: "/installation-status/organization/:organizationId",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
@ -67,6 +79,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
||||
const appInstallationCompleted = await server.services.secretScanning.getOrgInstallationStatus({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
@ -77,6 +90,9 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
||||
server.route({
|
||||
url: "/organization/:organizationId/risks",
|
||||
method: "GET",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
@ -88,6 +104,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
||||
const { risks } = await server.services.secretScanning.getRisksByOrg({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
@ -96,8 +113,11 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/organization/:organizationId/risks/:riskId/status",
|
||||
method: "POST",
|
||||
url: "/organization/:organizationId/risks/:riskId/status",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim(), riskId: z.string().trim() }),
|
||||
body: z.object({ status: z.nativeEnum(SecretScanningRiskStatus) }),
|
||||
@ -110,6 +130,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
||||
const { risk } = await server.services.secretScanning.updateRiskStatus({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.params.organizationId,
|
||||
riskId: req.params.riskId,
|
||||
|
@ -1,13 +1,17 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretVersionsSchema } from "@app/db/schemas";
|
||||
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 registerSecretVersionRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/:secretId/secret-versions",
|
||||
method: "GET",
|
||||
url: "/:secretId/secret-versions",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
secretId: z.string()
|
||||
@ -27,6 +31,7 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) =>
|
||||
const secretVersions = await server.services.secret.getSecretVersions({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
limit: req.query.limit,
|
||||
offset: req.query.offset,
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretSnapshotsSchema, SecretTagsSchema, SecretVersionsSchema } from "@app/db/schemas";
|
||||
import { PROJECTS } from "@app/lib/api-docs";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
@ -8,6 +10,9 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:secretSnapshotId",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
secretSnapshotId: z.string().trim()
|
||||
@ -46,6 +51,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||
const secretSnapshot = await server.services.snapshot.getSnapshotData({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
id: req.params.secretSnapshotId
|
||||
});
|
||||
@ -56,6 +62,9 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:secretSnapshotId/rollback",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Roll back project secrets to those captured in a secret snapshot version.",
|
||||
security: [
|
||||
@ -65,7 +74,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretSnapshotId: z.string().trim()
|
||||
secretSnapshotId: z.string().trim().describe(PROJECTS.ROLLBACK_TO_SNAPSHOT.secretSnapshotId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -78,6 +87,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||
const secretSnapshot = await server.services.snapshot.rollbackSnapshot({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
id: req.params.secretSnapshotId
|
||||
});
|
||||
|
@ -2,13 +2,17 @@ import { z } from "zod";
|
||||
|
||||
import { TrustedIpsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/:workspaceId/trusted-ips",
|
||||
method: "GET",
|
||||
url: "/:workspaceId/trusted-ips",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
@ -22,6 +26,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const trustedIps = await server.services.trustedIp.listIpsByProjectId({
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
projectId: req.params.workspaceId,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
@ -32,8 +37,11 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:workspaceId/trusted-ips",
|
||||
method: "POST",
|
||||
url: "/:workspaceId/trusted-ips",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
@ -52,6 +60,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { trustedIp, project } = await server.services.trustedIp.addProjectIp({
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
projectId: req.params.workspaceId,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
@ -76,8 +85,11 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
||||
method: "PATCH",
|
||||
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
@ -99,6 +111,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
||||
projectId: req.params.workspaceId,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
trustedIpId: req.params.trustedIpId,
|
||||
...req.body
|
||||
@ -121,8 +134,11 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
||||
method: "DELETE",
|
||||
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
@ -140,6 +156,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
||||
projectId: req.params.workspaceId,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
trustedIpId: req.params.trustedIpId
|
||||
});
|
||||
|
256
backend/src/ee/routes/v1/user-additional-privilege-router.ts
Normal file
256
backend/src/ee/routes/v1/user-additional-privilege-router.ts
Normal file
@ -0,0 +1,256 @@
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import ms from "ms";
|
||||
import { z } from "zod";
|
||||
|
||||
import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas";
|
||||
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
|
||||
import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/permanent",
|
||||
method: "POST",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId),
|
||||
slug: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(60)
|
||||
.trim()
|
||||
.refine((v) => v.toLowerCase() === v, "Slug must be lowercase")
|
||||
.refine((v) => slugify(v) === v, {
|
||||
message: "Slug must be a valid slug"
|
||||
})
|
||||
.optional()
|
||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const privilege = await server.services.projectUserAdditionalPrivilege.create({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
...req.body,
|
||||
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||
isTemporary: false,
|
||||
permissions: JSON.stringify(req.body.permissions)
|
||||
});
|
||||
return { privilege };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/temporary",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId),
|
||||
slug: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(60)
|
||||
.trim()
|
||||
.refine((v) => v.toLowerCase() === v, "Slug must be lowercase")
|
||||
.refine((v) => slugify(v) === v, {
|
||||
message: "Slug must be a valid slug"
|
||||
})
|
||||
.optional()
|
||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
||||
temporaryMode: z
|
||||
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
|
||||
temporaryRange: z
|
||||
.string()
|
||||
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryRange),
|
||||
temporaryAccessStartTime: z
|
||||
.string()
|
||||
.datetime()
|
||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryAccessStartTime)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const privilege = await server.services.projectUserAdditionalPrivilege.create({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
...req.body,
|
||||
slug: req.body.slug ? slugify(req.body.slug) : `privilege-${slugify(alphaNumericNanoId(12))}`,
|
||||
isTemporary: true,
|
||||
permissions: JSON.stringify(req.body.permissions)
|
||||
});
|
||||
return { privilege };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "PATCH",
|
||||
url: "/:privilegeId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
privilegeId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.privilegeId)
|
||||
}),
|
||||
body: z
|
||||
.object({
|
||||
slug: z
|
||||
.string()
|
||||
.max(60)
|
||||
.trim()
|
||||
.refine((v) => v.toLowerCase() === v, "Slug must be lowercase")
|
||||
.refine((v) => slugify(v) === v, {
|
||||
message: "Slug must be a valid slug"
|
||||
})
|
||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
||||
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
||||
isTemporary: z.boolean().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
|
||||
temporaryMode: z
|
||||
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
|
||||
temporaryRange: z
|
||||
.string()
|
||||
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryRange),
|
||||
temporaryAccessStartTime: z
|
||||
.string()
|
||||
.datetime()
|
||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryAccessStartTime)
|
||||
})
|
||||
.partial(),
|
||||
response: {
|
||||
200: z.object({
|
||||
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const privilege = await server.services.projectUserAdditionalPrivilege.updateById({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
...req.body,
|
||||
permissions: req.body.permissions ? JSON.stringify(req.body.permissions) : undefined,
|
||||
privilegeId: req.params.privilegeId
|
||||
});
|
||||
return { privilege };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/:privilegeId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
privilegeId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.DELETE.privilegeId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const privilege = await server.services.projectUserAdditionalPrivilege.deleteById({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
privilegeId: req.params.privilegeId
|
||||
});
|
||||
return { privilege };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
projectMembershipId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.LIST.projectMembershipId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
privileges: ProjectUserAdditionalPrivilegeSchema.array()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const privileges = await server.services.projectUserAdditionalPrivilege.listPrivileges({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
projectMembershipId: req.query.projectMembershipId
|
||||
});
|
||||
return { privileges };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:privilegeId",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
privilegeId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.GET_BY_PRIVILEGEID.privilegeId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const privilege = await server.services.projectUserAdditionalPrivilege.getPrivilegeDetailsById({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
privilegeId: req.params.privilegeId
|
||||
});
|
||||
return { privilege };
|
||||
}
|
||||
});
|
||||
};
|
@ -31,10 +31,17 @@ export const auditLogServiceFactory = ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectId,
|
||||
auditLogActor
|
||||
}: TListProjectAuditLogDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||
const auditLogs = await auditLogDAL.find({
|
||||
startDate,
|
||||
|
@ -0,0 +1,80 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { DynamicSecretLeasesSchema, TableName } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
|
||||
export type TDynamicSecretLeaseDALFactory = ReturnType<typeof dynamicSecretLeaseDALFactory>;
|
||||
|
||||
export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.DynamicSecretLease);
|
||||
|
||||
const countLeasesForDynamicSecret = async (dynamicSecretId: string, tx?: Knex) => {
|
||||
try {
|
||||
const doc = await (tx || db)(TableName.DynamicSecretLease).count("*").where({ dynamicSecretId }).first();
|
||||
return parseInt(doc || "0", 10);
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "DynamicSecretCountLeases" });
|
||||
}
|
||||
};
|
||||
|
||||
const findById = async (id: string, tx?: Knex) => {
|
||||
try {
|
||||
const doc = await (tx || db)(TableName.DynamicSecretLease)
|
||||
.where({ [`${TableName.DynamicSecretLease}.id` as "id"]: id })
|
||||
.first()
|
||||
.join(
|
||||
TableName.DynamicSecret,
|
||||
`${TableName.DynamicSecretLease}.dynamicSecretId`,
|
||||
`${TableName.DynamicSecret}.id`
|
||||
)
|
||||
.select(selectAllTableCols(TableName.DynamicSecretLease))
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.DynamicSecret).as("dynId"),
|
||||
db.ref("name").withSchema(TableName.DynamicSecret).as("dynName"),
|
||||
db.ref("version").withSchema(TableName.DynamicSecret).as("dynVersion"),
|
||||
db.ref("type").withSchema(TableName.DynamicSecret).as("dynType"),
|
||||
db.ref("defaultTTL").withSchema(TableName.DynamicSecret).as("dynDefaultTTL"),
|
||||
db.ref("maxTTL").withSchema(TableName.DynamicSecret).as("dynMaxTTL"),
|
||||
db.ref("inputIV").withSchema(TableName.DynamicSecret).as("dynInputIV"),
|
||||
db.ref("inputTag").withSchema(TableName.DynamicSecret).as("dynInputTag"),
|
||||
db.ref("inputCiphertext").withSchema(TableName.DynamicSecret).as("dynInputCiphertext"),
|
||||
db.ref("algorithm").withSchema(TableName.DynamicSecret).as("dynAlgorithm"),
|
||||
db.ref("keyEncoding").withSchema(TableName.DynamicSecret).as("dynKeyEncoding"),
|
||||
db.ref("folderId").withSchema(TableName.DynamicSecret).as("dynFolderId"),
|
||||
db.ref("status").withSchema(TableName.DynamicSecret).as("dynStatus"),
|
||||
db.ref("statusDetails").withSchema(TableName.DynamicSecret).as("dynStatusDetails"),
|
||||
db.ref("createdAt").withSchema(TableName.DynamicSecret).as("dynCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.DynamicSecret).as("dynUpdatedAt")
|
||||
);
|
||||
if (!doc) return;
|
||||
|
||||
return {
|
||||
...DynamicSecretLeasesSchema.parse(doc),
|
||||
dynamicSecret: {
|
||||
id: doc.dynId,
|
||||
name: doc.dynName,
|
||||
version: doc.dynVersion,
|
||||
type: doc.dynType,
|
||||
defaultTTL: doc.dynDefaultTTL,
|
||||
maxTTL: doc.dynMaxTTL,
|
||||
inputIV: doc.dynInputIV,
|
||||
inputTag: doc.dynInputTag,
|
||||
inputCiphertext: doc.dynInputCiphertext,
|
||||
algorithm: doc.dynAlgorithm,
|
||||
keyEncoding: doc.dynKeyEncoding,
|
||||
folderId: doc.dynFolderId,
|
||||
status: doc.dynStatus,
|
||||
statusDetails: doc.dynStatusDetails,
|
||||
createdAt: doc.dynCreatedAt,
|
||||
updatedAt: doc.dynUpdatedAt
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "DynamicSecretLeaseFindById" });
|
||||
}
|
||||
};
|
||||
|
||||
return { ...orm, findById, countLeasesForDynamicSecret };
|
||||
};
|
@ -0,0 +1,159 @@
|
||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||
import { DisableRotationErrors } from "@app/ee/services/secret-rotation/secret-rotation-queue";
|
||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||
|
||||
import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
|
||||
import { DynamicSecretStatus } from "../dynamic-secret/dynamic-secret-types";
|
||||
import { DynamicSecretProviders, TDynamicProviderFns } from "../dynamic-secret/providers/models";
|
||||
import { TDynamicSecretLeaseDALFactory } from "./dynamic-secret-lease-dal";
|
||||
|
||||
type TDynamicSecretLeaseQueueServiceFactoryDep = {
|
||||
queueService: TQueueServiceFactory;
|
||||
dynamicSecretLeaseDAL: Pick<TDynamicSecretLeaseDALFactory, "findById" | "deleteById" | "find" | "updateById">;
|
||||
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "findById" | "deleteById" | "updateById">;
|
||||
dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>;
|
||||
};
|
||||
|
||||
export type TDynamicSecretLeaseQueueServiceFactory = ReturnType<typeof dynamicSecretLeaseQueueServiceFactory>;
|
||||
|
||||
export const dynamicSecretLeaseQueueServiceFactory = ({
|
||||
queueService,
|
||||
dynamicSecretDAL,
|
||||
dynamicSecretProviders,
|
||||
dynamicSecretLeaseDAL
|
||||
}: TDynamicSecretLeaseQueueServiceFactoryDep) => {
|
||||
const pruneDynamicSecret = async (dynamicSecretCfgId: string) => {
|
||||
await queueService.queue(
|
||||
QueueName.DynamicSecretRevocation,
|
||||
QueueJobs.DynamicSecretPruning,
|
||||
{ dynamicSecretCfgId },
|
||||
{
|
||||
jobId: dynamicSecretCfgId,
|
||||
backoff: {
|
||||
type: "exponential",
|
||||
delay: 3000
|
||||
},
|
||||
removeOnFail: {
|
||||
count: 3
|
||||
},
|
||||
removeOnComplete: true
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const setLeaseRevocation = async (leaseId: string, expiry: number) => {
|
||||
await queueService.queue(
|
||||
QueueName.DynamicSecretRevocation,
|
||||
QueueJobs.DynamicSecretRevocation,
|
||||
{ leaseId },
|
||||
{
|
||||
jobId: leaseId,
|
||||
backoff: {
|
||||
type: "exponential",
|
||||
delay: 3000
|
||||
},
|
||||
delay: expiry,
|
||||
removeOnFail: {
|
||||
count: 3
|
||||
},
|
||||
removeOnComplete: true
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const unsetLeaseRevocation = async (leaseId: string) => {
|
||||
await queueService.stopJobById(QueueName.DynamicSecretRevocation, leaseId);
|
||||
};
|
||||
|
||||
queueService.start(QueueName.DynamicSecretRevocation, async (job) => {
|
||||
try {
|
||||
if (job.name === QueueJobs.DynamicSecretRevocation) {
|
||||
const { leaseId } = job.data as { leaseId: string };
|
||||
logger.info("Dynamic secret lease revocation started: ", leaseId, job.id);
|
||||
|
||||
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||
if (!dynamicSecretLease) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" });
|
||||
|
||||
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||
const decryptedStoredInput = JSON.parse(
|
||||
infisicalSymmetricDecrypt({
|
||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||
tag: dynamicSecretCfg.inputTag,
|
||||
iv: dynamicSecretCfg.inputIV
|
||||
})
|
||||
) as object;
|
||||
|
||||
await selectedProvider.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId);
|
||||
await dynamicSecretLeaseDAL.deleteById(dynamicSecretLease.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (job.name === QueueJobs.DynamicSecretPruning) {
|
||||
const { dynamicSecretCfgId } = job.data as { dynamicSecretCfgId: string };
|
||||
logger.info("Dynamic secret pruning started: ", dynamicSecretCfgId, job.id);
|
||||
const dynamicSecretCfg = await dynamicSecretDAL.findById(dynamicSecretCfgId);
|
||||
if (!dynamicSecretCfg) throw new DisableRotationErrors({ message: "Dynamic secret not found" });
|
||||
if ((dynamicSecretCfg.status as DynamicSecretStatus) !== DynamicSecretStatus.Deleting)
|
||||
throw new DisableRotationErrors({ message: "Document not deleted" });
|
||||
|
||||
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfgId });
|
||||
if (dynamicSecretLeases.length) {
|
||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||
const decryptedStoredInput = JSON.parse(
|
||||
infisicalSymmetricDecrypt({
|
||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||
tag: dynamicSecretCfg.inputTag,
|
||||
iv: dynamicSecretCfg.inputIV
|
||||
})
|
||||
) as object;
|
||||
|
||||
await Promise.all(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id)));
|
||||
await Promise.all(
|
||||
dynamicSecretLeases.map(({ externalEntityId }) =>
|
||||
selectedProvider.revoke(decryptedStoredInput, externalEntityId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await dynamicSecretDAL.deleteById(dynamicSecretCfgId);
|
||||
}
|
||||
logger.info("Finished dynamic secret job", job.id);
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
|
||||
if (job?.name === QueueJobs.DynamicSecretPruning) {
|
||||
const { dynamicSecretCfgId } = job.data as { dynamicSecretCfgId: string };
|
||||
await dynamicSecretDAL.updateById(dynamicSecretCfgId, {
|
||||
status: DynamicSecretStatus.FailedDeletion,
|
||||
statusDetails: (error as Error)?.message?.slice(0, 255)
|
||||
});
|
||||
}
|
||||
|
||||
if (job?.name === QueueJobs.DynamicSecretRevocation) {
|
||||
const { leaseId } = job.data as { leaseId: string };
|
||||
await dynamicSecretLeaseDAL.updateById(leaseId, {
|
||||
status: DynamicSecretStatus.FailedDeletion,
|
||||
statusDetails: (error as Error)?.message?.slice(0, 255)
|
||||
});
|
||||
}
|
||||
if (error instanceof DisableRotationErrors) {
|
||||
if (job.id) {
|
||||
await queueService.stopRepeatableJobByJobId(QueueName.DynamicSecretRevocation, job.id);
|
||||
}
|
||||
}
|
||||
// propogate to next part
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
pruneDynamicSecret,
|
||||
setLeaseRevocation,
|
||||
unsetLeaseRevocation
|
||||
};
|
||||
};
|
@ -0,0 +1,343 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import ms from "ms";
|
||||
|
||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||
|
||||
import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
|
||||
import { DynamicSecretProviders, TDynamicProviderFns } from "../dynamic-secret/providers/models";
|
||||
import { TDynamicSecretLeaseDALFactory } from "./dynamic-secret-lease-dal";
|
||||
import { TDynamicSecretLeaseQueueServiceFactory } from "./dynamic-secret-lease-queue";
|
||||
import {
|
||||
DynamicSecretLeaseStatus,
|
||||
TCreateDynamicSecretLeaseDTO,
|
||||
TDeleteDynamicSecretLeaseDTO,
|
||||
TDetailsDynamicSecretLeaseDTO,
|
||||
TListDynamicSecretLeasesDTO,
|
||||
TRenewDynamicSecretLeaseDTO
|
||||
} from "./dynamic-secret-lease-types";
|
||||
|
||||
type TDynamicSecretLeaseServiceFactoryDep = {
|
||||
dynamicSecretLeaseDAL: TDynamicSecretLeaseDALFactory;
|
||||
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "findOne">;
|
||||
dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>;
|
||||
dynamicSecretQueueService: TDynamicSecretLeaseQueueServiceFactory;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||
};
|
||||
|
||||
export type TDynamicSecretLeaseServiceFactory = ReturnType<typeof dynamicSecretLeaseServiceFactory>;
|
||||
|
||||
export const dynamicSecretLeaseServiceFactory = ({
|
||||
dynamicSecretLeaseDAL,
|
||||
dynamicSecretProviders,
|
||||
dynamicSecretDAL,
|
||||
folderDAL,
|
||||
permissionService,
|
||||
dynamicSecretQueueService,
|
||||
projectDAL,
|
||||
licenseService
|
||||
}: TDynamicSecretLeaseServiceFactoryDep) => {
|
||||
const create = async ({
|
||||
environmentSlug,
|
||||
path,
|
||||
name,
|
||||
projectSlug,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
ttl
|
||||
}: TCreateDynamicSecretLeaseDTO) => {
|
||||
const appCfg = getConfig();
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
|
||||
const projectId = project.id;
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||
);
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
if (!plan?.dynamicSecret) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to create lease due to plan restriction. Upgrade plan to create dynamic secret."
|
||||
});
|
||||
}
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||
|
||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||
|
||||
const totalLeasesTaken = await dynamicSecretLeaseDAL.countLeasesForDynamicSecret(dynamicSecretCfg.id);
|
||||
if (totalLeasesTaken >= appCfg.MAX_LEASE_LIMIT)
|
||||
throw new BadRequestError({ message: `Max lease limit reached. Limit: ${appCfg.MAX_LEASE_LIMIT}` });
|
||||
|
||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||
const decryptedStoredInput = JSON.parse(
|
||||
infisicalSymmetricDecrypt({
|
||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||
tag: dynamicSecretCfg.inputTag,
|
||||
iv: dynamicSecretCfg.inputIV
|
||||
})
|
||||
) as object;
|
||||
|
||||
const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL;
|
||||
const { maxTTL } = dynamicSecretCfg;
|
||||
const expireAt = new Date(new Date().getTime() + ms(selectedTTL));
|
||||
if (maxTTL) {
|
||||
const maxExpiryDate = new Date(new Date().getTime() + ms(maxTTL));
|
||||
if (expireAt > maxExpiryDate) throw new BadRequestError({ message: "TTL cannot be larger than max TTL" });
|
||||
}
|
||||
|
||||
const { entityId, data } = await selectedProvider.create(decryptedStoredInput, expireAt.getTime());
|
||||
const dynamicSecretLease = await dynamicSecretLeaseDAL.create({
|
||||
expireAt,
|
||||
version: 1,
|
||||
dynamicSecretId: dynamicSecretCfg.id,
|
||||
externalEntityId: entityId
|
||||
});
|
||||
await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, Number(expireAt) - Number(new Date()));
|
||||
return { lease: dynamicSecretLease, dynamicSecret: dynamicSecretCfg, data };
|
||||
};
|
||||
|
||||
const renewLease = async ({
|
||||
ttl,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actorId,
|
||||
actor,
|
||||
projectSlug,
|
||||
path,
|
||||
environmentSlug,
|
||||
leaseId
|
||||
}: TRenewDynamicSecretLeaseDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
|
||||
const projectId = project.id;
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||
);
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
if (!plan?.dynamicSecret) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to renew lease due to plan restriction. Upgrade plan to create dynamic secret."
|
||||
});
|
||||
}
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||
|
||||
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||
if (!dynamicSecretLease) throw new BadRequestError({ message: "Dynamic secret lease not found" });
|
||||
|
||||
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||
const decryptedStoredInput = JSON.parse(
|
||||
infisicalSymmetricDecrypt({
|
||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||
tag: dynamicSecretCfg.inputTag,
|
||||
iv: dynamicSecretCfg.inputIV
|
||||
})
|
||||
) as object;
|
||||
|
||||
const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL;
|
||||
const { maxTTL } = dynamicSecretCfg;
|
||||
const expireAt = new Date(dynamicSecretLease.expireAt.getTime() + ms(selectedTTL));
|
||||
if (maxTTL) {
|
||||
const maxExpiryDate = new Date(dynamicSecretLease.createdAt.getTime() + ms(maxTTL));
|
||||
if (expireAt > maxExpiryDate) throw new BadRequestError({ message: "TTL cannot be larger than max ttl" });
|
||||
}
|
||||
|
||||
const { entityId } = await selectedProvider.renew(
|
||||
decryptedStoredInput,
|
||||
dynamicSecretLease.externalEntityId,
|
||||
expireAt.getTime()
|
||||
);
|
||||
|
||||
await dynamicSecretQueueService.unsetLeaseRevocation(dynamicSecretLease.id);
|
||||
await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, Number(expireAt) - Number(new Date()));
|
||||
const updatedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
|
||||
expireAt,
|
||||
externalEntityId: entityId
|
||||
});
|
||||
return updatedDynamicSecretLease;
|
||||
};
|
||||
|
||||
const revokeLease = async ({
|
||||
leaseId,
|
||||
environmentSlug,
|
||||
path,
|
||||
projectSlug,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
isForced
|
||||
}: TDeleteDynamicSecretLeaseDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
|
||||
const projectId = project.id;
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||
);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||
|
||||
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||
if (!dynamicSecretLease) throw new BadRequestError({ message: "Dynamic secret lease not found" });
|
||||
|
||||
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||
const decryptedStoredInput = JSON.parse(
|
||||
infisicalSymmetricDecrypt({
|
||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||
tag: dynamicSecretCfg.inputTag,
|
||||
iv: dynamicSecretCfg.inputIV
|
||||
})
|
||||
) as object;
|
||||
|
||||
const revokeResponse = await selectedProvider
|
||||
.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId)
|
||||
.catch(async (err) => {
|
||||
// only propogate this error if forced is false
|
||||
if (!isForced) return { error: err as Error };
|
||||
});
|
||||
|
||||
if ((revokeResponse as { error?: Error })?.error) {
|
||||
const { error } = revokeResponse as { error?: Error };
|
||||
logger.error("Failed to revoke lease", { error: error?.message });
|
||||
const deletedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
|
||||
status: DynamicSecretLeaseStatus.FailedDeletion,
|
||||
statusDetails: error?.message?.slice(0, 255)
|
||||
});
|
||||
return deletedDynamicSecretLease;
|
||||
}
|
||||
|
||||
await dynamicSecretQueueService.unsetLeaseRevocation(dynamicSecretLease.id);
|
||||
const deletedDynamicSecretLease = await dynamicSecretLeaseDAL.deleteById(dynamicSecretLease.id);
|
||||
return deletedDynamicSecretLease;
|
||||
};
|
||||
|
||||
const listLeases = async ({
|
||||
path,
|
||||
name,
|
||||
actor,
|
||||
actorId,
|
||||
projectSlug,
|
||||
actorOrgId,
|
||||
environmentSlug,
|
||||
actorAuthMethod
|
||||
}: TListDynamicSecretLeasesDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
|
||||
const projectId = project.id;
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||
);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||
|
||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||
|
||||
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
||||
return dynamicSecretLeases;
|
||||
};
|
||||
|
||||
const getLeaseDetails = async ({
|
||||
projectSlug,
|
||||
actorOrgId,
|
||||
path,
|
||||
environmentSlug,
|
||||
actor,
|
||||
actorId,
|
||||
leaseId,
|
||||
actorAuthMethod
|
||||
}: TDetailsDynamicSecretLeaseDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
|
||||
const projectId = project.id;
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||
);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||
|
||||
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||
if (!dynamicSecretLease) throw new BadRequestError({ message: "Dynamic secret lease not found" });
|
||||
|
||||
return dynamicSecretLease;
|
||||
};
|
||||
|
||||
return {
|
||||
create,
|
||||
listLeases,
|
||||
revokeLease,
|
||||
renewLease,
|
||||
getLeaseDetails
|
||||
};
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
|
||||
export enum DynamicSecretLeaseStatus {
|
||||
FailedDeletion = "Failed to delete"
|
||||
}
|
||||
|
||||
export type TCreateDynamicSecretLeaseDTO = {
|
||||
name: string;
|
||||
path: string;
|
||||
environmentSlug: string;
|
||||
ttl?: string;
|
||||
projectSlug: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TDetailsDynamicSecretLeaseDTO = {
|
||||
leaseId: string;
|
||||
path: string;
|
||||
environmentSlug: string;
|
||||
projectSlug: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TListDynamicSecretLeasesDTO = {
|
||||
name: string;
|
||||
path: string;
|
||||
environmentSlug: string;
|
||||
projectSlug: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TDeleteDynamicSecretLeaseDTO = {
|
||||
leaseId: string;
|
||||
path: string;
|
||||
environmentSlug: string;
|
||||
projectSlug: string;
|
||||
isForced?: boolean;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TRenewDynamicSecretLeaseDTO = {
|
||||
leaseId: string;
|
||||
path: string;
|
||||
environmentSlug: string;
|
||||
ttl?: string;
|
||||
projectSlug: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
10
backend/src/ee/services/dynamic-secret/dynamic-secret-dal.ts
Normal file
10
backend/src/ee/services/dynamic-secret/dynamic-secret-dal.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TDynamicSecretDALFactory = ReturnType<typeof dynamicSecretDALFactory>;
|
||||
|
||||
export const dynamicSecretDALFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.DynamicSecret);
|
||||
return orm;
|
||||
};
|
341
backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts
Normal file
341
backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts
Normal file
@ -0,0 +1,341 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||
|
||||
import { TDynamicSecretLeaseDALFactory } from "../dynamic-secret-lease/dynamic-secret-lease-dal";
|
||||
import { TDynamicSecretLeaseQueueServiceFactory } from "../dynamic-secret-lease/dynamic-secret-lease-queue";
|
||||
import { TDynamicSecretDALFactory } from "./dynamic-secret-dal";
|
||||
import {
|
||||
DynamicSecretStatus,
|
||||
TCreateDynamicSecretDTO,
|
||||
TDeleteDynamicSecretDTO,
|
||||
TDetailsDynamicSecretDTO,
|
||||
TListDynamicSecretsDTO,
|
||||
TUpdateDynamicSecretDTO
|
||||
} from "./dynamic-secret-types";
|
||||
import { DynamicSecretProviders, TDynamicProviderFns } from "./providers/models";
|
||||
|
||||
type TDynamicSecretServiceFactoryDep = {
|
||||
dynamicSecretDAL: TDynamicSecretDALFactory;
|
||||
dynamicSecretLeaseDAL: Pick<TDynamicSecretLeaseDALFactory, "find">;
|
||||
dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>;
|
||||
dynamicSecretQueueService: Pick<
|
||||
TDynamicSecretLeaseQueueServiceFactory,
|
||||
"pruneDynamicSecret" | "unsetLeaseRevocation"
|
||||
>;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
};
|
||||
|
||||
export type TDynamicSecretServiceFactory = ReturnType<typeof dynamicSecretServiceFactory>;
|
||||
|
||||
export const dynamicSecretServiceFactory = ({
|
||||
dynamicSecretDAL,
|
||||
dynamicSecretLeaseDAL,
|
||||
licenseService,
|
||||
folderDAL,
|
||||
dynamicSecretProviders,
|
||||
permissionService,
|
||||
dynamicSecretQueueService,
|
||||
projectDAL
|
||||
}: TDynamicSecretServiceFactoryDep) => {
|
||||
const create = async ({
|
||||
path,
|
||||
actor,
|
||||
name,
|
||||
actorId,
|
||||
maxTTL,
|
||||
provider,
|
||||
environmentSlug,
|
||||
projectSlug,
|
||||
actorOrgId,
|
||||
defaultTTL,
|
||||
actorAuthMethod
|
||||
}: TCreateDynamicSecretDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
|
||||
const projectId = project.id;
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||
);
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
if (!plan?.dynamicSecret) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to create dynamic secret due to plan restriction. Upgrade plan to create dynamic secret."
|
||||
});
|
||||
}
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||
|
||||
const existingDynamicSecret = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||
if (existingDynamicSecret)
|
||||
throw new BadRequestError({ message: "Provided dynamic secret already exist under the folder" });
|
||||
|
||||
const selectedProvider = dynamicSecretProviders[provider.type];
|
||||
const inputs = await selectedProvider.validateProviderInputs(provider.inputs);
|
||||
|
||||
const isConnected = await selectedProvider.validateConnection(provider.inputs);
|
||||
if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" });
|
||||
|
||||
const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(inputs));
|
||||
const dynamicSecretCfg = await dynamicSecretDAL.create({
|
||||
type: provider.type,
|
||||
version: 1,
|
||||
inputIV: encryptedInput.iv,
|
||||
inputTag: encryptedInput.tag,
|
||||
inputCiphertext: encryptedInput.ciphertext,
|
||||
algorithm: encryptedInput.algorithm,
|
||||
keyEncoding: encryptedInput.encoding,
|
||||
maxTTL,
|
||||
defaultTTL,
|
||||
folderId: folder.id,
|
||||
name
|
||||
});
|
||||
return dynamicSecretCfg;
|
||||
};
|
||||
|
||||
const updateByName = async ({
|
||||
name,
|
||||
maxTTL,
|
||||
defaultTTL,
|
||||
inputs,
|
||||
environmentSlug,
|
||||
projectSlug,
|
||||
path,
|
||||
actor,
|
||||
actorId,
|
||||
newName,
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
}: TUpdateDynamicSecretDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
|
||||
const projectId = project.id;
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||
);
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
if (!plan?.dynamicSecret) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to update dynamic secret due to plan restriction. Upgrade plan to create dynamic secret."
|
||||
});
|
||||
}
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||
|
||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||
|
||||
if (newName) {
|
||||
const existingDynamicSecret = await dynamicSecretDAL.findOne({ name: newName, folderId: folder.id });
|
||||
if (existingDynamicSecret)
|
||||
throw new BadRequestError({ message: "Provided dynamic secret already exist under the folder" });
|
||||
}
|
||||
|
||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||
const decryptedStoredInput = JSON.parse(
|
||||
infisicalSymmetricDecrypt({
|
||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||
tag: dynamicSecretCfg.inputTag,
|
||||
iv: dynamicSecretCfg.inputIV
|
||||
})
|
||||
) as object;
|
||||
const newInput = { ...decryptedStoredInput, ...(inputs || {}) };
|
||||
const updatedInput = await selectedProvider.validateProviderInputs(newInput);
|
||||
|
||||
const isConnected = await selectedProvider.validateConnection(newInput);
|
||||
if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" });
|
||||
|
||||
const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(updatedInput));
|
||||
const updatedDynamicCfg = await dynamicSecretDAL.updateById(dynamicSecretCfg.id, {
|
||||
inputIV: encryptedInput.iv,
|
||||
inputTag: encryptedInput.tag,
|
||||
inputCiphertext: encryptedInput.ciphertext,
|
||||
algorithm: encryptedInput.algorithm,
|
||||
keyEncoding: encryptedInput.encoding,
|
||||
maxTTL,
|
||||
defaultTTL,
|
||||
name: newName ?? name,
|
||||
status: null,
|
||||
statusDetails: null
|
||||
});
|
||||
|
||||
return updatedDynamicCfg;
|
||||
};
|
||||
|
||||
const deleteByName = async ({
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actorId,
|
||||
actor,
|
||||
projectSlug,
|
||||
name,
|
||||
path,
|
||||
environmentSlug,
|
||||
isForced
|
||||
}: TDeleteDynamicSecretDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
|
||||
const projectId = project.id;
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||
);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||
|
||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||
|
||||
const leases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
||||
// when not forced we check with the external system to first remove the things
|
||||
// we introduce a forced concept because consider the external lease got deleted by some other external like a human or another system
|
||||
// this allows user to clean up it from infisical
|
||||
if (isForced) {
|
||||
// clear all queues for lease revocations
|
||||
await Promise.all(leases.map(({ id: leaseId }) => dynamicSecretQueueService.unsetLeaseRevocation(leaseId)));
|
||||
|
||||
const deletedDynamicSecretCfg = await dynamicSecretDAL.deleteById(dynamicSecretCfg.id);
|
||||
return deletedDynamicSecretCfg;
|
||||
}
|
||||
// if leases exist we should flag it as deleting and then remove leases in background
|
||||
// then delete the main one
|
||||
if (leases.length) {
|
||||
const updatedDynamicSecretCfg = await dynamicSecretDAL.updateById(dynamicSecretCfg.id, {
|
||||
status: DynamicSecretStatus.Deleting
|
||||
});
|
||||
await dynamicSecretQueueService.pruneDynamicSecret(updatedDynamicSecretCfg.id);
|
||||
return updatedDynamicSecretCfg;
|
||||
}
|
||||
// if no leases just delete the config
|
||||
const deletedDynamicSecretCfg = await dynamicSecretDAL.deleteById(dynamicSecretCfg.id);
|
||||
return deletedDynamicSecretCfg;
|
||||
};
|
||||
|
||||
const getDetails = async ({
|
||||
name,
|
||||
projectSlug,
|
||||
path,
|
||||
environmentSlug,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actorId,
|
||||
actor
|
||||
}: TDetailsDynamicSecretDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
|
||||
const projectId = project.id;
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||
);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||
|
||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||
const decryptedStoredInput = JSON.parse(
|
||||
infisicalSymmetricDecrypt({
|
||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||
tag: dynamicSecretCfg.inputTag,
|
||||
iv: dynamicSecretCfg.inputIV
|
||||
})
|
||||
) as object;
|
||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||
const providerInputs = (await selectedProvider.validateProviderInputs(decryptedStoredInput)) as object;
|
||||
return { ...dynamicSecretCfg, inputs: providerInputs };
|
||||
};
|
||||
|
||||
const list = async ({
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actorId,
|
||||
actor,
|
||||
projectSlug,
|
||||
path,
|
||||
environmentSlug
|
||||
}: TListDynamicSecretsDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
|
||||
const projectId = project.id;
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||
);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||
|
||||
const dynamicSecretCfg = await dynamicSecretDAL.find({ folderId: folder.id });
|
||||
return dynamicSecretCfg;
|
||||
};
|
||||
|
||||
return {
|
||||
create,
|
||||
updateByName,
|
||||
deleteByName,
|
||||
getDetails,
|
||||
list
|
||||
};
|
||||
};
|
@ -0,0 +1,54 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
|
||||
import { DynamicSecretProviderSchema } from "./providers/models";
|
||||
|
||||
// various status for dynamic secret that happens in background
|
||||
export enum DynamicSecretStatus {
|
||||
Deleting = "Revocation in process",
|
||||
FailedDeletion = "Failed to delete"
|
||||
}
|
||||
|
||||
type TProvider = z.infer<typeof DynamicSecretProviderSchema>;
|
||||
export type TCreateDynamicSecretDTO = {
|
||||
provider: TProvider;
|
||||
defaultTTL: string;
|
||||
maxTTL?: string | null;
|
||||
path: string;
|
||||
environmentSlug: string;
|
||||
name: string;
|
||||
projectSlug: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateDynamicSecretDTO = {
|
||||
name: string;
|
||||
newName?: string;
|
||||
defaultTTL?: string;
|
||||
maxTTL?: string | null;
|
||||
path: string;
|
||||
environmentSlug: string;
|
||||
inputs?: TProvider["inputs"];
|
||||
projectSlug: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TDeleteDynamicSecretDTO = {
|
||||
name: string;
|
||||
path: string;
|
||||
environmentSlug: string;
|
||||
projectSlug: string;
|
||||
isForced?: boolean;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TDetailsDynamicSecretDTO = {
|
||||
name: string;
|
||||
path: string;
|
||||
environmentSlug: string;
|
||||
projectSlug: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TListDynamicSecretsDTO = {
|
||||
path: string;
|
||||
environmentSlug: string;
|
||||
projectSlug: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
@ -0,0 +1,6 @@
|
||||
import { DynamicSecretProviders } from "./models";
|
||||
import { SqlDatabaseProvider } from "./sql-database";
|
||||
|
||||
export const buildDynamicSecretProviders = () => ({
|
||||
[DynamicSecretProviders.SqlDatabase]: SqlDatabaseProvider()
|
||||
});
|
34
backend/src/ee/services/dynamic-secret/providers/models.ts
Normal file
34
backend/src/ee/services/dynamic-secret/providers/models.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export enum SqlProviders {
|
||||
Postgres = "postgres"
|
||||
}
|
||||
|
||||
export const DynamicSecretSqlDBSchema = z.object({
|
||||
client: z.nativeEnum(SqlProviders),
|
||||
host: z.string().toLowerCase(),
|
||||
port: z.number(),
|
||||
database: z.string(),
|
||||
username: z.string(),
|
||||
password: z.string(),
|
||||
creationStatement: z.string(),
|
||||
revocationStatement: z.string(),
|
||||
renewStatement: z.string(),
|
||||
ca: z.string().optional()
|
||||
});
|
||||
|
||||
export enum DynamicSecretProviders {
|
||||
SqlDatabase = "sql-database"
|
||||
}
|
||||
|
||||
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal(DynamicSecretProviders.SqlDatabase), inputs: DynamicSecretSqlDBSchema })
|
||||
]);
|
||||
|
||||
export type TDynamicProviderFns = {
|
||||
create: (inputs: unknown, expireAt: number) => Promise<{ entityId: string; data: unknown }>;
|
||||
validateConnection: (inputs: unknown) => Promise<boolean>;
|
||||
validateProviderInputs: (inputs: object) => Promise<unknown>;
|
||||
revoke: (inputs: unknown, entityId: string) => Promise<{ entityId: string }>;
|
||||
renew: (inputs: unknown, entityId: string, expireAt: number) => Promise<{ entityId: string }>;
|
||||
};
|
123
backend/src/ee/services/dynamic-secret/providers/sql-database.ts
Normal file
123
backend/src/ee/services/dynamic-secret/providers/sql-database.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import handlebars from "handlebars";
|
||||
import knex from "knex";
|
||||
import { customAlphabet } from "nanoid";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { getDbConnectionHost } from "@app/lib/knex";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { DynamicSecretSqlDBSchema, TDynamicProviderFns } from "./models";
|
||||
|
||||
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
||||
|
||||
const generatePassword = (size?: number) => {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
||||
return customAlphabet(charset, 48)(size);
|
||||
};
|
||||
|
||||
export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
||||
const validateProviderInputs = async (inputs: unknown) => {
|
||||
const appCfg = getConfig();
|
||||
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);
|
||||
|
||||
const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs);
|
||||
if (
|
||||
// localhost
|
||||
providerInputs.host === "localhost" ||
|
||||
providerInputs.host === "127.0.0.1" ||
|
||||
// database infisical uses
|
||||
dbHost === providerInputs.host ||
|
||||
// internal ips
|
||||
providerInputs.host === "host.docker.internal" ||
|
||||
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
|
||||
providerInputs.host.match(/^192\.168\.\d+\.\d+/)
|
||||
)
|
||||
throw new BadRequestError({ message: "Invalid db host" });
|
||||
return providerInputs;
|
||||
};
|
||||
|
||||
const getClient = async (providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>) => {
|
||||
const ssl = providerInputs.ca ? { rejectUnauthorized: false, ca: providerInputs.ca } : undefined;
|
||||
const db = knex({
|
||||
client: providerInputs.client,
|
||||
connection: {
|
||||
database: providerInputs.database,
|
||||
port: providerInputs.port,
|
||||
host: providerInputs.host,
|
||||
user: providerInputs.username,
|
||||
password: providerInputs.password,
|
||||
connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT,
|
||||
ssl,
|
||||
pool: { min: 0, max: 1 }
|
||||
}
|
||||
});
|
||||
return db;
|
||||
};
|
||||
|
||||
const validateConnection = async (inputs: unknown) => {
|
||||
const providerInputs = await validateProviderInputs(inputs);
|
||||
const db = await getClient(providerInputs);
|
||||
const isConnected = await db
|
||||
.raw("SELECT NOW()")
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
await db.destroy();
|
||||
return isConnected;
|
||||
};
|
||||
|
||||
const create = async (inputs: unknown, expireAt: number) => {
|
||||
const providerInputs = await validateProviderInputs(inputs);
|
||||
const db = await getClient(providerInputs);
|
||||
|
||||
const username = alphaNumericNanoId(32);
|
||||
const password = generatePassword();
|
||||
const expiration = new Date(expireAt).toISOString();
|
||||
|
||||
const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({
|
||||
username,
|
||||
password,
|
||||
expiration
|
||||
});
|
||||
|
||||
await db.raw(creationStatement.toString());
|
||||
await db.destroy();
|
||||
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
|
||||
};
|
||||
|
||||
const revoke = async (inputs: unknown, entityId: string) => {
|
||||
const providerInputs = await validateProviderInputs(inputs);
|
||||
const db = await getClient(providerInputs);
|
||||
|
||||
const username = entityId;
|
||||
|
||||
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username });
|
||||
await db.raw(revokeStatement);
|
||||
|
||||
await db.destroy();
|
||||
return { entityId: username };
|
||||
};
|
||||
|
||||
const renew = async (inputs: unknown, entityId: string, expireAt: number) => {
|
||||
const providerInputs = await validateProviderInputs(inputs);
|
||||
const db = await getClient(providerInputs);
|
||||
|
||||
const username = entityId;
|
||||
const expiration = new Date(expireAt).toISOString();
|
||||
|
||||
const renewStatement = handlebars.compile(providerInputs.renewStatement)({ username, expiration });
|
||||
await db.raw(renewStatement);
|
||||
|
||||
await db.destroy();
|
||||
return { entityId: username };
|
||||
};
|
||||
|
||||
return {
|
||||
validateProviderInputs,
|
||||
validateConnection,
|
||||
create,
|
||||
revoke,
|
||||
renew
|
||||
};
|
||||
};
|
12
backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-dal.ts
Normal file
12
backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-dal.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TIdentityProjectAdditionalPrivilegeDALFactory = ReturnType<
|
||||
typeof identityProjectAdditionalPrivilegeDALFactory
|
||||
>;
|
||||
|
||||
export const identityProjectAdditionalPrivilegeDALFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.IdentityProjectAdditionalPrivilege);
|
||||
return orm;
|
||||
};
|
297
backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts
Normal file
297
backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts
Normal file
@ -0,0 +1,297 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import ms from "ms";
|
||||
|
||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||
import { TIdentityProjectAdditionalPrivilegeDALFactory } from "./identity-project-additional-privilege-dal";
|
||||
import {
|
||||
IdentityProjectAdditionalPrivilegeTemporaryMode,
|
||||
TCreateIdentityPrivilegeDTO,
|
||||
TDeleteIdentityPrivilegeDTO,
|
||||
TGetIdentityPrivilegeDetailsDTO,
|
||||
TListIdentityPrivilegesDTO,
|
||||
TUpdateIdentityPrivilegeDTO
|
||||
} from "./identity-project-additional-privilege-types";
|
||||
|
||||
type TIdentityProjectAdditionalPrivilegeServiceFactoryDep = {
|
||||
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeDALFactory;
|
||||
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
};
|
||||
|
||||
export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
|
||||
typeof identityProjectAdditionalPrivilegeServiceFactory
|
||||
>;
|
||||
|
||||
export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
identityProjectAdditionalPrivilegeDAL,
|
||||
identityProjectDAL,
|
||||
permissionService,
|
||||
projectDAL
|
||||
}: TIdentityProjectAdditionalPrivilegeServiceFactoryDep) => {
|
||||
const create = async ({
|
||||
slug,
|
||||
actor,
|
||||
actorId,
|
||||
identityId,
|
||||
projectSlug,
|
||||
permissions: customPermission,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
...dto
|
||||
}: TCreateIdentityPrivilegeDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
const projectId = project.id;
|
||||
|
||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||
if (!identityProjectMembership)
|
||||
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||
ActorType.IDENTITY,
|
||||
identityId,
|
||||
identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||
if (!hasRequiredPriviledges)
|
||||
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||
|
||||
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||
slug,
|
||||
projectMembershipId: identityProjectMembership.id
|
||||
});
|
||||
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||
|
||||
if (!dto.isTemporary) {
|
||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||
projectMembershipId: identityProjectMembership.id,
|
||||
slug,
|
||||
permissions: customPermission
|
||||
});
|
||||
return additionalPrivilege;
|
||||
}
|
||||
|
||||
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||
projectMembershipId: identityProjectMembership.id,
|
||||
slug,
|
||||
permissions: customPermission,
|
||||
isTemporary: true,
|
||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
|
||||
temporaryRange: dto.temporaryRange,
|
||||
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||
});
|
||||
return additionalPrivilege;
|
||||
};
|
||||
|
||||
const updateBySlug = async ({
|
||||
projectSlug,
|
||||
slug,
|
||||
identityId,
|
||||
data,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod
|
||||
}: TUpdateIdentityPrivilegeDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
const projectId = project.id;
|
||||
|
||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||
if (!identityProjectMembership)
|
||||
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||
ActorType.IDENTITY,
|
||||
identityProjectMembership.identityId,
|
||||
identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||
if (!hasRequiredPriviledges)
|
||||
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||
|
||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||
slug,
|
||||
projectMembershipId: identityProjectMembership.id
|
||||
});
|
||||
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
|
||||
if (data?.slug) {
|
||||
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||
slug: data.slug,
|
||||
projectMembershipId: identityProjectMembership.id
|
||||
});
|
||||
if (existingSlug && existingSlug.id !== identityPrivilege.id)
|
||||
throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||
}
|
||||
|
||||
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
||||
if (isTemporary) {
|
||||
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
|
||||
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
|
||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||
...data,
|
||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||
});
|
||||
return additionalPrivilege;
|
||||
}
|
||||
|
||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||
...data,
|
||||
isTemporary: false,
|
||||
temporaryAccessStartTime: null,
|
||||
temporaryAccessEndTime: null,
|
||||
temporaryRange: null,
|
||||
temporaryMode: null
|
||||
});
|
||||
return additionalPrivilege;
|
||||
};
|
||||
|
||||
const deleteBySlug = async ({
|
||||
actorId,
|
||||
slug,
|
||||
identityId,
|
||||
projectSlug,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
}: TDeleteIdentityPrivilegeDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
const projectId = project.id;
|
||||
|
||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||
if (!identityProjectMembership)
|
||||
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||
ActorType.IDENTITY,
|
||||
identityProjectMembership.identityId,
|
||||
identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||
if (!hasRequiredPriviledges)
|
||||
throw new ForbiddenRequestError({ message: "Failed to edit more privileged identity" });
|
||||
|
||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||
slug,
|
||||
projectMembershipId: identityProjectMembership.id
|
||||
});
|
||||
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
|
||||
|
||||
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
||||
return deletedPrivilege;
|
||||
};
|
||||
|
||||
const getPrivilegeDetailsBySlug = async ({
|
||||
projectSlug,
|
||||
identityId,
|
||||
slug,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod
|
||||
}: TGetIdentityPrivilegeDetailsDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
const projectId = project.id;
|
||||
|
||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||
if (!identityProjectMembership)
|
||||
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||
|
||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||
slug,
|
||||
projectMembershipId: identityProjectMembership.id
|
||||
});
|
||||
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
|
||||
|
||||
return identityPrivilege;
|
||||
};
|
||||
|
||||
const listIdentityProjectPrivileges = async ({
|
||||
identityId,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
projectSlug
|
||||
}: TListIdentityPrivilegesDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
const projectId = project.id;
|
||||
|
||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||
if (!identityProjectMembership)
|
||||
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||
|
||||
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find({
|
||||
projectMembershipId: identityProjectMembership.id
|
||||
});
|
||||
return identityPrivileges;
|
||||
};
|
||||
|
||||
return {
|
||||
create,
|
||||
updateBySlug,
|
||||
deleteBySlug,
|
||||
getPrivilegeDetailsBySlug,
|
||||
listIdentityProjectPrivileges
|
||||
};
|
||||
};
|
54
backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types.ts
Normal file
54
backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
|
||||
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
||||
Relative = "relative"
|
||||
}
|
||||
|
||||
export type TCreateIdentityPrivilegeDTO = {
|
||||
permissions: unknown;
|
||||
identityId: string;
|
||||
projectSlug: string;
|
||||
slug: string;
|
||||
} & (
|
||||
| {
|
||||
isTemporary: false;
|
||||
}
|
||||
| {
|
||||
isTemporary: true;
|
||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}
|
||||
) &
|
||||
Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateIdentityPrivilegeDTO = { slug: string; identityId: string; projectSlug: string } & Omit<
|
||||
TProjectPermission,
|
||||
"projectId"
|
||||
> & {
|
||||
data: Partial<{
|
||||
permissions: unknown;
|
||||
slug: string;
|
||||
isTemporary: boolean;
|
||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type TDeleteIdentityPrivilegeDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
slug: string;
|
||||
identityId: string;
|
||||
projectSlug: string;
|
||||
};
|
||||
|
||||
export type TGetIdentityPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
slug: string;
|
||||
identityId: string;
|
||||
projectSlug: string;
|
||||
};
|
||||
|
||||
export type TListIdentityPrivilegesDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
identityId: string;
|
||||
projectSlug: string;
|
||||
};
|
@ -55,6 +55,7 @@ export const ldapConfigServiceFactory = ({
|
||||
actorId,
|
||||
orgId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
isActive,
|
||||
url,
|
||||
bindDN,
|
||||
@ -62,7 +63,7 @@ export const ldapConfigServiceFactory = ({
|
||||
searchBase,
|
||||
caCert
|
||||
}: TCreateLdapCfgDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
|
||||
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
@ -149,13 +150,14 @@ export const ldapConfigServiceFactory = ({
|
||||
orgId,
|
||||
actorOrgId,
|
||||
isActive,
|
||||
actorAuthMethod,
|
||||
url,
|
||||
bindDN,
|
||||
bindPass,
|
||||
searchBase,
|
||||
caCert
|
||||
}: TUpdateLdapCfgDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
||||
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
@ -180,21 +182,21 @@ export const ldapConfigServiceFactory = ({
|
||||
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
|
||||
});
|
||||
|
||||
if (bindDN) {
|
||||
if (bindDN !== undefined) {
|
||||
const { ciphertext: encryptedBindDN, iv: bindDNIV, tag: bindDNTag } = encryptSymmetric(bindDN, key);
|
||||
updateQuery.encryptedBindDN = encryptedBindDN;
|
||||
updateQuery.bindDNIV = bindDNIV;
|
||||
updateQuery.bindDNTag = bindDNTag;
|
||||
}
|
||||
|
||||
if (bindPass) {
|
||||
if (bindPass !== undefined) {
|
||||
const { ciphertext: encryptedBindPass, iv: bindPassIV, tag: bindPassTag } = encryptSymmetric(bindPass, key);
|
||||
updateQuery.encryptedBindPass = encryptedBindPass;
|
||||
updateQuery.bindPassIV = bindPassIV;
|
||||
updateQuery.bindPassTag = bindPassTag;
|
||||
}
|
||||
|
||||
if (caCert) {
|
||||
if (caCert !== undefined) {
|
||||
const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key);
|
||||
updateQuery.encryptedCACert = encryptedCACert;
|
||||
updateQuery.caCertIV = caCertIV;
|
||||
@ -274,8 +276,14 @@ export const ldapConfigServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const getLdapCfgWithPermissionCheck = async ({ actor, actorId, orgId, actorOrgId }: TOrgPermission) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const getLdapCfgWithPermissionCheck = async ({
|
||||
actor,
|
||||
actorId,
|
||||
orgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TOrgPermission) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
|
||||
return getLdapCfg({
|
||||
orgId
|
||||
|
@ -15,6 +15,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
||||
membersUsed: 0,
|
||||
environmentLimit: null,
|
||||
environmentsUsed: 0,
|
||||
dynamicSecret: false,
|
||||
secretVersioning: true,
|
||||
pitRecovery: false,
|
||||
ipAllowlisting: false,
|
||||
|
@ -8,6 +8,7 @@ import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { verifyOfflineLicense } from "@app/lib/crypto";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
@ -26,6 +27,7 @@ import {
|
||||
TFeatureSet,
|
||||
TGetOrgBillInfoDTO,
|
||||
TGetOrgTaxIdDTO,
|
||||
TOfflineLicenseContents,
|
||||
TOrgInvoiceDTO,
|
||||
TOrgLicensesDTO,
|
||||
TOrgPlanDTO,
|
||||
@ -96,6 +98,36 @@ export const licenseServiceFactory = ({
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (appCfg.LICENSE_KEY_OFFLINE) {
|
||||
let isValidOfflineLicense = true;
|
||||
const contents: TOfflineLicenseContents = JSON.parse(
|
||||
Buffer.from(appCfg.LICENSE_KEY_OFFLINE, "base64").toString("utf8")
|
||||
);
|
||||
const isVerified = await verifyOfflineLicense(JSON.stringify(contents.license), contents.signature);
|
||||
|
||||
if (!isVerified) {
|
||||
isValidOfflineLicense = false;
|
||||
logger.warn(`Infisical EE offline license verification failed`);
|
||||
}
|
||||
|
||||
if (contents.license.terminatesAt) {
|
||||
const terminationDate = new Date(contents.license.terminatesAt);
|
||||
if (terminationDate < new Date()) {
|
||||
isValidOfflineLicense = false;
|
||||
logger.warn(`Infisical EE offline license has expired`);
|
||||
}
|
||||
}
|
||||
|
||||
if (isValidOfflineLicense) {
|
||||
onPremFeatures = contents.license.features;
|
||||
instanceType = InstanceType.EnterpriseOnPrem;
|
||||
logger.info(`Instance type: ${InstanceType.EnterpriseOnPrem}`);
|
||||
isValidLicense = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// this means this is self hosted oss version
|
||||
// else it would reach catch statement
|
||||
isValidLicense = true;
|
||||
@ -192,9 +224,10 @@ export const licenseServiceFactory = ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
billingCycle
|
||||
}: TOrgPlansTableDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
const { data } = await licenseServerCloudApi.request.get(
|
||||
`/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
|
||||
@ -202,15 +235,22 @@ export const licenseServiceFactory = ({
|
||||
return data;
|
||||
};
|
||||
|
||||
const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, projectId }: TOrgPlanDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, actorAuthMethod, projectId }: TOrgPlanDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
const plan = await getPlan(orgId, projectId);
|
||||
return plan;
|
||||
};
|
||||
|
||||
const startOrgTrial = async ({ orgId, actorId, actor, actorOrgId, success_url }: TStartOrgTrialDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const startOrgTrial = async ({
|
||||
orgId,
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
success_url
|
||||
}: TStartOrgTrialDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
||||
|
||||
@ -231,8 +271,14 @@ export const licenseServiceFactory = ({
|
||||
return { url };
|
||||
};
|
||||
|
||||
const createOrganizationPortalSession = async ({ orgId, actorId, actor, actorOrgId }: TCreateOrgPortalSession) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const createOrganizationPortalSession = async ({
|
||||
orgId,
|
||||
actorId,
|
||||
actor,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TCreateOrgPortalSession) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
||||
|
||||
@ -278,8 +324,8 @@ export const licenseServiceFactory = ({
|
||||
return { url };
|
||||
};
|
||||
|
||||
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
@ -295,8 +341,8 @@ export const licenseServiceFactory = ({
|
||||
};
|
||||
|
||||
// returns org current plan feature table
|
||||
const getOrgPlanTable = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const getOrgPlanTable = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
@ -311,8 +357,8 @@ export const licenseServiceFactory = ({
|
||||
return data;
|
||||
};
|
||||
|
||||
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
@ -332,11 +378,12 @@ export const licenseServiceFactory = ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
orgId,
|
||||
name,
|
||||
email
|
||||
}: TUpdateOrgBillingDetailsDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
@ -355,8 +402,8 @@ export const licenseServiceFactory = ({
|
||||
return data;
|
||||
};
|
||||
|
||||
const getOrgPmtMethods = async ({ orgId, actor, actorId, actorOrgId }: TOrgPmtMethodsDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const getOrgPmtMethods = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPmtMethodsDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
@ -378,11 +425,12 @@ export const licenseServiceFactory = ({
|
||||
orgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
success_url,
|
||||
cancel_url
|
||||
}: TAddOrgPmtMethodDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
@ -403,8 +451,15 @@ export const licenseServiceFactory = ({
|
||||
return { url };
|
||||
};
|
||||
|
||||
const delOrgPmtMethods = async ({ actorId, actor, actorOrgId, orgId, pmtMethodId }: TDelOrgPmtMethodDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const delOrgPmtMethods = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
orgId,
|
||||
pmtMethodId
|
||||
}: TDelOrgPmtMethodDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
@ -420,8 +475,8 @@ export const licenseServiceFactory = ({
|
||||
return data;
|
||||
};
|
||||
|
||||
const getOrgTaxIds = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgTaxIdDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
@ -438,8 +493,8 @@ export const licenseServiceFactory = ({
|
||||
return taxIds;
|
||||
};
|
||||
|
||||
const addOrgTaxId = async ({ actorId, actor, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
@ -459,8 +514,8 @@ export const licenseServiceFactory = ({
|
||||
return data;
|
||||
};
|
||||
|
||||
const delOrgTaxId = async ({ orgId, actor, actorId, actorOrgId, taxId }: TDelOrgTaxIdDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
@ -476,8 +531,8 @@ export const licenseServiceFactory = ({
|
||||
return data;
|
||||
};
|
||||
|
||||
const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, orgId }: TOrgInvoiceDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, actorAuthMethod, orgId }: TOrgInvoiceDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
@ -493,8 +548,8 @@ export const licenseServiceFactory = ({
|
||||
return invoices;
|
||||
};
|
||||
|
||||
const getOrgLicenses = async ({ orgId, actor, actorId, actorOrgId }: TOrgLicensesDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const getOrgLicenses = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgLicensesDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
|
@ -6,12 +6,28 @@ export enum InstanceType {
|
||||
Cloud = "cloud"
|
||||
}
|
||||
|
||||
export type TOfflineLicenseContents = {
|
||||
license: TOfflineLicense;
|
||||
signature: string;
|
||||
};
|
||||
|
||||
export type TOfflineLicense = {
|
||||
issuedTo: string;
|
||||
licenseId: string;
|
||||
customerId: string | null;
|
||||
issuedAt: string;
|
||||
expiresAt: string | null;
|
||||
terminatesAt: string | null;
|
||||
features: TFeatureSet;
|
||||
};
|
||||
|
||||
export type TFeatureSet = {
|
||||
_id: null;
|
||||
slug: null;
|
||||
tier: -1;
|
||||
workspaceLimit: null;
|
||||
workspacesUsed: 0;
|
||||
dynamicSecret: false;
|
||||
memberLimit: null;
|
||||
membersUsed: 0;
|
||||
environmentLimit: null;
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { IdentityProjectMembershipRoleSchema, ProjectUserMembershipRolesSchema, TableName } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { selectAllTableCols } from "@app/lib/knex";
|
||||
import { selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||
|
||||
export type TPermissionDALFactory = ReturnType<typeof permissionDALFactory>;
|
||||
|
||||
@ -43,21 +45,119 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
|
||||
const getProjectPermission = async (userId: string, projectId: string) => {
|
||||
try {
|
||||
const membership = await db(TableName.ProjectMembership)
|
||||
.leftJoin(TableName.ProjectRoles, `${TableName.ProjectMembership}.roleId`, `${TableName.ProjectRoles}.id`)
|
||||
const docs = await db(TableName.ProjectMembership)
|
||||
.join(
|
||||
TableName.ProjectUserMembershipRole,
|
||||
`${TableName.ProjectUserMembershipRole}.projectMembershipId`,
|
||||
`${TableName.ProjectMembership}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.ProjectRoles,
|
||||
`${TableName.ProjectUserMembershipRole}.customRoleId`,
|
||||
`${TableName.ProjectRoles}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.ProjectUserAdditionalPrivilege,
|
||||
`${TableName.ProjectUserAdditionalPrivilege}.projectMembershipId`,
|
||||
`${TableName.ProjectMembership}.id`
|
||||
)
|
||||
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
||||
.where("userId", userId)
|
||||
.where(`${TableName.ProjectMembership}.projectId`, projectId)
|
||||
.select(selectAllTableCols(TableName.ProjectMembership))
|
||||
.select(selectAllTableCols(TableName.ProjectUserMembershipRole))
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"),
|
||||
// TODO(roll-forward-migration): remove this field when we drop this in next migration after a week
|
||||
db.ref("role").withSchema(TableName.ProjectMembership).as("oldRoleField"),
|
||||
db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"),
|
||||
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
||||
db.ref("orgId").withSchema(TableName.Project)
|
||||
)
|
||||
.select("permissions")
|
||||
.first();
|
||||
db.ref("orgId").withSchema(TableName.Project),
|
||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
|
||||
db.ref("permissions").withSchema(TableName.ProjectRoles),
|
||||
db.ref("id").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApId"),
|
||||
db.ref("permissions").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApPermissions"),
|
||||
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryMode"),
|
||||
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApIsTemporary"),
|
||||
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryRange"),
|
||||
db
|
||||
.ref("temporaryAccessStartTime")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("userApTemporaryAccessStartTime"),
|
||||
db
|
||||
.ref("temporaryAccessEndTime")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("userApTemporaryAccessEndTime")
|
||||
);
|
||||
|
||||
return membership;
|
||||
const permission = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "membershipId",
|
||||
parentMapper: ({
|
||||
orgId,
|
||||
orgAuthEnforced,
|
||||
membershipId,
|
||||
membershipCreatedAt,
|
||||
membershipUpdatedAt,
|
||||
oldRoleField
|
||||
}) => ({
|
||||
orgId,
|
||||
orgAuthEnforced,
|
||||
userId,
|
||||
role: oldRoleField,
|
||||
id: membershipId,
|
||||
projectId,
|
||||
createdAt: membershipCreatedAt,
|
||||
updatedAt: membershipUpdatedAt
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "id",
|
||||
label: "roles" as const,
|
||||
mapper: (data) =>
|
||||
ProjectUserMembershipRolesSchema.extend({
|
||||
permissions: z.unknown(),
|
||||
customRoleSlug: z.string().optional().nullable()
|
||||
}).parse(data)
|
||||
},
|
||||
{
|
||||
key: "userApId",
|
||||
label: "additionalPrivileges" as const,
|
||||
mapper: ({
|
||||
userApId,
|
||||
userApPermissions,
|
||||
userApIsTemporary,
|
||||
userApTemporaryMode,
|
||||
userApTemporaryRange,
|
||||
userApTemporaryAccessEndTime,
|
||||
userApTemporaryAccessStartTime
|
||||
}) => ({
|
||||
id: userApId,
|
||||
permissions: userApPermissions,
|
||||
temporaryRange: userApTemporaryRange,
|
||||
temporaryMode: userApTemporaryMode,
|
||||
temporaryAccessEndTime: userApTemporaryAccessEndTime,
|
||||
temporaryAccessStartTime: userApTemporaryAccessStartTime,
|
||||
isTemporary: userApIsTemporary
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!permission?.[0]) return undefined;
|
||||
// when introducting cron mode change it here
|
||||
const activeRoles = permission?.[0]?.roles?.filter(
|
||||
({ isTemporary, temporaryAccessEndTime }) =>
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
);
|
||||
|
||||
const activeAdditionalPrivileges = permission?.[0]?.additionalPrivileges?.filter(
|
||||
({ isTemporary, temporaryAccessEndTime }) =>
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
);
|
||||
|
||||
return { ...permission[0], roles: activeRoles, additionalPrivileges: activeAdditionalPrivileges };
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "GetProjectPermission" });
|
||||
}
|
||||
@ -65,18 +165,121 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
|
||||
const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
|
||||
try {
|
||||
const membership = await db(TableName.IdentityProjectMembership)
|
||||
const docs = await db(TableName.IdentityProjectMembership)
|
||||
.join(
|
||||
TableName.IdentityProjectMembershipRole,
|
||||
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,
|
||||
`${TableName.IdentityProjectMembership}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.ProjectRoles,
|
||||
`${TableName.IdentityProjectMembership}.roleId`,
|
||||
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
|
||||
`${TableName.ProjectRoles}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.IdentityProjectAdditionalPrivilege,
|
||||
`${TableName.IdentityProjectAdditionalPrivilege}.projectMembershipId`,
|
||||
`${TableName.IdentityProjectMembership}.id`
|
||||
)
|
||||
.join(
|
||||
// Join the Project table to later select orgId
|
||||
TableName.Project,
|
||||
`${TableName.IdentityProjectMembership}.projectId`,
|
||||
`${TableName.Project}.id`
|
||||
)
|
||||
.where("identityId", identityId)
|
||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||
.select(selectAllTableCols(TableName.IdentityProjectMembership))
|
||||
.select("permissions")
|
||||
.first();
|
||||
return membership;
|
||||
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"),
|
||||
db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project
|
||||
db.ref("role").withSchema(TableName.IdentityProjectMembership).as("oldRoleField"),
|
||||
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
|
||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
|
||||
db.ref("permissions").withSchema(TableName.ProjectRoles),
|
||||
db.ref("id").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApId"),
|
||||
db.ref("permissions").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApPermissions"),
|
||||
db
|
||||
.ref("temporaryMode")
|
||||
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||
.as("identityApTemporaryMode"),
|
||||
db.ref("isTemporary").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApIsTemporary"),
|
||||
db
|
||||
.ref("temporaryRange")
|
||||
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||
.as("identityApTemporaryRange"),
|
||||
db
|
||||
.ref("temporaryAccessStartTime")
|
||||
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||
.as("identityApTemporaryAccessStartTime"),
|
||||
db
|
||||
.ref("temporaryAccessEndTime")
|
||||
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||
.as("identityApTemporaryAccessEndTime")
|
||||
);
|
||||
|
||||
const permission = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "membershipId",
|
||||
parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, oldRoleField, orgId }) => ({
|
||||
id: membershipId,
|
||||
identityId,
|
||||
projectId,
|
||||
role: oldRoleField,
|
||||
createdAt: membershipCreatedAt,
|
||||
updatedAt: membershipUpdatedAt,
|
||||
orgId,
|
||||
// just a prefilled value
|
||||
orgAuthEnforced: false
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "id",
|
||||
label: "roles" as const,
|
||||
mapper: (data) =>
|
||||
IdentityProjectMembershipRoleSchema.extend({
|
||||
permissions: z.unknown(),
|
||||
customRoleSlug: z.string().optional().nullable()
|
||||
}).parse(data)
|
||||
},
|
||||
{
|
||||
key: "identityApId",
|
||||
label: "additionalPrivileges" as const,
|
||||
mapper: ({
|
||||
identityApId,
|
||||
identityApPermissions,
|
||||
identityApIsTemporary,
|
||||
identityApTemporaryMode,
|
||||
identityApTemporaryRange,
|
||||
identityApTemporaryAccessEndTime,
|
||||
identityApTemporaryAccessStartTime
|
||||
}) => ({
|
||||
id: identityApId,
|
||||
permissions: identityApPermissions,
|
||||
temporaryRange: identityApTemporaryRange,
|
||||
temporaryMode: identityApTemporaryMode,
|
||||
temporaryAccessEndTime: identityApTemporaryAccessEndTime,
|
||||
temporaryAccessStartTime: identityApTemporaryAccessStartTime,
|
||||
isTemporary: identityApIsTemporary
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!permission?.[0]) return undefined;
|
||||
|
||||
// when introducting cron mode change it here
|
||||
const activeRoles = permission?.[0]?.roles.filter(
|
||||
({ isTemporary, temporaryAccessEndTime }) =>
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
);
|
||||
const activeAdditionalPrivileges = permission?.[0]?.additionalPrivileges?.filter(
|
||||
({ isTemporary, temporaryAccessEndTime }) =>
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
);
|
||||
|
||||
return { ...permission[0], roles: activeRoles, additionalPrivileges: activeAdditionalPrivileges };
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "GetProjectIdentityPermission" });
|
||||
}
|
||||
|
27
backend/src/ee/services/permission/permission-fns.ts
Normal file
27
backend/src/ee/services/permission/permission-fns.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { TOrganizations } from "@app/db/schemas";
|
||||
import { UnauthorizedError } from "@app/lib/errors";
|
||||
import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
|
||||
|
||||
function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
|
||||
if (!actorAuthMethod) return false;
|
||||
|
||||
return [
|
||||
AuthMethod.AZURE_SAML,
|
||||
AuthMethod.OKTA_SAML,
|
||||
AuthMethod.JUMPCLOUD_SAML,
|
||||
AuthMethod.GOOGLE_SAML,
|
||||
AuthMethod.KEYCLOAK_SAML
|
||||
].includes(actorAuthMethod);
|
||||
}
|
||||
|
||||
function validateOrgSAML(actorAuthMethod: ActorAuthMethod, isSamlEnforced: TOrganizations["authEnforced"]) {
|
||||
if (actorAuthMethod === undefined) {
|
||||
throw new UnauthorizedError({ name: "No auth method defined" });
|
||||
}
|
||||
|
||||
if (isSamlEnforced && actorAuthMethod !== null && !isAuthMethodSaml(actorAuthMethod)) {
|
||||
throw new UnauthorizedError({ name: "Cannot access org-scoped resource" });
|
||||
}
|
||||
}
|
||||
|
||||
export { isAuthMethodSaml, validateOrgSAML };
|
@ -11,13 +11,16 @@ import {
|
||||
} from "@app/db/schemas";
|
||||
import { conditionsMatcher } from "@app/lib/casl";
|
||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectRoleDALFactory } from "@app/services/project-role/project-role-dal";
|
||||
import { TServiceTokenDALFactory } from "@app/services/service-token/service-token-dal";
|
||||
|
||||
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
|
||||
import { TPermissionDALFactory } from "./permission-dal";
|
||||
import { validateOrgSAML } from "./permission-fns";
|
||||
import { TBuildProjectPermissionDTO } from "./permission-types";
|
||||
import {
|
||||
buildServiceTokenProjectPermission,
|
||||
projectAdminPermissions,
|
||||
@ -31,6 +34,7 @@ type TPermissionServiceFactoryDep = {
|
||||
orgRoleDAL: Pick<TOrgRoleDALFactory, "findOne">;
|
||||
projectRoleDAL: Pick<TProjectRoleDALFactory, "findOne">;
|
||||
serviceTokenDAL: Pick<TServiceTokenDALFactory, "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
permissionDAL: TPermissionDALFactory;
|
||||
};
|
||||
|
||||
@ -40,7 +44,8 @@ export const permissionServiceFactory = ({
|
||||
permissionDAL,
|
||||
orgRoleDAL,
|
||||
projectRoleDAL,
|
||||
serviceTokenDAL
|
||||
serviceTokenDAL,
|
||||
projectDAL
|
||||
}: TPermissionServiceFactoryDep) => {
|
||||
const buildOrgPermission = (role: string, permission?: unknown) => {
|
||||
switch (role) {
|
||||
@ -64,45 +69,63 @@ export const permissionServiceFactory = ({
|
||||
}
|
||||
};
|
||||
|
||||
const buildProjectPermission = (role: string, permission?: unknown) => {
|
||||
switch (role) {
|
||||
case ProjectMembershipRole.Admin:
|
||||
return projectAdminPermissions;
|
||||
case ProjectMembershipRole.Member:
|
||||
return projectMemberPermissions;
|
||||
case ProjectMembershipRole.Viewer:
|
||||
return projectViewerPermission;
|
||||
case ProjectMembershipRole.NoAccess:
|
||||
return projectNoAccessPermissions;
|
||||
case ProjectMembershipRole.Custom:
|
||||
return createMongoAbility<ProjectPermissionSet>(
|
||||
unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
|
||||
permission as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
|
||||
),
|
||||
{
|
||||
conditionsMatcher
|
||||
const buildProjectPermission = (projectUserRoles: TBuildProjectPermissionDTO) => {
|
||||
const rules = projectUserRoles
|
||||
.map(({ role, permissions }) => {
|
||||
switch (role) {
|
||||
case ProjectMembershipRole.Admin:
|
||||
return projectAdminPermissions;
|
||||
case ProjectMembershipRole.Member:
|
||||
return projectMemberPermissions;
|
||||
case ProjectMembershipRole.Viewer:
|
||||
return projectViewerPermission;
|
||||
case ProjectMembershipRole.NoAccess:
|
||||
return projectNoAccessPermissions;
|
||||
case ProjectMembershipRole.Custom: {
|
||||
return unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
|
||||
permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
|
||||
);
|
||||
}
|
||||
);
|
||||
default:
|
||||
throw new BadRequestError({
|
||||
name: "ProjectRoleInvalid",
|
||||
message: "Project role not found"
|
||||
});
|
||||
}
|
||||
default:
|
||||
throw new BadRequestError({
|
||||
name: "ProjectRoleInvalid",
|
||||
message: "Project role not found"
|
||||
});
|
||||
}
|
||||
})
|
||||
.reduce((curr, prev) => prev.concat(curr), []);
|
||||
|
||||
return createMongoAbility<ProjectPermissionSet>(rules, {
|
||||
conditionsMatcher
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Get user permission in an organization
|
||||
* */
|
||||
const getUserOrgPermission = async (userId: string, orgId: string, userOrgId?: string) => {
|
||||
*/
|
||||
const getUserOrgPermission = async (
|
||||
userId: string,
|
||||
orgId: string,
|
||||
authMethod: ActorAuthMethod,
|
||||
userOrgId?: string
|
||||
) => {
|
||||
const membership = await permissionDAL.getOrgPermission(userId, orgId);
|
||||
if (!membership) throw new UnauthorizedError({ name: "User not in org" });
|
||||
if (membership.role === OrgMembershipRole.Custom && !membership.permissions) {
|
||||
throw new BadRequestError({ name: "Custom permission not found" });
|
||||
}
|
||||
if (membership.orgAuthEnforced && membership.orgId !== userOrgId) {
|
||||
throw new BadRequestError({ name: "Cannot access org-scoped resource" });
|
||||
|
||||
// If the org ID is API_KEY, the request is being made with an API Key.
|
||||
// Since we can't scope API keys to an organization, we'll need to do an arbitrary check to see if the user is a member of the organization.
|
||||
|
||||
// Extra: This means that when users are using API keys to make requests, they can't use slug-based routes.
|
||||
// Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization.
|
||||
if (userOrgId !== "API_KEY" && membership.orgId !== userOrgId) {
|
||||
throw new UnauthorizedError({ name: "You are not logged into this organization" });
|
||||
}
|
||||
|
||||
validateOrgSAML(authMethod, membership.orgAuthEnforced);
|
||||
|
||||
return { permission: buildOrgPermission(membership.role, membership.permissions), membership };
|
||||
};
|
||||
|
||||
@ -115,10 +138,16 @@ export const permissionServiceFactory = ({
|
||||
return { permission: buildOrgPermission(membership.role, membership.permissions), membership };
|
||||
};
|
||||
|
||||
const getOrgPermission = async (type: ActorType, id: string, orgId: string, actorOrgId?: string) => {
|
||||
const getOrgPermission = async (
|
||||
type: ActorType,
|
||||
id: string,
|
||||
orgId: string,
|
||||
authMethod: ActorAuthMethod,
|
||||
actorOrgId: string | undefined
|
||||
) => {
|
||||
switch (type) {
|
||||
case ActorType.USER:
|
||||
return getUserOrgPermission(id, orgId, actorOrgId);
|
||||
return getUserOrgPermission(id, orgId, authMethod, actorOrgId);
|
||||
case ActorType.IDENTITY:
|
||||
return getIdentityOrgPermission(id, orgId);
|
||||
default:
|
||||
@ -145,44 +174,114 @@ export const permissionServiceFactory = ({
|
||||
};
|
||||
|
||||
// user permission for a project in an organization
|
||||
const getUserProjectPermission = async (userId: string, projectId: string, userOrgId?: string) => {
|
||||
const membership = await permissionDAL.getProjectPermission(userId, projectId);
|
||||
if (!membership) throw new UnauthorizedError({ name: "User not in project" });
|
||||
if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) {
|
||||
const getUserProjectPermission = async (
|
||||
userId: string,
|
||||
projectId: string,
|
||||
authMethod: ActorAuthMethod,
|
||||
userOrgId?: string
|
||||
): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
||||
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
||||
if (!userProjectPermission) throw new UnauthorizedError({ name: "User not in project" });
|
||||
|
||||
if (
|
||||
userProjectPermission.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions)
|
||||
) {
|
||||
throw new BadRequestError({ name: "Custom permission not found" });
|
||||
}
|
||||
|
||||
if (membership.orgAuthEnforced && membership.orgId !== userOrgId) {
|
||||
throw new BadRequestError({ name: "Cannot access org-scoped resource" });
|
||||
// If the org ID is API_KEY, the request is being made with an API Key.
|
||||
// Since we can't scope API keys to an organization, we'll need to do an arbitrary check to see if the user is a member of the organization.
|
||||
|
||||
// Extra: This means that when users are using API keys to make requests, they can't use slug-based routes.
|
||||
// Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization.
|
||||
if (userOrgId !== "API_KEY" && userProjectPermission.orgId !== userOrgId) {
|
||||
throw new UnauthorizedError({ name: "You are not logged into this organization" });
|
||||
}
|
||||
|
||||
validateOrgSAML(authMethod, userProjectPermission.orgAuthEnforced);
|
||||
|
||||
// join two permissions and pass to build the final permission set
|
||||
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||
const additionalPrivileges =
|
||||
userProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
|
||||
role: ProjectMembershipRole.Custom,
|
||||
permissions
|
||||
})) || [];
|
||||
|
||||
return {
|
||||
permission: buildProjectPermission(membership.role, membership.permissions),
|
||||
membership
|
||||
permission: buildProjectPermission(rolePermissions.concat(additionalPrivileges)),
|
||||
membership: userProjectPermission,
|
||||
hasRole: (role: string) =>
|
||||
userProjectPermission.roles.findIndex(
|
||||
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
||||
) !== -1
|
||||
};
|
||||
};
|
||||
|
||||
const getIdentityProjectPermission = async (identityId: string, projectId: string) => {
|
||||
const membership = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
||||
if (!membership) throw new UnauthorizedError({ name: "Identity not in project" });
|
||||
if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) {
|
||||
const getIdentityProjectPermission = async (
|
||||
identityId: string,
|
||||
projectId: string,
|
||||
identityOrgId: string | undefined
|
||||
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
||||
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
||||
if (!identityProjectPermission) throw new UnauthorizedError({ name: "Identity not in project" });
|
||||
|
||||
if (
|
||||
identityProjectPermission.roles.some(
|
||||
({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions
|
||||
)
|
||||
) {
|
||||
throw new BadRequestError({ name: "Custom permission not found" });
|
||||
}
|
||||
|
||||
if (identityProjectPermission.orgId !== identityOrgId) {
|
||||
throw new UnauthorizedError({ name: "You are not a member of this organization" });
|
||||
}
|
||||
|
||||
const rolePermissions =
|
||||
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||
const additionalPrivileges =
|
||||
identityProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
|
||||
role: ProjectMembershipRole.Custom,
|
||||
permissions
|
||||
})) || [];
|
||||
|
||||
return {
|
||||
permission: buildProjectPermission(membership.role, membership.permissions),
|
||||
membership
|
||||
permission: buildProjectPermission(rolePermissions.concat(additionalPrivileges)),
|
||||
membership: identityProjectPermission,
|
||||
hasRole: (role: string) =>
|
||||
identityProjectPermission.roles.findIndex(
|
||||
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
||||
) !== -1
|
||||
};
|
||||
};
|
||||
|
||||
const getServiceTokenProjectPermission = async (serviceTokenId: string, projectId: string) => {
|
||||
const getServiceTokenProjectPermission = async (
|
||||
serviceTokenId: string,
|
||||
projectId: string,
|
||||
actorOrgId: string | undefined
|
||||
) => {
|
||||
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
|
||||
if (!serviceToken) throw new BadRequestError({ message: "Service token not found" });
|
||||
|
||||
const serviceTokenProject = await projectDAL.findById(serviceToken.projectId);
|
||||
|
||||
if (!serviceTokenProject) throw new BadRequestError({ message: "Service token not linked to a project" });
|
||||
|
||||
if (serviceTokenProject.orgId !== actorOrgId) {
|
||||
throw new UnauthorizedError({ message: "Service token not a part of this organization" });
|
||||
}
|
||||
|
||||
if (serviceToken.projectId !== projectId)
|
||||
throw new UnauthorizedError({
|
||||
message: "Failed to find service authorization for given project"
|
||||
});
|
||||
|
||||
if (serviceTokenProject.orgId !== actorOrgId)
|
||||
throw new UnauthorizedError({
|
||||
message: "Failed to find service authorization for given project"
|
||||
});
|
||||
|
||||
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
||||
return {
|
||||
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
|
||||
@ -191,29 +290,35 @@ export const permissionServiceFactory = ({
|
||||
};
|
||||
|
||||
type TProjectPermissionRT<T extends ActorType> = T extends ActorType.SERVICE
|
||||
? { permission: MongoAbility<ProjectPermissionSet, MongoQuery>; membership: undefined }
|
||||
? {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
membership: undefined;
|
||||
hasRole: (arg: string) => boolean;
|
||||
} // service token doesn't have both membership and roles
|
||||
: {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
membership: (T extends ActorType.USER ? TProjectMemberships : TIdentityProjectMemberships) & {
|
||||
orgAuthEnforced: boolean;
|
||||
orgAuthEnforced: boolean | null | undefined;
|
||||
orgId: string;
|
||||
permissions?: unknown;
|
||||
roles: Array<{ role: string }>;
|
||||
};
|
||||
hasRole: (role: string) => boolean;
|
||||
};
|
||||
|
||||
const getProjectPermission = async <T extends ActorType>(
|
||||
type: T,
|
||||
id: string,
|
||||
projectId: string,
|
||||
actorOrgId?: string
|
||||
actorAuthMethod: ActorAuthMethod,
|
||||
actorOrgId: string | undefined
|
||||
): Promise<TProjectPermissionRT<T>> => {
|
||||
switch (type) {
|
||||
case ActorType.USER:
|
||||
return getUserProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
||||
return getUserProjectPermission(id, projectId, actorAuthMethod, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
||||
case ActorType.SERVICE:
|
||||
return getServiceTokenProjectPermission(id, projectId) as Promise<TProjectPermissionRT<T>>;
|
||||
return getServiceTokenProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
||||
case ActorType.IDENTITY:
|
||||
return getIdentityProjectPermission(id, projectId) as Promise<TProjectPermissionRT<T>>;
|
||||
return getIdentityProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
||||
default:
|
||||
throw new UnauthorizedError({
|
||||
message: "Permission not defined",
|
||||
@ -228,11 +333,13 @@ export const permissionServiceFactory = ({
|
||||
const projectRole = await projectRoleDAL.findOne({ slug: role, projectId });
|
||||
if (!projectRole) throw new BadRequestError({ message: "Role not found" });
|
||||
return {
|
||||
permission: buildProjectPermission(ProjectMembershipRole.Custom, projectRole.permissions),
|
||||
permission: buildProjectPermission([
|
||||
{ role: ProjectMembershipRole.Custom, permissions: projectRole.permissions }
|
||||
]),
|
||||
role: projectRole
|
||||
};
|
||||
}
|
||||
return { permission: buildProjectPermission(role, []) };
|
||||
return { permission: buildProjectPermission([{ role, permissions: [] }]) };
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -0,0 +1,4 @@
|
||||
export type TBuildProjectPermissionDTO = {
|
||||
permissions?: unknown;
|
||||
role: string;
|
||||
}[];
|
||||
|
@ -56,8 +56,8 @@ export type ProjectPermissionSet =
|
||||
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
||||
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback];
|
||||
|
||||
const buildAdminPermission = () => {
|
||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
const buildAdminPermissionRules = () => {
|
||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
|
||||
@ -135,13 +135,13 @@ const buildAdminPermission = () => {
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Project);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
|
||||
|
||||
return build({ conditionsMatcher });
|
||||
return rules;
|
||||
};
|
||||
|
||||
export const projectAdminPermissions = buildAdminPermission();
|
||||
export const projectAdminPermissions = buildAdminPermissionRules();
|
||||
|
||||
const buildMemberPermission = () => {
|
||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
const buildMemberPermissionRules = () => {
|
||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
|
||||
@ -196,13 +196,13 @@ const buildMemberPermission = () => {
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||
|
||||
return build({ conditionsMatcher });
|
||||
return rules;
|
||||
};
|
||||
|
||||
export const projectMemberPermissions = buildMemberPermission();
|
||||
export const projectMemberPermissions = buildMemberPermissionRules();
|
||||
|
||||
const buildViewerPermission = () => {
|
||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
const buildViewerPermissionRules = () => {
|
||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||
@ -220,14 +220,14 @@ const buildViewerPermission = () => {
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||
|
||||
return build({ conditionsMatcher });
|
||||
return rules;
|
||||
};
|
||||
|
||||
export const projectViewerPermission = buildViewerPermission();
|
||||
export const projectViewerPermission = buildViewerPermissionRules();
|
||||
|
||||
const buildNoAccessProjectPermission = () => {
|
||||
const { build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
return build({ conditionsMatcher });
|
||||
const { rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
return rules;
|
||||
};
|
||||
|
||||
export const buildServiceTokenProjectPermission = (
|
||||
|
10
backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal.ts
Normal file
10
backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TProjectUserAdditionalPrivilegeDALFactory = ReturnType<typeof projectUserAdditionalPrivilegeDALFactory>;
|
||||
|
||||
export const projectUserAdditionalPrivilegeDALFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.ProjectUserAdditionalPrivilege);
|
||||
return orm;
|
||||
};
|
212
backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts
Normal file
212
backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts
Normal file
@ -0,0 +1,212 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import ms from "ms";
|
||||
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
|
||||
import {
|
||||
ProjectUserAdditionalPrivilegeTemporaryMode,
|
||||
TCreateUserPrivilegeDTO,
|
||||
TDeleteUserPrivilegeDTO,
|
||||
TGetUserPrivilegeDetailsDTO,
|
||||
TListUserPrivilegesDTO,
|
||||
TUpdateUserPrivilegeDTO
|
||||
} from "./project-user-additional-privilege-types";
|
||||
|
||||
type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
|
||||
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
};
|
||||
|
||||
export type TProjectUserAdditionalPrivilegeServiceFactory = ReturnType<
|
||||
typeof projectUserAdditionalPrivilegeServiceFactory
|
||||
>;
|
||||
|
||||
export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
projectUserAdditionalPrivilegeDAL,
|
||||
projectMembershipDAL,
|
||||
permissionService
|
||||
}: TProjectUserAdditionalPrivilegeServiceFactoryDep) => {
|
||||
const create = async ({
|
||||
slug,
|
||||
actor,
|
||||
actorId,
|
||||
permissions: customPermission,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectMembershipId,
|
||||
...dto
|
||||
}: TCreateUserPrivilegeDTO) => {
|
||||
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||
|
||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({ slug, projectMembershipId });
|
||||
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||
|
||||
if (!dto.isTemporary) {
|
||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||
projectMembershipId,
|
||||
slug,
|
||||
permissions: customPermission
|
||||
});
|
||||
return additionalPrivilege;
|
||||
}
|
||||
|
||||
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||
projectMembershipId,
|
||||
slug,
|
||||
permissions: customPermission,
|
||||
isTemporary: true,
|
||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
|
||||
temporaryRange: dto.temporaryRange,
|
||||
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||
});
|
||||
return additionalPrivilege;
|
||||
};
|
||||
|
||||
const updateById = async ({
|
||||
privilegeId,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
...dto
|
||||
}: TUpdateUserPrivilegeDTO) => {
|
||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
||||
|
||||
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
|
||||
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||
|
||||
if (dto?.slug) {
|
||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
||||
slug: dto.slug,
|
||||
projectMembershipId: projectMembership.id
|
||||
});
|
||||
if (existingSlug && existingSlug.id !== userPrivilege.id)
|
||||
throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||
}
|
||||
|
||||
const isTemporary = typeof dto?.isTemporary !== "undefined" ? dto.isTemporary : userPrivilege.isTemporary;
|
||||
if (isTemporary) {
|
||||
const temporaryAccessStartTime = dto?.temporaryAccessStartTime || userPrivilege?.temporaryAccessStartTime;
|
||||
const temporaryRange = dto?.temporaryRange || userPrivilege?.temporaryRange;
|
||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
||||
...dto,
|
||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||
});
|
||||
return additionalPrivilege;
|
||||
}
|
||||
|
||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
||||
...dto,
|
||||
isTemporary: false,
|
||||
temporaryAccessStartTime: null,
|
||||
temporaryAccessEndTime: null,
|
||||
temporaryRange: null,
|
||||
temporaryMode: null
|
||||
});
|
||||
return additionalPrivilege;
|
||||
};
|
||||
|
||||
const deleteById = async ({ actorId, actor, actorOrgId, actorAuthMethod, privilegeId }: TDeleteUserPrivilegeDTO) => {
|
||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
||||
|
||||
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
|
||||
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||
|
||||
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
||||
return deletedPrivilege;
|
||||
};
|
||||
|
||||
const getPrivilegeDetailsById = async ({
|
||||
privilegeId,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod
|
||||
}: TGetUserPrivilegeDetailsDTO) => {
|
||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
||||
|
||||
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
|
||||
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||
|
||||
return userPrivilege;
|
||||
};
|
||||
|
||||
const listPrivileges = async ({
|
||||
projectMembershipId,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod
|
||||
}: TListUserPrivilegesDTO) => {
|
||||
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||
|
||||
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find({ projectMembershipId });
|
||||
return userPrivileges;
|
||||
};
|
||||
|
||||
return {
|
||||
create,
|
||||
updateById,
|
||||
deleteById,
|
||||
getPrivilegeDetailsById,
|
||||
listPrivileges
|
||||
};
|
||||
};
|
40
backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-types.ts
Normal file
40
backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-types.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
|
||||
export enum ProjectUserAdditionalPrivilegeTemporaryMode {
|
||||
Relative = "relative"
|
||||
}
|
||||
|
||||
export type TCreateUserPrivilegeDTO = (
|
||||
| {
|
||||
permissions: unknown;
|
||||
projectMembershipId: string;
|
||||
slug: string;
|
||||
isTemporary: false;
|
||||
}
|
||||
| {
|
||||
permissions: unknown;
|
||||
projectMembershipId: string;
|
||||
slug: string;
|
||||
isTemporary: true;
|
||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}
|
||||
) &
|
||||
Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateUserPrivilegeDTO = { privilegeId: string } & Omit<TProjectPermission, "projectId"> &
|
||||
Partial<{
|
||||
permissions: unknown;
|
||||
slug: string;
|
||||
isTemporary: boolean;
|
||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}>;
|
||||
|
||||
export type TDeleteUserPrivilegeDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
|
||||
|
||||
export type TGetUserPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
|
||||
|
||||
export type TListUserPrivilegesDTO = Omit<TProjectPermission, "projectId"> & { projectMembershipId: string };
|
@ -55,6 +55,7 @@ export const samlConfigServiceFactory = ({
|
||||
const createSamlCfg = async ({
|
||||
cert,
|
||||
actor,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
orgId,
|
||||
issuer,
|
||||
@ -63,7 +64,7 @@ export const samlConfigServiceFactory = ({
|
||||
entryPoint,
|
||||
authProvider
|
||||
}: TCreateSamlCfgDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso);
|
||||
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
@ -146,6 +147,7 @@ export const samlConfigServiceFactory = ({
|
||||
orgId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
cert,
|
||||
actorId,
|
||||
issuer,
|
||||
@ -153,7 +155,7 @@ export const samlConfigServiceFactory = ({
|
||||
entryPoint,
|
||||
authProvider
|
||||
}: TUpdateSamlCfgDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (!plan.samlSSO)
|
||||
@ -172,7 +174,7 @@ export const samlConfigServiceFactory = ({
|
||||
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
|
||||
});
|
||||
|
||||
if (entryPoint) {
|
||||
if (entryPoint !== undefined) {
|
||||
const {
|
||||
ciphertext: encryptedEntryPoint,
|
||||
iv: entryPointIV,
|
||||
@ -182,18 +184,19 @@ export const samlConfigServiceFactory = ({
|
||||
updateQuery.entryPointIV = entryPointIV;
|
||||
updateQuery.entryPointTag = entryPointTag;
|
||||
}
|
||||
if (issuer) {
|
||||
if (issuer !== undefined) {
|
||||
const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key);
|
||||
updateQuery.encryptedIssuer = encryptedIssuer;
|
||||
updateQuery.issuerIV = issuerIV;
|
||||
updateQuery.issuerTag = issuerTag;
|
||||
}
|
||||
if (cert) {
|
||||
if (cert !== undefined) {
|
||||
const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key);
|
||||
updateQuery.encryptedCert = encryptedCert;
|
||||
updateQuery.certIV = certIV;
|
||||
updateQuery.certTag = certTag;
|
||||
}
|
||||
|
||||
const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery);
|
||||
await orgDAL.updateById(orgId, { authEnforced: false, scimEnabled: false });
|
||||
|
||||
@ -237,6 +240,7 @@ export const samlConfigServiceFactory = ({
|
||||
dto.actor,
|
||||
dto.actorId,
|
||||
ssoConfig.orgId,
|
||||
dto.actorAuthMethod,
|
||||
dto.actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
|
||||
@ -315,6 +319,11 @@ export const samlConfigServiceFactory = ({
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
||||
|
||||
// TODO(dangtony98): remove this after aliases update
|
||||
if (authProvider === AuthMethod.KEYCLOAK_SAML && appCfg.LICENSE_SERVER_KEY) {
|
||||
throw new BadRequestError({ message: "Keycloak SAML is not yet available on Infisical Cloud" });
|
||||
}
|
||||
|
||||
if (user) {
|
||||
await userDAL.transaction(async (tx) => {
|
||||
const [orgMembership] = await orgDAL.findMembership(
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { TOrgPermission } from "@app/lib/types";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
|
||||
export enum SamlProviders {
|
||||
OKTA_SAML = "okta-saml",
|
||||
AZURE_SAML = "azure-saml",
|
||||
JUMPCLOUD_SAML = "jumpcloud-saml",
|
||||
GOOGLE_SAML = "google-saml"
|
||||
GOOGLE_SAML = "google-saml",
|
||||
KEYCLOAK_SAML = "keycloak-saml"
|
||||
}
|
||||
|
||||
export type TCreateSamlCfgDTO = {
|
||||
@ -26,7 +27,14 @@ export type TUpdateSamlCfgDTO = Partial<{
|
||||
TOrgPermission;
|
||||
|
||||
export type TGetSamlCfgDTO =
|
||||
| { type: "org"; orgId: string; actor: ActorType; actorId: string; actorOrgId?: string }
|
||||
| {
|
||||
type: "org";
|
||||
orgId: string;
|
||||
actor: ActorType;
|
||||
actorId: string;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
actorOrgId: string | undefined;
|
||||
}
|
||||
| {
|
||||
type: "orgSlug";
|
||||
orgSlug: string;
|
||||
|
@ -56,8 +56,16 @@ export const scimServiceFactory = ({
|
||||
permissionService,
|
||||
smtpService
|
||||
}: TScimServiceFactoryDep) => {
|
||||
const createScimToken = async ({ actor, actorId, actorOrgId, orgId, description, ttlDays }: TCreateScimTokenDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const createScimToken = async ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
orgId,
|
||||
description,
|
||||
ttlDays
|
||||
}: TCreateScimTokenDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Scim);
|
||||
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
@ -85,8 +93,8 @@ export const scimServiceFactory = ({
|
||||
return { scimToken };
|
||||
};
|
||||
|
||||
const listScimTokens = async ({ actor, actorId, actorOrgId, orgId }: TOrgPermission) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const listScimTokens = async ({ actor, actorId, actorOrgId, actorAuthMethod, orgId }: TOrgPermission) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim);
|
||||
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
@ -99,11 +107,17 @@ export const scimServiceFactory = ({
|
||||
return scimTokens;
|
||||
};
|
||||
|
||||
const deleteScimToken = async ({ scimTokenId, actor, actorId, actorOrgId }: TDeleteScimTokenDTO) => {
|
||||
const deleteScimToken = async ({ scimTokenId, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteScimTokenDTO) => {
|
||||
let scimToken = await scimDAL.findById(scimTokenId);
|
||||
if (!scimToken) throw new BadRequestError({ message: "Failed to find SCIM token to delete" });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, scimToken.orgId, actorOrgId);
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
scimToken.orgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
|
||||
|
||||
const plan = await licenseService.getPlan(scimToken.orgId);
|
||||
|
@ -45,6 +45,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
approvals,
|
||||
approvers,
|
||||
projectId,
|
||||
@ -54,7 +55,13 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
if (approvals > approvers.length)
|
||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.SecretApproval
|
||||
@ -98,6 +105,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
approvals,
|
||||
secretPolicyId
|
||||
}: TUpdateSapDTO) => {
|
||||
@ -108,6 +116,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actor,
|
||||
actorId,
|
||||
secretApprovalPolicy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||
@ -152,7 +161,13 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const deleteSecretApprovalPolicy = async ({ secretPolicyId, actor, actorId, actorOrgId }: TDeleteSapDTO) => {
|
||||
const deleteSecretApprovalPolicy = async ({
|
||||
secretPolicyId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TDeleteSapDTO) => {
|
||||
const sapPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
||||
if (!sapPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
|
||||
|
||||
@ -160,6 +175,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actor,
|
||||
actorId,
|
||||
sapPolicy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@ -171,8 +187,20 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
return sapPolicy;
|
||||
};
|
||||
|
||||
const getSecretApprovalPolicyByProjectId = async ({ actorId, actor, actorOrgId, projectId }: TListSapDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const getSecretApprovalPolicyByProjectId = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectId
|
||||
}: TListSapDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||
|
||||
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId });
|
||||
@ -201,10 +229,17 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
environment,
|
||||
secretPath
|
||||
}: TGetBoardSapDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { secretPath, environment })
|
||||
|
@ -82,13 +82,14 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
secretVersionDAL,
|
||||
secretQueueService
|
||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId }: TApprovalRequestCountDTO) => {
|
||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||
|
||||
const { membership } = await permissionService.getProjectPermission(
|
||||
actor as ActorType.USER,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
@ -100,6 +101,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
projectId,
|
||||
actorId,
|
||||
actor,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
status,
|
||||
environment,
|
||||
@ -109,7 +111,13 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
}: TListApprovalsDTO) => {
|
||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||
|
||||
const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const { membership } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
const approvals = await secretApprovalRequestDAL.findByProjectId({
|
||||
projectId,
|
||||
committer,
|
||||
@ -122,21 +130,28 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
return approvals;
|
||||
};
|
||||
|
||||
const getSecretApprovalDetails = async ({ actor, actorId, actorOrgId, id }: TSecretApprovalDetailsDTO) => {
|
||||
const getSecretApprovalDetails = async ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
id
|
||||
}: TSecretApprovalDetailsDTO) => {
|
||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||
|
||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(id);
|
||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||
|
||||
const { policy } = secretApprovalRequest;
|
||||
const { membership } = await permissionService.getProjectPermission(
|
||||
const { membership, hasRole } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
secretApprovalRequest.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
if (
|
||||
membership.role !== ProjectMembershipRole.Admin &&
|
||||
!hasRole(ProjectMembershipRole.Admin) &&
|
||||
secretApprovalRequest.committerId !== membership.id &&
|
||||
!policy.approvers.find((approverId) => approverId === membership.id)
|
||||
) {
|
||||
@ -150,20 +165,28 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
return { ...secretApprovalRequest, secretPath: secretPath?.[0]?.path || "/", commits: secrets };
|
||||
};
|
||||
|
||||
const reviewApproval = async ({ approvalId, actor, status, actorId, actorOrgId }: TReviewRequestDTO) => {
|
||||
const reviewApproval = async ({
|
||||
approvalId,
|
||||
actor,
|
||||
status,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TReviewRequestDTO) => {
|
||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||
|
||||
const { policy } = secretApprovalRequest;
|
||||
const { membership } = await permissionService.getProjectPermission(
|
||||
const { membership, hasRole } = await permissionService.getProjectPermission(
|
||||
ActorType.USER,
|
||||
actorId,
|
||||
secretApprovalRequest.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
if (
|
||||
membership.role !== ProjectMembershipRole.Admin &&
|
||||
!hasRole(ProjectMembershipRole.Admin) &&
|
||||
secretApprovalRequest.committerId !== membership.id &&
|
||||
!policy.approvers.find((approverId) => approverId === membership.id)
|
||||
) {
|
||||
@ -192,20 +215,28 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
return reviewStatus;
|
||||
};
|
||||
|
||||
const updateApprovalStatus = async ({ actorId, status, approvalId, actor, actorOrgId }: TStatusChangeDTO) => {
|
||||
const updateApprovalStatus = async ({
|
||||
actorId,
|
||||
status,
|
||||
approvalId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
}: TStatusChangeDTO) => {
|
||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||
|
||||
const { policy } = secretApprovalRequest;
|
||||
const { membership } = await permissionService.getProjectPermission(
|
||||
const { membership, hasRole } = await permissionService.getProjectPermission(
|
||||
ActorType.USER,
|
||||
actorId,
|
||||
secretApprovalRequest.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
if (
|
||||
membership.role !== ProjectMembershipRole.Admin &&
|
||||
!hasRole(ProjectMembershipRole.Admin) &&
|
||||
secretApprovalRequest.committerId !== membership.id &&
|
||||
!policy.approvers.find((approverId) => approverId === membership.id)
|
||||
) {
|
||||
@ -229,16 +260,24 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
approvalId,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
}: TMergeSecretApprovalRequestDTO) => {
|
||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||
|
||||
const { policy, folderId, projectId } = secretApprovalRequest;
|
||||
const { membership } = await permissionService.getProjectPermission(ActorType.USER, actorId, projectId, actorOrgId);
|
||||
const { membership, hasRole } = await permissionService.getProjectPermission(
|
||||
ActorType.USER,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
if (
|
||||
membership.role !== ProjectMembershipRole.Admin &&
|
||||
!hasRole(ProjectMembershipRole.Admin) &&
|
||||
secretApprovalRequest.committerId !== membership.id &&
|
||||
!policy.approvers.find((approverId) => approverId === membership.id)
|
||||
) {
|
||||
@ -433,6 +472,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
policy,
|
||||
projectId,
|
||||
secretPath,
|
||||
@ -444,6 +484,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
|
@ -9,6 +9,7 @@ import jmespath from "jmespath";
|
||||
import knex from "knex";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { getDbConnectionHost } from "@app/lib/knex";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { TAssignOp, TDbProviderClients, TDirectAssignOp, THttpProviderFunction } from "../templates/types";
|
||||
@ -89,7 +90,17 @@ export const secretRotationDbFn = async ({
|
||||
const appCfg = getConfig();
|
||||
|
||||
const ssl = ca ? { rejectUnauthorized: false, ca } : undefined;
|
||||
if (host === "localhost" || host === "127.0.0.1" || appCfg.DB_CONNECTION_URI.includes(host))
|
||||
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);
|
||||
if (
|
||||
host === "localhost" ||
|
||||
host === "127.0.0.1" ||
|
||||
// database infisical uses
|
||||
dbHost === host ||
|
||||
// internal ips
|
||||
host === "host.docker.internal" ||
|
||||
host.match(/^10\.\d+\.\d+\.\d+/) ||
|
||||
host.match(/^192\.168\.\d+\.\d+/)
|
||||
)
|
||||
throw new Error("Invalid db host");
|
||||
|
||||
const db = knex({
|
||||
|
@ -1,3 +1,10 @@
|
||||
import {
|
||||
CreateAccessKeyCommand,
|
||||
DeleteAccessKeyCommand,
|
||||
GetAccessKeyLastUsedCommand,
|
||||
IAMClient
|
||||
} from "@aws-sdk/client-iam";
|
||||
|
||||
import { SecretKeyEncoding, SecretType } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import {
|
||||
@ -18,7 +25,12 @@ import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||
|
||||
import { TSecretRotationDALFactory } from "../secret-rotation-dal";
|
||||
import { rotationTemplates } from "../templates";
|
||||
import { TDbProviderClients, TProviderFunctionTypes, TSecretRotationProviderTemplate } from "../templates/types";
|
||||
import {
|
||||
TAwsProviderSystems,
|
||||
TDbProviderClients,
|
||||
TProviderFunctionTypes,
|
||||
TSecretRotationProviderTemplate
|
||||
} from "../templates/types";
|
||||
import {
|
||||
getDbSetQuery,
|
||||
secretRotationDbFn,
|
||||
@ -127,7 +139,10 @@ export const secretRotationQueueFactory = ({
|
||||
internal: {}
|
||||
};
|
||||
|
||||
// when its a database we keep cycling the variables accordingly
|
||||
/* Rotation Function For Database
|
||||
* A database like sql cannot have multiple password for a user
|
||||
* thus we ask users to create two users with required permission and then we keep cycling between these two db users
|
||||
*/
|
||||
if (provider.template.type === TProviderFunctionTypes.DB) {
|
||||
const lastCred = variables.creds.at(-1);
|
||||
if (lastCred && variables.creds.length === 1) {
|
||||
@ -170,6 +185,65 @@ export const secretRotationQueueFactory = ({
|
||||
if (variables.creds.length === 2) variables.creds.pop();
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotation Function For AWS Services
|
||||
* Due to complexity in AWS Authorization hashing signature process we keep it as seperate entity instead of http template mode
|
||||
* We first delete old key before creating a new one because aws iam has a quota limit of 2 keys
|
||||
* */
|
||||
if (provider.template.type === TProviderFunctionTypes.AWS) {
|
||||
if (provider.template.client === TAwsProviderSystems.IAM) {
|
||||
const client = new IAMClient({
|
||||
region: newCredential.inputs.manager_user_aws_region as string,
|
||||
credentials: {
|
||||
accessKeyId: newCredential.inputs.manager_user_access_key as string,
|
||||
secretAccessKey: newCredential.inputs.manager_user_secret_key as string
|
||||
}
|
||||
});
|
||||
|
||||
const iamUserName = newCredential.inputs.iam_username as string;
|
||||
|
||||
if (variables.creds.length === 2) {
|
||||
const deleteCycleCredential = variables.creds.pop();
|
||||
if (deleteCycleCredential) {
|
||||
const deletedIamAccessKey = await client.send(
|
||||
new DeleteAccessKeyCommand({
|
||||
UserName: iamUserName,
|
||||
AccessKeyId: deleteCycleCredential.outputs.iam_user_access_key as string
|
||||
})
|
||||
);
|
||||
|
||||
if (
|
||||
!deletedIamAccessKey?.$metadata?.httpStatusCode ||
|
||||
deletedIamAccessKey?.$metadata?.httpStatusCode > 300
|
||||
) {
|
||||
throw new DisableRotationErrors({
|
||||
message: "Failed to delete aws iam access key. Check managed iam user policy"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newIamAccessKey = await client.send(new CreateAccessKeyCommand({ UserName: iamUserName }));
|
||||
if (!newIamAccessKey.AccessKey)
|
||||
throw new DisableRotationErrors({ message: "Failed to create access key. Check managed iam user policy" });
|
||||
|
||||
// test
|
||||
const testAccessKey = await client.send(
|
||||
new GetAccessKeyLastUsedCommand({ AccessKeyId: newIamAccessKey.AccessKey.AccessKeyId })
|
||||
);
|
||||
if (testAccessKey?.UserName !== iamUserName)
|
||||
throw new DisableRotationErrors({ message: "Failed to create access key. Check managed iam user policy" });
|
||||
|
||||
newCredential.outputs.iam_user_access_key = newIamAccessKey.AccessKey.AccessKeyId;
|
||||
newCredential.outputs.iam_user_secret_key = newIamAccessKey.AccessKey.SecretAccessKey;
|
||||
}
|
||||
}
|
||||
|
||||
/* Rotation function of HTTP infisical template
|
||||
* This is a generic http based template system for rotation
|
||||
* we use this for sendgrid and for custom secret rotation
|
||||
* This will ensure user provided rotation is easier to make
|
||||
* */
|
||||
if (provider.template.type === TProviderFunctionTypes.HTTP) {
|
||||
if (provider.template.functions.set?.pre) {
|
||||
secretRotationPreSetFn(provider.template.functions.set.pre, newCredential);
|
||||
@ -185,6 +259,9 @@ export const secretRotationQueueFactory = ({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert the new variables to start
|
||||
// encrypt the data - save it
|
||||
variables.creds.unshift({
|
||||
outputs: newCredential.outputs,
|
||||
internal: newCredential.internal
|
||||
@ -200,6 +277,7 @@ export const secretRotationQueueFactory = ({
|
||||
key
|
||||
)
|
||||
}));
|
||||
// map the final values to output keys in the board
|
||||
await secretRotationDAL.transaction(async (tx) => {
|
||||
await secretRotationDAL.updateById(
|
||||
rotationId,
|
||||
|
@ -39,8 +39,20 @@ export const secretRotationServiceFactory = ({
|
||||
folderDAL,
|
||||
secretDAL
|
||||
}: TSecretRotationServiceFactoryDep) => {
|
||||
const getProviderTemplates = async ({ actor, actorId, actorOrgId, projectId }: TProjectPermission) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const getProviderTemplates = async ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectId
|
||||
}: TProjectPermission) => {
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
||||
|
||||
return {
|
||||
@ -54,6 +66,7 @@ export const secretRotationServiceFactory = ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
inputs,
|
||||
outputs,
|
||||
interval,
|
||||
@ -61,7 +74,13 @@ export const secretRotationServiceFactory = ({
|
||||
secretPath,
|
||||
environment
|
||||
}: TCreateSecretRotationDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.SecretRotation
|
||||
@ -139,14 +158,20 @@ export const secretRotationServiceFactory = ({
|
||||
return secretRotation;
|
||||
};
|
||||
|
||||
const getByProjectId = async ({ actorId, projectId, actor, actorOrgId }: TListByProjectIdDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const getByProjectId = async ({ actorId, projectId, actor, actorOrgId, actorAuthMethod }: TListByProjectIdDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
||||
const doc = await secretRotationDAL.find({ projectId });
|
||||
return doc;
|
||||
};
|
||||
|
||||
const restartById = async ({ actor, actorId, actorOrgId, rotationId }: TRestartDTO) => {
|
||||
const restartById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TRestartDTO) => {
|
||||
const doc = await secretRotationDAL.findById(rotationId);
|
||||
if (!doc) throw new BadRequestError({ message: "Rotation not found" });
|
||||
|
||||
@ -157,18 +182,30 @@ export const secretRotationServiceFactory = ({
|
||||
message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation."
|
||||
});
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId, actorOrgId);
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
doc.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation);
|
||||
await secretRotationQueue.removeFromQueue(doc.id, doc.interval);
|
||||
await secretRotationQueue.addToQueue(doc.id, doc.interval);
|
||||
return doc;
|
||||
};
|
||||
|
||||
const deleteById = async ({ actor, actorId, actorOrgId, rotationId }: TDeleteDTO) => {
|
||||
const deleteById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TDeleteDTO) => {
|
||||
const doc = await secretRotationDAL.findById(rotationId);
|
||||
if (!doc) throw new BadRequestError({ message: "Rotation not found" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId, actorOrgId);
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
doc.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.SecretRotation
|
||||
|
21
backend/src/ee/services/secret-rotation/templates/aws-iam.ts
Normal file
21
backend/src/ee/services/secret-rotation/templates/aws-iam.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { TAwsProviderSystems, TProviderFunctionTypes } from "./types";
|
||||
|
||||
export const AWS_IAM_TEMPLATE = {
|
||||
type: TProviderFunctionTypes.AWS as const,
|
||||
client: TAwsProviderSystems.IAM,
|
||||
inputs: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
manager_user_access_key: { type: "string" as const },
|
||||
manager_user_secret_key: { type: "string" as const },
|
||||
manager_user_aws_region: { type: "string" as const },
|
||||
iam_username: { type: "string" as const }
|
||||
},
|
||||
required: ["manager_user_access_key", "manager_user_secret_key", "manager_user_aws_region", "iam_username"],
|
||||
additionalProperties: false
|
||||
},
|
||||
outputs: {
|
||||
iam_user_access_key: { type: "string" },
|
||||
iam_user_secret_key: { type: "string" }
|
||||
}
|
||||
};
|
@ -1,3 +1,4 @@
|
||||
import { AWS_IAM_TEMPLATE } from "./aws-iam";
|
||||
import { MYSQL_TEMPLATE } from "./mysql";
|
||||
import { POSTGRES_TEMPLATE } from "./postgres";
|
||||
import { SENDGRID_TEMPLATE } from "./sendgrid";
|
||||
@ -24,5 +25,12 @@ export const rotationTemplates: TSecretRotationProviderTemplate[] = [
|
||||
image: "mysql.png",
|
||||
description: "Rotate MySQL@7/MariaDB user credentials",
|
||||
template: MYSQL_TEMPLATE
|
||||
},
|
||||
{
|
||||
name: "aws-iam",
|
||||
title: "AWS IAM",
|
||||
image: "aws-iam.svg",
|
||||
description: "Rotate AWS IAM User credentials",
|
||||
template: AWS_IAM_TEMPLATE
|
||||
}
|
||||
];
|
||||
|
@ -1,6 +1,7 @@
|
||||
export enum TProviderFunctionTypes {
|
||||
HTTP = "http",
|
||||
DB = "database"
|
||||
DB = "database",
|
||||
AWS = "aws"
|
||||
}
|
||||
|
||||
export enum TDbProviderClients {
|
||||
@ -10,6 +11,10 @@ export enum TDbProviderClients {
|
||||
MySql = "mysql"
|
||||
}
|
||||
|
||||
export enum TAwsProviderSystems {
|
||||
IAM = "iam"
|
||||
}
|
||||
|
||||
export enum TAssignOp {
|
||||
Direct = "direct",
|
||||
JmesPath = "jmesopath"
|
||||
@ -42,7 +47,7 @@ export type TSecretRotationProviderTemplate = {
|
||||
title: string;
|
||||
image?: string;
|
||||
description?: string;
|
||||
template: THttpProviderTemplate | TDbProviderTemplate;
|
||||
template: THttpProviderTemplate | TDbProviderTemplate | TAwsProviderTemplate;
|
||||
};
|
||||
|
||||
export type THttpProviderTemplate = {
|
||||
@ -70,3 +75,14 @@ export type TDbProviderTemplate = {
|
||||
};
|
||||
outputs: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type TAwsProviderTemplate = {
|
||||
type: TProviderFunctionTypes.AWS;
|
||||
client: TAwsProviderSystems;
|
||||
inputs: {
|
||||
type: "object";
|
||||
properties: Record<string, { type: string; [x: string]: unknown; desc?: string }>;
|
||||
required?: string[];
|
||||
};
|
||||
outputs: Record<string, unknown>;
|
||||
};
|
||||
|
@ -39,8 +39,14 @@ export const secretScanningServiceFactory = ({
|
||||
permissionService,
|
||||
secretScanningQueue
|
||||
}: TSecretScanningServiceFactoryDep) => {
|
||||
const createInstallationSession = async ({ actor, orgId, actorId, actorOrgId }: TInstallAppSessionDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const createInstallationSession = async ({
|
||||
actor,
|
||||
orgId,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TInstallAppSessionDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
|
||||
|
||||
const sessionId = crypto.randomBytes(16).toString("hex");
|
||||
@ -53,12 +59,19 @@ export const secretScanningServiceFactory = ({
|
||||
actorId,
|
||||
installationId,
|
||||
actor,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TLinkInstallSessionDTO) => {
|
||||
const session = await gitAppInstallSessionDAL.findOne({ sessionId });
|
||||
if (!session) throw new UnauthorizedError({ message: "Session not found" });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, session.orgId, actorOrgId);
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
session.orgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
|
||||
const installatedApp = await gitAppOrgDAL.transaction(async (tx) => {
|
||||
await gitAppInstallSessionDAL.deleteById(session.id, tx);
|
||||
@ -89,23 +102,37 @@ export const secretScanningServiceFactory = ({
|
||||
return { installatedApp };
|
||||
};
|
||||
|
||||
const getOrgInstallationStatus = async ({ actorId, orgId, actor, actorOrgId }: TGetOrgInstallStatusDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const getOrgInstallationStatus = async ({
|
||||
actorId,
|
||||
orgId,
|
||||
actor,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TGetOrgInstallStatusDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
||||
|
||||
const appInstallation = await gitAppOrgDAL.findOne({ orgId });
|
||||
return Boolean(appInstallation);
|
||||
};
|
||||
|
||||
const getRisksByOrg = async ({ actor, orgId, actorId, actorOrgId }: TGetOrgRisksDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const getRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId }: TGetOrgRisksDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
||||
const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] });
|
||||
return { risks };
|
||||
};
|
||||
|
||||
const updateRiskStatus = async ({ actorId, orgId, actor, actorOrgId, riskId, status }: TUpdateRiskStatusDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||
const updateRiskStatus = async ({
|
||||
actorId,
|
||||
orgId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
riskId,
|
||||
status
|
||||
}: TUpdateRiskStatusDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
|
||||
|
||||
const isRiskResolved = Boolean(
|
||||
|
@ -59,9 +59,16 @@ export const secretSnapshotServiceFactory = ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
path
|
||||
}: TProjectSnapshotCountDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||
@ -77,11 +84,18 @@ export const secretSnapshotServiceFactory = ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
path,
|
||||
limit = 20,
|
||||
offset = 0
|
||||
}: TProjectSnapshotListDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||
@ -91,10 +105,16 @@ export const secretSnapshotServiceFactory = ({
|
||||
return snapshots;
|
||||
};
|
||||
|
||||
const getSnapshotData = async ({ actorId, actor, actorOrgId, id }: TGetSnapshotDataDTO) => {
|
||||
const getSnapshotData = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetSnapshotDataDTO) => {
|
||||
const snapshot = await snapshotDAL.findSecretSnapshotDataById(id);
|
||||
if (!snapshot) throw new BadRequestError({ message: "Snapshot not found" });
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId, actorOrgId);
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
snapshot.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||
return snapshot;
|
||||
};
|
||||
@ -145,11 +165,23 @@ export const secretSnapshotServiceFactory = ({
|
||||
}
|
||||
};
|
||||
|
||||
const rollbackSnapshot = async ({ id: snapshotId, actor, actorId, actorOrgId }: TRollbackSnapshotDTO) => {
|
||||
const rollbackSnapshot = async ({
|
||||
id: snapshotId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TRollbackSnapshotDTO) => {
|
||||
const snapshot = await snapshotDAL.findById(snapshotId);
|
||||
if (!snapshot) throw new BadRequestError({ message: "Snapshot not found" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId, actorOrgId);
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
snapshot.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.SecretRollback
|
||||
|
@ -26,8 +26,14 @@ export const trustedIpServiceFactory = ({
|
||||
licenseService,
|
||||
projectDAL
|
||||
}: TTrustedIpServiceFactoryDep) => {
|
||||
const listIpsByProjectId = async ({ projectId, actor, actorId, actorOrgId }: TProjectPermission) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const listIpsByProjectId = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TProjectPermission) => {
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||
const trustedIps = await trustedIpDAL.find({
|
||||
projectId
|
||||
@ -38,13 +44,20 @@ export const trustedIpServiceFactory = ({
|
||||
const addProjectIp = async ({
|
||||
projectId,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actor,
|
||||
actorOrgId,
|
||||
ipAddress: ip,
|
||||
comment,
|
||||
isActive
|
||||
}: TCreateIpDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
||||
|
||||
const project = await projectDAL.findById(projectId);
|
||||
@ -78,11 +91,18 @@ export const trustedIpServiceFactory = ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
ipAddress: ip,
|
||||
comment,
|
||||
trustedIpId
|
||||
}: TUpdateIpDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
||||
|
||||
const project = await projectDAL.findById(projectId);
|
||||
@ -113,8 +133,21 @@ export const trustedIpServiceFactory = ({
|
||||
return { trustedIp, project }; // for audit log
|
||||
};
|
||||
|
||||
const deleteProjectIp = async ({ projectId, actorId, actor, actorOrgId, trustedIpId }: TDeleteIpDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
||||
const deleteProjectIp = async ({
|
||||
projectId,
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
trustedIpId
|
||||
}: TDeleteIpDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
||||
|
||||
const project = await projectDAL.findById(projectId);
|
||||
|
555
backend/src/lib/api-docs/constants.ts
Normal file
555
backend/src/lib/api-docs/constants.ts
Normal file
@ -0,0 +1,555 @@
|
||||
export const IDENTITIES = {
|
||||
CREATE: {
|
||||
name: "The name of the identity to create.",
|
||||
organizationId: "The organization ID to which the identity belongs.",
|
||||
role: "The role of the identity. Possible values are 'no-access', 'member', and 'admin'."
|
||||
},
|
||||
UPDATE: {
|
||||
identityId: "The ID of the identity to update.",
|
||||
name: "The new name of the identity.",
|
||||
role: "The new role of the identity."
|
||||
},
|
||||
DELETE: {
|
||||
identityId: "The ID of the identity to delete."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const UNIVERSAL_AUTH = {
|
||||
LOGIN: {
|
||||
clientId: "Your Machine Identity Client ID.",
|
||||
clientSecret: "Your Machine Identity Client Secret."
|
||||
},
|
||||
ATTACH: {
|
||||
identityId: "The ID of the identity to attach the configuration onto.",
|
||||
clientSecretTrustedIps:
|
||||
"A list of IPs or CIDR ranges that the Client Secret can be used from together with the Client ID to get back an access token. You can use 0.0.0.0/0, to allow usage from any network address.",
|
||||
accessTokenTrustedIps:
|
||||
"A list of IPs or CIDR ranges that access tokens can be used from. You can use 0.0.0.0/0, to allow usage from any network address.",
|
||||
accessTokenTTL: "The lifetime for an access token in seconds. This value will be referenced at renewal time.",
|
||||
accessTokenMaxTTL:
|
||||
"The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.",
|
||||
accessTokenNumUsesLimit:
|
||||
"The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses."
|
||||
},
|
||||
RETRIEVE: {
|
||||
identityId: "The ID of the identity to retrieve."
|
||||
},
|
||||
UPDATE: {
|
||||
identityId: "The ID of the identity to update.",
|
||||
clientSecretTrustedIps: "The new list of IPs or CIDR ranges that the Client Secret can be used from.",
|
||||
accessTokenTrustedIps: "The new list of IPs or CIDR ranges that access tokens can be used from.",
|
||||
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
|
||||
},
|
||||
CREATE_CLIENT_SECRET: {
|
||||
identityId: "The ID of the identity to create a client secret for.",
|
||||
description: "The description of the client secret.",
|
||||
numUsesLimit:
|
||||
"The maximum number of times that the client secret can be used; a value of 0 implies infinite number of uses.",
|
||||
ttl: "The lifetime for the client secret in seconds."
|
||||
},
|
||||
LIST_CLIENT_SECRETS: {
|
||||
identityId: "The ID of the identity to list client secrets for."
|
||||
},
|
||||
REVOKE_CLIENT_SECRET: {
|
||||
identityId: "The ID of the identity to revoke the client secret from.",
|
||||
clientSecretId: "The ID of the client secret to revoke."
|
||||
},
|
||||
RENEW_ACCESS_TOKEN: {
|
||||
accessToken: "The access token to renew."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const ORGANIZATIONS = {
|
||||
LIST_USER_MEMBERSHIPS: {
|
||||
organizationId: "The ID of the organization to get memberships from."
|
||||
},
|
||||
UPDATE_USER_MEMBERSHIP: {
|
||||
organizationId: "The ID of the organization to update the membership for.",
|
||||
membershipId: "The ID of the membership to update.",
|
||||
role: "The new role of the membership."
|
||||
},
|
||||
DELETE_USER_MEMBERSHIP: {
|
||||
organizationId: "The ID of the organization to delete the membership from.",
|
||||
membershipId: "The ID of the membership to delete."
|
||||
},
|
||||
LIST_IDENTITY_MEMBERSHIPS: {
|
||||
orgId: "The ID of the organization to get identity memberships from."
|
||||
},
|
||||
GET_PROJECTS: {
|
||||
organizationId: "The ID of the organization to get projects from."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const PROJECTS = {
|
||||
CREATE: {
|
||||
organizationSlug: "The slug of the organization to create the project in.",
|
||||
projectName: "The name of the project to create.",
|
||||
slug: "An optional slug for the project."
|
||||
},
|
||||
DELETE: {
|
||||
workspaceId: "The ID of the project to delete."
|
||||
},
|
||||
GET: {
|
||||
workspaceId: "The ID of the project."
|
||||
},
|
||||
UPDATE: {
|
||||
workspaceId: "The ID of the project to update.",
|
||||
name: "The new name of the project.",
|
||||
autoCapitalization: "Disable or enable auto-capitalization for the project."
|
||||
},
|
||||
INVITE_MEMBER: {
|
||||
projectId: "The ID of the project to invite the member to.",
|
||||
emails: "A list of organization member emails to invite to the project.",
|
||||
usernames: "A list of usernames to invite to the project."
|
||||
},
|
||||
REMOVE_MEMBER: {
|
||||
projectId: "The ID of the project to remove the member from.",
|
||||
emails: "A list of organization member emails to remove from the project.",
|
||||
usernames: "A list of usernames to remove from the project."
|
||||
},
|
||||
GET_USER_MEMBERSHIPS: {
|
||||
workspaceId: "The ID of the project to get memberships from."
|
||||
},
|
||||
UPDATE_USER_MEMBERSHIP: {
|
||||
workspaceId: "The ID of the project to update the membership for.",
|
||||
membershipId: "The ID of the membership to update.",
|
||||
roles: "A list of roles to update the membership to."
|
||||
},
|
||||
LIST_IDENTITY_MEMBERSHIPS: {
|
||||
projectId: "The ID of the project to get identity memberships from."
|
||||
},
|
||||
UPDATE_IDENTITY_MEMBERSHIP: {
|
||||
projectId: "The ID of the project to update the identity membership for.",
|
||||
identityId: "The ID of the identity to update the membership for.",
|
||||
roles: "A list of roles to update the membership to."
|
||||
},
|
||||
DELETE_IDENTITY_MEMBERSHIP: {
|
||||
projectId: "The ID of the project to delete the identity membership from.",
|
||||
identityId: "The ID of the identity to delete the membership from."
|
||||
},
|
||||
GET_KEY: {
|
||||
workspaceId: "The ID of the project to get the key from."
|
||||
},
|
||||
GET_SNAPSHOTS: {
|
||||
workspaceId: "The ID of the project to get snapshots from.",
|
||||
environment: "The environment to get snapshots from.",
|
||||
path: "The secret path to get snapshots from.",
|
||||
offset: "The offset to start from. If you enter 10, it will start from the 10th snapshot.",
|
||||
limit: "The number of snapshots to return."
|
||||
},
|
||||
ROLLBACK_TO_SNAPSHOT: {
|
||||
secretSnapshotId: "The ID of the snapshot to rollback to."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const ENVIRONMENTS = {
|
||||
CREATE: {
|
||||
workspaceId: "The ID of the project to create the environment in.",
|
||||
name: "The name of the environment to create.",
|
||||
slug: "The slug of the environment to create."
|
||||
},
|
||||
UPDATE: {
|
||||
workspaceId: "The ID of the project to update the environment in.",
|
||||
id: "The ID of the environment to update.",
|
||||
name: "The new name of the environment.",
|
||||
slug: "The new slug of the environment.",
|
||||
position: "The new position of the environment. The lowest number will be displayed as the first environment."
|
||||
},
|
||||
DELETE: {
|
||||
workspaceId: "The ID of the project to delete the environment from.",
|
||||
id: "The ID of the environment to delete."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const FOLDERS = {
|
||||
LIST: {
|
||||
workspaceId: "The ID of the project to list folders from.",
|
||||
environment: "The slug of the environment to list folders from.",
|
||||
path: "The path to list folders from.",
|
||||
directory: "The directory to list folders from. (Deprecated in favor of path)"
|
||||
},
|
||||
CREATE: {
|
||||
workspaceId: "The ID of the project to create the folder in.",
|
||||
environment: "The slug of the environment to create the folder in.",
|
||||
name: "The name of the folder to create.",
|
||||
path: "The path of the folder to create.",
|
||||
directory: "The directory of the folder to create. (Deprecated in favor of path)"
|
||||
},
|
||||
UPDATE: {
|
||||
folderId: "The ID of the folder to update.",
|
||||
environment: "The slug of the environment where the folder is located.",
|
||||
name: "The new name of the folder.",
|
||||
path: "The path of the folder to update.",
|
||||
directory: "The new directory of the folder to update. (Deprecated in favor of path)",
|
||||
workspaceId: "The ID of the project where the folder is located."
|
||||
},
|
||||
DELETE: {
|
||||
folderIdOrName: "The ID or name of the folder to delete.",
|
||||
workspaceId: "The ID of the project to delete the folder from.",
|
||||
environment: "The slug of the environment where the folder is located.",
|
||||
directory: "The directory of the folder to delete. (Deprecated in favor of path)",
|
||||
path: "The path of the folder to delete."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const SECRETS = {
|
||||
ATTACH_TAGS: {
|
||||
secretName: "The name of the secret to attach tags to.",
|
||||
secretPath: "The path of the secret to attach tags to.",
|
||||
type: "The type of the secret to attach tags to. (shared/personal)",
|
||||
environment: "The slug of the environment where the secret is located",
|
||||
projectSlug: "The slug of the project where the secret is located",
|
||||
tagSlugs: "An array of existing tag slugs to attach to the secret."
|
||||
},
|
||||
DETACH_TAGS: {
|
||||
secretName: "The name of the secret to detach tags from.",
|
||||
secretPath: "The path of the secret to detach tags from.",
|
||||
type: "The type of the secret to attach tags to. (shared/personal)",
|
||||
environment: "The slug of the environment where the secret is located",
|
||||
projectSlug: "The slug of the project where the secret is located",
|
||||
tagSlugs: "An array of existing tag slugs to detach from the secret."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const RAW_SECRETS = {
|
||||
LIST: {
|
||||
recursive: "Whether or not to fetch all secrets from the specified base path, and all of its subdirectories.",
|
||||
workspaceId: "The ID of the project to list secrets from.",
|
||||
workspaceSlug: "The slug of the project to list secrets from. This parameter is only usable by machine identities.",
|
||||
environment: "The slug of the environment to list secrets from.",
|
||||
secretPath: "The secret path to list secrets from.",
|
||||
includeImports: "Weather to include imported secrets or not."
|
||||
},
|
||||
CREATE: {
|
||||
secretName: "The name of the secret to create.",
|
||||
environment: "The slug of the environment to create the secret in.",
|
||||
secretComment: "Attach a comment to the secret.",
|
||||
secretPath: "The path to create the secret in.",
|
||||
secretValue: "The value of the secret to create.",
|
||||
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
|
||||
type: "The type of the secret to create.",
|
||||
workspaceId: "The ID of the project to create the secret in."
|
||||
},
|
||||
GET: {
|
||||
secretName: "The name of the secret to get.",
|
||||
workspaceId: "The ID of the project to get the secret from.",
|
||||
environment: "The slug of the environment to get the secret from.",
|
||||
secretPath: "The path of the secret to get.",
|
||||
version: "The version of the secret to get.",
|
||||
type: "The type of the secret to get.",
|
||||
includeImports: "Weather to include imported secrets or not."
|
||||
},
|
||||
UPDATE: {
|
||||
secretName: "The name of the secret to update.",
|
||||
environment: "The slug of the environment where the secret is located.",
|
||||
secretPath: "The path of the secret to update",
|
||||
secretValue: "The new value of the secret.",
|
||||
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
|
||||
type: "The type of the secret to update.",
|
||||
workspaceId: "The ID of the project to update the secret in."
|
||||
},
|
||||
DELETE: {
|
||||
secretName: "The name of the secret to delete.",
|
||||
environment: "The slug of the environment where the secret is located.",
|
||||
secretPath: "The path of the secret.",
|
||||
type: "The type of the secret to delete.",
|
||||
workspaceId: "The ID of the project where the secret is located."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const SECRET_IMPORTS = {
|
||||
LIST: {
|
||||
workspaceId: "The ID of the project to list secret imports from.",
|
||||
environment: "The slug of the environment to list secret imports from.",
|
||||
path: "The path to list secret imports from."
|
||||
},
|
||||
CREATE: {
|
||||
environment: "The slug of the environment to import into.",
|
||||
path: "The path to import into.",
|
||||
workspaceId: "The ID of the project you are working in.",
|
||||
import: {
|
||||
environment: "The slug of the environment to import from.",
|
||||
path: "The path to import from."
|
||||
}
|
||||
},
|
||||
UPDATE: {
|
||||
secretImportId: "The ID of the secret import to update.",
|
||||
environment: "The slug of the environment where the secret import is located.",
|
||||
import: {
|
||||
environment: "The new environment slug to import from.",
|
||||
path: "The new path to import from.",
|
||||
position: "The new position of the secret import. The lowest number will be displayed as the first import."
|
||||
},
|
||||
path: "The path of the secret import to update.",
|
||||
workspaceId: "The ID of the project where the secret import is located."
|
||||
},
|
||||
DELETE: {
|
||||
workspaceId: "The ID of the project to delete the secret import from.",
|
||||
secretImportId: "The ID of the secret import to delete.",
|
||||
environment: "The slug of the environment where the secret import is located.",
|
||||
path: "The path of the secret import to delete."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const AUDIT_LOGS = {
|
||||
EXPORT: {
|
||||
workspaceId: "The ID of the project to export audit logs from.",
|
||||
eventType: "The type of the event to export.",
|
||||
userAgentType: "Choose which consuming application to export audit logs for.",
|
||||
startDate: "The date to start the export from.",
|
||||
endDate: "The date to end the export at.",
|
||||
offset: "The offset to start from. If you enter 10, it will start from the 10th audit log.",
|
||||
limit: "The number of audit logs to return.",
|
||||
actor: "The actor to filter the audit logs by."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const DYNAMIC_SECRETS = {
|
||||
LIST: {
|
||||
projectSlug: "The slug of the project to create dynamic secret in.",
|
||||
environmentSlug: "The slug of the environment to list folders from.",
|
||||
path: "The path to list folders from."
|
||||
},
|
||||
LIST_LEAES_BY_NAME: {
|
||||
projectSlug: "The slug of the project to create dynamic secret in.",
|
||||
environmentSlug: "The slug of the environment to list folders from.",
|
||||
path: "The path to list folders from.",
|
||||
name: "The name of the dynamic secret."
|
||||
},
|
||||
GET_BY_NAME: {
|
||||
projectSlug: "The slug of the project to create dynamic secret in.",
|
||||
environmentSlug: "The slug of the environment to list folders from.",
|
||||
path: "The path to list folders from.",
|
||||
name: "The name of the dynamic secret."
|
||||
},
|
||||
CREATE: {
|
||||
projectSlug: "The slug of the project to create dynamic secret in.",
|
||||
environmentSlug: "The slug of the environment to create the dynamic secret in.",
|
||||
path: "The path to create the dynamic secret in.",
|
||||
name: "The name of the dynamic secret.",
|
||||
provider: "The type of dynamic secret.",
|
||||
defaultTTL: "The default TTL that will be applied for all the leases.",
|
||||
maxTTL: "The maximum limit a TTL can be leases or renewed."
|
||||
},
|
||||
UPDATE: {
|
||||
projectSlug: "The slug of the project to update dynamic secret in.",
|
||||
environmentSlug: "The slug of the environment to update the dynamic secret in.",
|
||||
path: "The path to update the dynamic secret in.",
|
||||
name: "The name of the dynamic secret.",
|
||||
inputs: "The new partial values for the configurated provider of the dynamic secret",
|
||||
defaultTTL: "The default TTL that will be applied for all the leases.",
|
||||
maxTTL: "The maximum limit a TTL can be leases or renewed.",
|
||||
newName: "The new name for the dynamic secret."
|
||||
},
|
||||
DELETE: {
|
||||
projectSlug: "The slug of the project to delete dynamic secret in.",
|
||||
environmentSlug: "The slug of the environment to delete the dynamic secret in.",
|
||||
path: "The path to delete the dynamic secret in.",
|
||||
name: "The name of the dynamic secret.",
|
||||
isForced:
|
||||
"A boolean flag to delete the the dynamic secret from infisical without trying to remove it from external provider. Used when the dynamic secret got modified externally."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const DYNAMIC_SECRET_LEASES = {
|
||||
GET_BY_LEASEID: {
|
||||
projectSlug: "The slug of the project to create dynamic secret in.",
|
||||
environmentSlug: "The slug of the environment to list folders from.",
|
||||
path: "The path to list folders from.",
|
||||
leaseId: "The ID of the dynamic secret lease."
|
||||
},
|
||||
CREATE: {
|
||||
projectSlug: "The slug of the project of the dynamic secret in.",
|
||||
environmentSlug: "The slug of the environment of the dynamic secret in.",
|
||||
path: "The path of the dynamic secret in.",
|
||||
dynamicSecretName: "The name of the dynamic secret.",
|
||||
ttl: "The lease lifetime ttl. If not provided the default TTL of dynamic secret will be used."
|
||||
},
|
||||
RENEW: {
|
||||
projectSlug: "The slug of the project of the dynamic secret in.",
|
||||
environmentSlug: "The slug of the environment of the dynamic secret in.",
|
||||
path: "The path of the dynamic secret in.",
|
||||
leaseId: "The ID of the dynamic secret lease.",
|
||||
ttl: "The renew TTL that gets added with current expiry (ensure it's below max TTL) for a total less than creation time + max TTL."
|
||||
},
|
||||
DELETE: {
|
||||
projectSlug: "The slug of the project of the dynamic secret in.",
|
||||
environmentSlug: "The slug of the environment of the dynamic secret in.",
|
||||
path: "The path of the dynamic secret in.",
|
||||
leaseId: "The ID of the dynamic secret lease.",
|
||||
isForced:
|
||||
"A boolean flag to delete the the dynamic secret from infisical without trying to remove it from external provider. Used when the dynamic secret got modified externally."
|
||||
}
|
||||
} as const;
|
||||
export const SECRET_TAGS = {
|
||||
LIST: {
|
||||
projectId: "The ID of the project to list tags from."
|
||||
},
|
||||
CREATE: {
|
||||
projectId: "The ID of the project to create the tag in.",
|
||||
name: "The name of the tag to create.",
|
||||
slug: "The slug of the tag to create.",
|
||||
color: "The color of the tag to create."
|
||||
},
|
||||
DELETE: {
|
||||
tagId: "The ID of the tag to delete.",
|
||||
projectId: "The ID of the project to delete the tag from."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const IDENTITY_ADDITIONAL_PRIVILEGE = {
|
||||
CREATE: {
|
||||
projectSlug: "The slug of the project of the identity in.",
|
||||
identityId: "The ID of the identity to delete.",
|
||||
slug: "The slug of the privilege to create.",
|
||||
permissions: `The permission object for the privilege.
|
||||
1. [["read", "secrets", {environment: "dev", secretPath: {$glob: "/"}}]]
|
||||
2. [["read", "secrets", {environment: "dev"}], ["create", "secrets", {environment: "dev"}]]
|
||||
2. [["read", "secrets", {environment: "dev"}]]
|
||||
`,
|
||||
isPackPermission: "Whether the server should pack(compact) the permission object.",
|
||||
isTemporary: "Whether the privilege is temporary.",
|
||||
temporaryMode: "Type of temporary access given. Types: relative",
|
||||
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
|
||||
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
||||
},
|
||||
UPDATE: {
|
||||
projectSlug: "The slug of the project of the identity in.",
|
||||
identityId: "The ID of the identity to update.",
|
||||
slug: "The slug of the privilege to update.",
|
||||
newSlug: "The new slug of the privilege to update.",
|
||||
permissions: `The permission object for the privilege.
|
||||
1. [["read", "secrets", {environment: "dev", secretPath: {$glob: "/"}}]]
|
||||
2. [["read", "secrets", {environment: "dev"}], ["create", "secrets", {environment: "dev"}]]
|
||||
2. [["read", "secrets", {environment: "dev"}]]
|
||||
`,
|
||||
isPackPermission: "Whether the server should pack(compact) the permission object.",
|
||||
isTemporary: "Whether the privilege is temporary.",
|
||||
temporaryMode: "Type of temporary access given. Types: relative",
|
||||
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
|
||||
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
||||
},
|
||||
DELETE: {
|
||||
projectSlug: "The slug of the project of the identity in.",
|
||||
identityId: "The ID of the identity to delete.",
|
||||
slug: "The slug of the privilege to delete."
|
||||
},
|
||||
GET_BY_SLUG: {
|
||||
projectSlug: "The slug of the project of the identity in.",
|
||||
identityId: "The ID of the identity to list.",
|
||||
slug: "The slug of the privilege."
|
||||
},
|
||||
LIST: {
|
||||
projectSlug: "The slug of the project of the identity in.",
|
||||
identityId: "The ID of the identity to list.",
|
||||
unpacked: "Whether the system should send the permissions as unpacked"
|
||||
}
|
||||
};
|
||||
|
||||
export const PROJECT_USER_ADDITIONAL_PRIVILEGE = {
|
||||
CREATE: {
|
||||
projectMembershipId: "Project membership id of user",
|
||||
slug: "The slug of the privilege to create.",
|
||||
permissions:
|
||||
"The permission object for the privilege. Refer https://casl.js.org/v6/en/guide/define-rules#the-shape-of-raw-rule to understand the shape",
|
||||
isPackPermission: "Whether the server should pack(compact) the permission object.",
|
||||
isTemporary: "Whether the privilege is temporary.",
|
||||
temporaryMode: "Type of temporary access given. Types: relative",
|
||||
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
|
||||
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
||||
},
|
||||
UPDATE: {
|
||||
privilegeId: "The id of privilege object",
|
||||
slug: "The slug of the privilege to create.",
|
||||
newSlug: "The new slug of the privilege to create.",
|
||||
permissions:
|
||||
"The permission object for the privilege. Refer https://casl.js.org/v6/en/guide/define-rules#the-shape-of-raw-rule to understand the shape",
|
||||
isPackPermission: "Whether the server should pack(compact) the permission object.",
|
||||
isTemporary: "Whether the privilege is temporary.",
|
||||
temporaryMode: "Type of temporary access given. Types: relative",
|
||||
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
|
||||
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
||||
},
|
||||
DELETE: {
|
||||
privilegeId: "The id of privilege object"
|
||||
},
|
||||
GET_BY_PRIVILEGEID: {
|
||||
privilegeId: "The id of privilege object"
|
||||
},
|
||||
LIST: {
|
||||
projectMembershipId: "Project membership id of user"
|
||||
}
|
||||
};
|
||||
|
||||
export const INTEGRATION_AUTH = {
|
||||
GET: {
|
||||
integrationAuthId: "The id of integration authentication object."
|
||||
},
|
||||
DELETE: {
|
||||
integration: "The slug of the integration to be unauthorized.",
|
||||
projectId: "The ID of the project to delete the integration auth from."
|
||||
},
|
||||
DELETE_BY_ID: {
|
||||
integrationAuthId: "The id of integration authentication object to delete."
|
||||
},
|
||||
CREATE_ACCESS_TOKEN: {
|
||||
workspaceId: "The ID of the project to create the integration auth for.",
|
||||
integration: "The slug of integration for the auth object.",
|
||||
accessId: "The unique authorized access id of the external integration provider.",
|
||||
accessToken: "The unique authorized access token of the external integration provider.",
|
||||
url: "",
|
||||
namespace: "",
|
||||
refreshToken: "The refresh token for integration authorization."
|
||||
},
|
||||
LIST_AUTHORIZATION: {
|
||||
workspaceId: "The ID of the project to list integration auths for."
|
||||
}
|
||||
};
|
||||
|
||||
export const INTEGRATION = {
|
||||
CREATE: {
|
||||
integrationAuthId: "The ID of the integration auth object to link with integration.",
|
||||
app: "The name of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.",
|
||||
isActive: "Whether the integration should be active or disabled.",
|
||||
appId:
|
||||
"The ID of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.",
|
||||
secretPath: "The path of the secrets to sync secrets from.",
|
||||
sourceEnvironment: "The environment to sync secret from.",
|
||||
targetEnvironment:
|
||||
"The target environment of the integration provider. Used in cloudflare pages, TeamCity, Gitlab integrations.",
|
||||
targetEnvironmentId:
|
||||
"The target environment id of the integration provider. Used in cloudflare pages, teamcity, gitlab integrations.",
|
||||
targetService:
|
||||
"The service based grouping identifier of the external provider. Used in Terraform cloud, Checkly, Railway and NorthFlank",
|
||||
targetServiceId:
|
||||
"The service based grouping identifier ID of the external provider. Used in Terraform cloud, Checkly, Railway and NorthFlank",
|
||||
owner: "External integration providers service entity owner. Used in Github.",
|
||||
path: "Path to save the synced secrets. Used by Gitlab, AWS Parameter Store, Vault",
|
||||
region: "AWS region to sync secrets to.",
|
||||
scope: "Scope of the provider. Used by Github, Qovery",
|
||||
metadata: {
|
||||
secretPrefix: "The prefix for the saved secret. Used by GCP",
|
||||
secretSuffix: "The suffix for the saved secret. Used by GCP",
|
||||
initialSyncBehavoir: "Type of syncing behavoir with the integration",
|
||||
shouldAutoRedeploy: "Used by Render to trigger auto deploy",
|
||||
secretGCPLabel: "The label for the GCP secrets"
|
||||
}
|
||||
},
|
||||
UPDATE: {
|
||||
integrationId: "The ID of the integration object.",
|
||||
app: "The name of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.",
|
||||
appId:
|
||||
"The ID of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.",
|
||||
isActive: "Whether the integration should be active or disabled.",
|
||||
secretPath: "The path of the secrets to sync secrets from.",
|
||||
owner: "External integration providers service entity owner. Used in Github.",
|
||||
targetEnvironment:
|
||||
"The target environment of the integration provider. Used in cloudflare pages, TeamCity, Gitlab integrations.",
|
||||
environment: "The environment to sync secrets from."
|
||||
},
|
||||
DELETE: {
|
||||
integrationId: "The ID of the integration object."
|
||||
}
|
||||
};
|
1
backend/src/lib/api-docs/index.ts
Normal file
1
backend/src/lib/api-docs/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./constants";
|
@ -15,8 +15,17 @@ const envSchema = z
|
||||
PORT: z.coerce.number().default(4000),
|
||||
REDIS_URL: zpStr(z.string()),
|
||||
HOST: zpStr(z.string().default("localhost")),
|
||||
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")),
|
||||
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")).default(
|
||||
`postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`
|
||||
),
|
||||
MAX_LEASE_LIMIT: z.coerce.number().default(10000),
|
||||
DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()),
|
||||
DB_HOST: zpStr(z.string().describe("Postgres database host").optional()),
|
||||
DB_PORT: zpStr(z.string().describe("Postgres database port").optional()).default("5432"),
|
||||
DB_USER: zpStr(z.string().describe("Postgres database username").optional()),
|
||||
DB_PASSWORD: zpStr(z.string().describe("Postgres database password").optional()),
|
||||
DB_NAME: zpStr(z.string().describe("Postgres database name").optional()),
|
||||
|
||||
NODE_ENV: z.enum(["development", "test", "production"]).default("production"),
|
||||
SALT_ROUNDS: z.coerce.number().default(10),
|
||||
INITIAL_ORGANIZATION_NAME: zpStr(z.string().optional()),
|
||||
@ -98,13 +107,15 @@ const envSchema = z
|
||||
LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")),
|
||||
LICENSE_SERVER_KEY: zpStr(z.string().optional()),
|
||||
LICENSE_KEY: zpStr(z.string().optional()),
|
||||
LICENSE_KEY_OFFLINE: zpStr(z.string().optional()),
|
||||
|
||||
// GENERIC
|
||||
STANDALONE_MODE: z
|
||||
.enum(["true", "false"])
|
||||
.transform((val) => val === "true")
|
||||
.optional(),
|
||||
INFISICAL_CLOUD: zodStrBool.default("false")
|
||||
INFISICAL_CLOUD: zodStrBool.default("false"),
|
||||
MAINTENANCE_MODE: zodStrBool.default("false")
|
||||
})
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
|
@ -17,4 +17,5 @@ export {
|
||||
decryptSecrets,
|
||||
decryptSecretVersions
|
||||
} from "./secret-encryption";
|
||||
export { verifyOfflineLicense } from "./signing";
|
||||
export { generateSrpServerKey, srpCheckClientProof } from "./srp";
|
||||
|
8
backend/src/lib/crypto/license_public_key.pem
Normal file
8
backend/src/lib/crypto/license_public_key.pem
Normal file
@ -0,0 +1,8 @@
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEApchBY3BXTu4zWGBguB7nM/pjpVLY3V7VGZOAxmR5ueQTJOwiGM13
|
||||
5HN3EM9fDlQnZu9VSc0OFqRM/bUeUaI1oLPE6WzTHjdHyKjDI/S+TLx3VGEsvhM1
|
||||
uukZpYX+3KX2w4wzRHBaBWyglFy0CVNth9UJhhpD+KKfv7dzcRmsbyoUWi9wGfJu
|
||||
wLYCwaCwZRXIt1sLGmMncPz14vfwdnm2a5Tj1Jbt0GTyBl+1/ZqLbO6SsslLg2G+
|
||||
o7FfGS9z8OUTkvDdu16qxL+p2wCEFZMnOz5BB4oakuT2gS9iOO2l5AOPcT4WzPzy
|
||||
PYbX3d7cN9BkOY9I5z0cX4wzqHjQTvGNLQIDAQAB
|
||||
-----END RSA PUBLIC KEY-----
|
22
backend/src/lib/crypto/signing.ts
Normal file
22
backend/src/lib/crypto/signing.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import crypto, { KeyObject } from "crypto";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
export const verifySignature = (data: string, signature: Buffer, publicKey: KeyObject) => {
|
||||
const verify = crypto.createVerify("SHA256");
|
||||
verify.update(data);
|
||||
verify.end();
|
||||
return verify.verify(publicKey, signature);
|
||||
};
|
||||
|
||||
export const verifyOfflineLicense = async (licenseContents: string, signature: string) => {
|
||||
const publicKeyPem = await fs.readFile(path.join(__dirname, "license_public_key.pem"), "utf8");
|
||||
|
||||
const publicKey = crypto.createPublicKey({
|
||||
key: publicKeyPem,
|
||||
format: "pem",
|
||||
type: "pkcs1"
|
||||
});
|
||||
|
||||
return verifySignature(licenseContents, Buffer.from(signature, "base64"), publicKey);
|
||||
};
|
@ -59,6 +59,18 @@ export class BadRequestError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export class DisableRotationErrors extends Error {
|
||||
name: string;
|
||||
|
||||
error: unknown;
|
||||
|
||||
constructor({ name, error, message }: { message: string; name?: string; error?: unknown }) {
|
||||
super(message);
|
||||
this.name = name || "DisableRotationErrors";
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
export class ScimRequestError extends Error {
|
||||
name: string;
|
||||
|
||||
|
11
backend/src/lib/knex/connection.ts
Normal file
11
backend/src/lib/knex/connection.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { URL } from "url"; // Import the URL class
|
||||
|
||||
export const getDbConnectionHost = (urlString: string) => {
|
||||
try {
|
||||
const url = new URL(urlString);
|
||||
// Split hostname and port (if provided)
|
||||
return url.hostname.split(":")[0];
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
@ -4,6 +4,7 @@ import { Tables } from "knex/types/tables";
|
||||
|
||||
import { DatabaseError } from "../errors";
|
||||
|
||||
export * from "./connection";
|
||||
export * from "./join";
|
||||
export * from "./select";
|
||||
|
||||
|
@ -1,17 +1,19 @@
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
|
||||
export type TOrgPermission = {
|
||||
actor: ActorType;
|
||||
actorId: string;
|
||||
orgId: string;
|
||||
actorOrgId?: string;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
actorOrgId: string | undefined;
|
||||
};
|
||||
|
||||
export type TProjectPermission = {
|
||||
actor: ActorType;
|
||||
actorId: string;
|
||||
projectId: string;
|
||||
actorOrgId?: string;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
actorOrgId: string;
|
||||
};
|
||||
|
||||
export type RequiredKeys<T> = {
|
||||
|
@ -18,7 +18,8 @@ export enum QueueName {
|
||||
SecretWebhook = "secret-webhook",
|
||||
SecretFullRepoScan = "secret-full-repo-scan",
|
||||
SecretPushEventScan = "secret-push-event-scan",
|
||||
UpgradeProjectToGhost = "upgrade-project-to-ghost"
|
||||
UpgradeProjectToGhost = "upgrade-project-to-ghost",
|
||||
DynamicSecretRevocation = "dynamic-secret-revocation"
|
||||
}
|
||||
|
||||
export enum QueueJobs {
|
||||
@ -30,7 +31,9 @@ export enum QueueJobs {
|
||||
TelemetryInstanceStats = "telemetry-self-hosted-stats",
|
||||
IntegrationSync = "secret-integration-pull",
|
||||
SecretScan = "secret-scan",
|
||||
UpgradeProjectToGhost = "upgrade-project-to-ghost-job"
|
||||
UpgradeProjectToGhost = "upgrade-project-to-ghost-job",
|
||||
DynamicSecretRevocation = "dynamic-secret-revocation",
|
||||
DynamicSecretPruning = "dynamic-secret-pruning"
|
||||
}
|
||||
|
||||
export type TQueueJobTypes = {
|
||||
@ -86,6 +89,19 @@ export type TQueueJobTypes = {
|
||||
name: QueueJobs.TelemetryInstanceStats;
|
||||
payload: undefined;
|
||||
};
|
||||
[QueueName.DynamicSecretRevocation]:
|
||||
| {
|
||||
name: QueueJobs.DynamicSecretRevocation;
|
||||
payload: {
|
||||
leaseId: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
name: QueueJobs.DynamicSecretPruning;
|
||||
payload: {
|
||||
dynamicSecretCfgId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user