Compare commits

...

153 Commits

Author SHA1 Message Date
Maidul Islam
a8264b17e4 Merge pull request #1689 from akhilmhdh/dynamic-secret/mysql
Dynamic secret mysql support
2024-04-15 15:51:17 -04:00
Maidul Islam
cb66733e6d remove password expire 2024-04-15 15:47:56 -04:00
Akhil Mohan
40a0691ccb feat(ui): added mysql dynamic secret 2024-04-15 19:35:29 +05:30
Akhil Mohan
6410d51033 feat(server): added mysql dynamic secret server logic 2024-04-15 19:34:47 +05:30
Maidul Islam
a0259712df Update aws-ecs.mdx 2024-04-15 03:36:12 -04:00
Maidul Islam
1132d07dea Merge pull request #1686 from Infisical/aws-reference-guide-ecs
AWS ECS reference architecture
2024-04-15 03:24:26 -04:00
Maidul Islam
1f0b1964b9 ecs reference architecture 2024-04-15 03:23:58 -04:00
Maidul Islam
fbc7b34786 Merge pull request #1683 from akhilmhdh/fix/audit-log-latency
fix: resolved slow audit log list
2024-04-14 11:54:11 -04:00
Akhil Mohan
9e6641c058 fix: resolved slow audit log list 2024-04-14 18:38:42 +05:30
Maidul Islam
d035403af1 Update kubernetes.mdx 2024-04-12 14:07:31 -04:00
Tuan Dang
1af0d958dd Update migration order for group 2024-04-12 10:50:06 -07:00
Maidul Islam
66a51658d7 Merge pull request #1682 from Infisical/k8s-owner-policy
add docs for owner policy
2024-04-12 12:52:09 -04:00
Maidul Islam
28dc3a4b1c add docs for owner policy 2024-04-12 12:49:45 -04:00
BlackMagiq
b27cadb651 Merge pull request #1638 from Infisical/groups
User Groups
2024-04-12 08:28:10 -07:00
Maidul Islam
3dca82ad2f Merge pull request #1680 from Infisical/daniel/recursive-max-depth
Fix: Hard limit on recursive secret fetching
2024-04-12 10:38:38 -04:00
Maidul Islam
1c90df9dd4 add log for secrets depth breakout 2024-04-12 10:34:59 -04:00
Maidul Islam
e15c9e72c6 allow inetgrations list fetch via UA 2024-04-12 10:06:16 -04:00
Daniel Hougaard
702cd0d403 Update secret-fns.ts 2024-04-12 13:31:48 +02:00
Daniel Hougaard
75267987fc Fix: Add recursive search max depth (20) 2024-04-12 13:28:03 +02:00
Daniel Hougaard
d734a3f6f4 Fix: Add hard recursion limit to documentation 2024-04-12 13:15:42 +02:00
vmatsiiako
cbb749e34a Update list-project-integrations.mdx 2024-04-11 23:52:25 -04:00
Tuan Dang
4535c1069a Fix merge conflicts 2024-04-11 20:46:14 -07:00
Tuan Dang
747acfe070 Resolve PR review issues 2024-04-11 20:44:38 -07:00
Tuan Dang
fa1b236f26 Disallow adding groups to E2EE projects 2024-04-11 19:52:10 -07:00
Tuan Dang
c98ef0eca8 Add pagination for user assignment modal 2024-04-11 19:33:19 -07:00
Maidul Islam
9f23106c6c Update list-project-integrations.mdx 2024-04-11 20:49:31 -04:00
Maidul Islam
1e7744b498 Merge pull request #1679 from Infisical/list-project-integrations-api
Expose List integratiosn API
2024-04-11 20:20:22 -04:00
Daniel Hougaard
44c736facd Fix: Updated descriptions 2024-04-12 02:15:23 +02:00
Daniel Hougaard
51928ddb47 Fix: OpenAPI doc descriptions structure 2024-04-12 02:15:11 +02:00
Daniel Hougaard
c7cded4af6 Merge pull request #1678 from Infisical/daniel/workspace-endpoint-fix
FIx: Fetching workspaces with no environments
2024-04-12 01:54:06 +02:00
Daniel Hougaard
8b56e20b42 Fix: Removed icon 2024-04-12 01:49:59 +02:00
Daniel Hougaard
39c2c37cc0 Remove log 2024-04-12 01:49:28 +02:00
Daniel Hougaard
3131ae7dae Feat: Disable integration creation when no environments are present on project 2024-04-12 01:46:19 +02:00
Daniel Hougaard
5315a67d74 Feat: Disable integration creation when no environments are present on project 2024-04-12 01:46:11 +02:00
Maidul Islam
79de7f9f5b expose list integrations api 2024-04-11 19:41:55 -04:00
Daniel Hougaard
71ffed026d FIx: Fetching workspaces with no environments 2024-04-12 00:52:22 +02:00
Vladyslav Matsiiako
ee98b15e2b fix typo 2024-04-11 17:43:13 -05:00
Maidul Islam
945d81ad4b update aws SES docs 2024-04-11 16:28:02 -04:00
Tuan Dang
ff8354605c Patch getProjectPermission 2024-04-11 13:00:50 -07:00
Tuan Dang
09b63eee90 Merge remote-tracking branch 'origin' into groups 2024-04-11 11:42:01 -07:00
Maidul Islam
d175256bb4 Merge pull request #1677 from Infisical/integration-auth-del-update
Integration Auth deletion upon Integration deletion
2024-04-11 14:30:31 -04:00
Tuan Dang
ee0c79d018 Delete integration auth upon integration deletion if no other integrations share the same auth 2024-04-11 11:25:28 -07:00
Maidul Islam
d5d7564550 Merge pull request #1643 from akhilmhdh/feat/import-sync-secret
fix(server): added sync secret for imports and added check for avoid cyclic import
2024-04-11 10:16:30 -04:00
Maidul Islam
0db682c5f0 remove depth from exceed message 2024-04-11 10:11:05 -04:00
Tuan Dang
a01a995585 Add comments to explain new getIntegrationSecrets 2024-04-11 10:11:05 -04:00
Tuan Dang
2ac785493a Add comments to explain new getIntegrationSecrets 2024-04-10 21:52:33 -07:00
Tuan Dang
85489a81ff Add resync on integration import creation/deletion and update forward/backward recursive logic for syncing dependent imports 2024-04-10 21:18:26 -07:00
Maidul Islam
7116c85f2c remove note 2024-04-09 20:51:19 -04:00
Maidul Islam
31e4da0dd3 Merge pull request #1672 from JunedKhan101/main
docs: fixed another broken link
2024-04-09 09:47:07 -07:00
Tuan Dang
f255d891ae Merge remote-tracking branch 'origin' into feat/import-sync-secret 2024-04-09 08:40:10 -07:00
Juned Khan
4774469244 docs: fixed another broken link 2024-04-09 14:05:45 +05:30
Maidul Islam
e143a31e79 Merge pull request #1670 from JunedKhan101/main
docs:fixed broken link
2024-04-08 17:45:51 -07:00
Maidul Islam
f6cc20b08b remove link from docs 2024-04-08 12:00:11 -07:00
Maidul Islam
90e125454e remove docs for e2ee 2024-04-08 11:58:48 -07:00
Maidul Islam
fbdf3dc9ce Merge pull request #1647 from akhilmhdh/doc/integration-api-guide
docs: added guide to setup integration with api
2024-04-08 11:56:19 -07:00
Maidul Islam
f333c905d9 revise generic integration docs 2024-04-08 11:55:38 -07:00
Maidul Islam
71e60df39a Merge pull request #1659 from agilesyndrome/fix_universalAuth_operatorinstall
fix: Run make kubectl-install
2024-04-08 10:18:00 -07:00
Maidul Islam
8b4d050d05 updated original value in replace script 2024-04-08 10:15:09 -07:00
Maidul Islam
3b4bb591a3 set default for NEXT_PUBLIC_SAML_ORG_SLUG 2024-04-08 09:25:37 -07:00
Maidul Islam
54f1a4416b add default value 2024-04-08 09:06:42 -07:00
Maidul Islam
47e3f1b510 Merge pull request #1661 from Infisical/saml-auto-redirect
add automatic SAML redirect
2024-04-08 08:22:04 -07:00
Juned Khan
5810b76027 docs:fixed broken link 2024-04-08 16:55:11 +05:30
Daniel Hougaard
246e6c64d1 Merge pull request #1668 from JunedKhan101/main
removed extra whitespace from error message
2024-04-07 16:05:31 -07:00
Juned Khan
4e836c5dca removed extra whitespace from error message 2024-04-07 17:41:39 +05:30
Maidul Islam
63a289c3be add saml org clug to standalone 2024-04-06 11:58:50 -07:00
Maidul Islam
0a52bbd55d add render once to use effect 2024-04-06 11:40:13 -07:00
Maidul Islam
593bdf74b8 patch notice 2024-04-06 10:57:08 -07:00
Maidul Islam
1f3742e619 update april_2024_db_update_closed 2024-04-06 10:39:21 -07:00
Maidul Islam
d6e5ac2133 maintenance postponed 2024-04-06 10:01:13 -07:00
Vladyslav Matsiiako
fea48518a3 removed new tag from identities 2024-04-05 19:01:54 -07:00
Vladyslav Matsiiako
94d509eb01 fixed search bar with folders 2024-04-05 18:37:12 -07:00
Vladyslav Matsiiako
055fd34c33 added baked env var 2024-04-05 17:25:25 -07:00
Tuan Dang
dc0d3b860e Continue making progress on SCIM groups 2024-04-05 17:20:17 -07:00
Vladyslav Matsiiako
74fefa9879 add automatic SAML redirect 2024-04-05 14:39:31 -07:00
Vladyslav Matsiiako
ff2c8d017f add automatic SAML redirect 2024-04-05 14:29:50 -07:00
Daniel Hougaard
ba1f8f4564 Merge pull request #1660 from Infisical/fix/delete-role-error
Fix: Error handling when deleting roles that are assigned to identities or users
2024-04-05 11:15:25 -07:00
Daniel Hougaard
e26df005c2 Fix: Typo 2024-04-05 11:11:32 -07:00
Daniel Hougaard
aca9b47f82 Fix: Typo 2024-04-05 11:11:26 -07:00
Daniel Hougaard
a16ce8899b Fix: Check for identities and project users who has the selected role before deleting 2024-04-05 11:11:15 -07:00
Daniel Hougaard
b61511d100 Update index.ts 2024-04-05 11:10:54 -07:00
Tuan Dang
f8ea421a0e Add group deletion and (name) update support for SCIM integration 2024-04-05 10:13:47 -07:00
Vladyslav Matsiiako
a945bdfc4c update docs style 2024-04-05 10:07:42 -07:00
Tuan Dang
f7b8345da4 Fix merge conflicts 2024-04-05 09:04:30 -07:00
Drew Easley
f6d7ec52c2 fix: Run make kubectl-install 2024-04-05 08:10:38 -04:00
Daniel Hougaard
3f6999b2e3 Merge pull request #1657 from Infisical/rate-limit
Add new rate limits for API
2024-04-04 19:53:31 -07:00
Maidul Islam
9128461409 Merge pull request #1658 from Infisical/daniel/delete-duplicate-org-memberships-migration
Feat: Delete duplicate memberships migration
2024-04-04 19:19:39 -07:00
Daniel Hougaard
893235c40f Update 20240405000045_org-memberships-unique-constraint.ts 2024-04-04 18:43:32 -07:00
Tuan Dang
d3cdaa8449 Add new rate limits 2024-04-04 18:12:23 -07:00
BlackMagiq
e0f655ae30 Merge pull request #1656 from Infisical/fix/duplicate-org-memberships
Fix: Duplicate organization memberships
2024-04-04 17:10:55 -07:00
Daniel Hougaard
93aeca3a38 Fix: Add unique constraint on orgId and userId 2024-04-04 17:04:23 -07:00
Daniel Hougaard
1edebdf8a5 Fix: Improve create migration script 2024-04-04 17:04:06 -07:00
BlackMagiq
1017707642 Merge pull request #1655 from Infisical/project-limit
Remove plan cache upon create/delete project
2024-04-04 13:21:32 -07:00
Tuan Dang
5639306303 Remove plan cache upon create/delete project 2024-04-04 13:17:46 -07:00
Tuan Dang
b3a9661755 Merge main 2024-04-04 12:24:28 -07:00
BlackMagiq
72f50ec399 Merge pull request #1654 from Infisical/fix-additional-privilege-slug
Move default slug init for users/identities out of fastify schema
2024-04-04 12:22:31 -07:00
Tuan Dang
effc7a3627 Move default slug init for users/identities out of fastify schema 2024-04-04 12:18:10 -07:00
Tuan Dang
175ce865aa Move group migration to top 2024-04-04 12:06:10 -07:00
Tuan Dang
51f220ba2c Fix getProjectMembership to work with additional privileges 2024-04-04 11:20:39 -07:00
Tuan Dang
51819e57d1 Address merge conflicts 2024-04-04 10:01:19 -07:00
Maidul Islam
510c91cef1 Update infisical-agent.mdx 2024-04-04 09:34:31 -07:00
Vladyslav Matsiiako
9be5d89fcf added docs images 2024-04-03 22:55:47 -07:00
Vladyslav Matsiiako
94f4497903 update access request docs 2024-04-03 22:51:48 -07:00
Tuan Dang
e1d9f779b2 Remove role and roleId from group project membership 2024-04-03 20:30:14 -07:00
BlackMagiq
b5af5646ee Merge pull request #1653 from Infisical/pentest
Add separate rate limit to invite user to org
2024-04-03 18:48:24 -07:00
Tuan Dang
1554618167 Add separate rate limit to invite user 2024-04-03 18:46:29 -07:00
Maidul Islam
5fbfcdda30 Merge pull request #1651 from Infisical/daniel/cli-secrets-get-fix
Fix: CLI get secrets by name
2024-04-03 10:43:42 -07:00
Daniel Hougaard
cdbb3b9c47 Update secrets.go 2024-04-03 10:36:25 -07:00
Vladyslav Matsiiako
0042a95b21 update docs image 2024-04-03 09:08:53 -07:00
BlackMagiq
53233e05d4 Merge pull request #1648 from Infisical/keycloak
Add documentation + option for Keycloak SAML (self-hosted)
2024-04-02 16:42:41 -07:00
Tuan Dang
4f15f9c8d3 Add support for keycloak saml on self-hosted infisical 2024-04-02 16:35:37 -07:00
Maidul Islam
97223fabe6 Merge pull request #1617 from Infisical/daniel/improve-create-project
Feat: Recursively get all secrets from all folders in specified path
2024-04-02 13:50:16 -07:00
Maidul Islam
04b312cbe4 Merge pull request #1646 from akhilmhdh/fix/disable-role-button
fix(ui): resolved multi role modal button hiding clickable
2024-04-02 13:06:57 -07:00
Akhil Mohan
40bb9668fe docs: added guide to setup integration with api 2024-04-03 01:12:30 +05:30
Akhil Mohan
93146fcd96 fix(ui): resolved multi role modal button hiding clickable 2024-04-03 00:12:34 +05:30
Akhil Mohan
abd62867eb fix(server): resolved failing test in import 2024-04-02 13:55:26 +05:30
Akhil Mohan
179573a269 fix(server): added sync secret for imports and added check for avoiding cyclic import 2024-04-02 13:20:48 +05:30
Tuan Dang
457edef5fe Merge remote-tracking branch 'origin/groups' into groups 2024-04-01 11:34:30 -07:00
Tuan Dang
f0b84d5bc9 Begin add push groups SCIM 2024-04-01 11:30:25 -07:00
Daniel Hougaard
36bf1b2abc Fix: Renamed deep parameter to recursive 2024-04-01 10:10:49 -07:00
Daniel Hougaard
42fb732955 Fix: Renamed deep parameter to recursive 2024-04-01 10:10:34 -07:00
Daniel Hougaard
da2dcb347a Fix: Restructured recursive path functions as suggested by Akhil 2024-04-01 09:58:13 -07:00
Daniel Hougaard
b9482966cf Fix: Replaced merge with extend as proposed by Akhil 2024-04-01 09:52:49 -07:00
Akhil Mohan
9fddcea3db fix(ui): sending group users without orgid 2024-04-01 17:15:57 +05:30
Daniel Hougaard
4c496d5e3d Update secret-service.ts 2024-03-30 08:40:43 +01:00
Tuan Dang
0c2e566184 Add docs for groups 2024-03-29 17:49:20 -07:00
Tuan Dang
38adc83f2b Rename group fns and add upgrade plan modal to project level groups tab 2024-03-28 20:54:25 -07:00
Tuan Dang
f2e5f1bb10 Fix lint issues 2024-03-28 20:22:58 -07:00
Tuan Dang
9460eafd91 Add API specs to groups endpoints, convert projectId groups endpoints to be slug-based 2024-03-28 18:21:12 -07:00
Tuan Dang
8afecac7d8 Rely on actorOrgId for group orgId refs 2024-03-28 17:00:33 -07:00
Tuan Dang
bf13b81c0f Fix type issues 2024-03-28 12:55:18 -07:00
Tuan Dang
c753a91958 run linter 2024-03-28 12:44:44 -07:00
Tuan Dang
695a4a34b5 Fix merge conflicts 2024-03-28 12:41:34 -07:00
Tuan Dang
372f71f2b0 Add/remove bulk users to projects upon add/remove users to/from groups 2024-03-28 12:18:44 -07:00
Tuan Dang
0da6262ead Complete logic for user provisioning/deprovisioning to projects with groups 2024-03-27 10:53:51 -07:00
Daniel Hougaard
4f05e4ce93 Fix test case 2024-03-26 19:41:48 +01:00
Daniel Hougaard
2e8680c5d4 Update secret-service.ts 2024-03-26 19:36:18 +01:00
Daniel Hougaard
e5136c9ef5 Feat: Recursively get all secrets 2024-03-26 19:36:18 +01:00
Daniel Hougaard
812fe5cf31 Feat: Recursively get all secrets 2024-03-26 19:36:18 +01:00
Daniel Hougaard
50082e192c Feat: Recursively get all secrets, findByFolderIds DLA 2024-03-26 19:35:45 +01:00
Daniel Hougaard
1e1b5d655e Fix: Refactored secret fetching to be more performant 2024-03-26 19:35:45 +01:00
Daniel Hougaard
3befd90723 Fix: Refactor to in-memory approach 2024-03-26 19:35:45 +01:00
Daniel Hougaard
88549f4030 Feat: Deep search support 2024-03-26 19:35:45 +01:00
Daniel Hougaard
46a638cc63 FIx: Rename parameter from recursive to deep 2024-03-26 19:35:45 +01:00
Daniel Hougaard
566f7e4c61 Feat: Recursively get all secrets from inside path 2024-03-26 19:35:45 +01:00
Daniel Hougaard
9ff3210ed6 Feat: Recursively get all secrets from inside path 2024-03-26 19:35:12 +01:00
Daniel Hougaard
f91a6683c2 Fix: Rename parameter 2024-03-26 19:35:12 +01:00
Daniel Hougaard
c29cb667d7 Feat: Recursively get secrets from all nested secret paths 2024-03-26 19:35:12 +01:00
Tuan Dang
8ffbaa2f6c Minor group validation changes 2024-03-20 14:59:42 -07:00
Tuan Dang
796d5e3540 Complete preliminary list, update, create group in project 2024-03-20 14:47:12 -07:00
Tuan Dang
686b88fc97 Complete basic pre-cleaned group member assignment/unassignment 2024-03-19 18:23:52 -07:00
Tuan Dang
2a134b9dc2 Weave roles into groups 2024-03-19 11:34:28 -07:00
Tuan Dang
d8d63ecaec Merge remote-tracking branch 'origin' into groups 2024-03-19 10:25:03 -07:00
Tuan Dang
efc186ae6c Finish basic CRUD groups 2024-03-19 10:20:51 -07:00
253 changed files with 7410 additions and 1612 deletions

2
.gitignore vendored
View File

@@ -59,6 +59,8 @@ yarn-error.log*
# Infisical init
.infisical.json
.infisicalignore
# Editor specific
.vscode/*

View File

@@ -1 +1,5 @@
.github/resources/docker-compose.be-test.yml:generic-api-key:16
frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/IdentityRbacSection.tsx:generic-api-key:206
frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:304
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/MemberRbacSection.tsx:generic-api-key:206
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:292

View File

@@ -1,6 +1,7 @@
ARG POSTHOG_HOST=https://app.posthog.com
ARG POSTHOG_API_KEY=posthog-api-key
ARG INTERCOM_ID=intercom-id
ARG SAML_ORG_SLUG=saml-org-slug-default
FROM node:20-alpine AS base
@@ -35,6 +36,8 @@ ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
ARG INFISICAL_PLATFORM_VERSION
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
ARG SAML_ORG_SLUG
ENV NEXT_PUBLIC_SAML_ORG_SLUG=$SAML_ORG_SLUG
# Build
RUN npm run build
@@ -100,6 +103,9 @@ ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
ARG INTERCOM_ID=intercom-id
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
ARG SAML_ORG_SLUG
ENV NEXT_PUBLIC_SAML_ORG_SLUG=$SAML_ORG_SLUG \
BAKED_NEXT_PUBLIC_SAML_ORG_SLUG=$SAML_ORG_SLUG
WORKDIR /

View File

@@ -46,7 +46,7 @@ const deleteSecretImport = async (id: string) => {
describe("Secret Import Router", async () => {
test.each([
{ importEnv: "dev", importPath: "/" }, // one in root
{ importEnv: "prod", importPath: "/" }, // one in root
{ importEnv: "staging", importPath: "/" } // then create a deep one creating intermediate ones
])("Create secret import $importEnv with path $importPath", async ({ importPath, importEnv }) => {
// check for default environments
@@ -66,7 +66,7 @@ describe("Secret Import Router", async () => {
});
test("Get secret imports", async () => {
const createdImport1 = await createSecretImport("/", "dev");
const createdImport1 = await createSecretImport("/", "prod");
const createdImport2 = await createSecretImport("/", "staging");
const res = await testServer.inject({
method: "GET",
@@ -103,10 +103,10 @@ describe("Secret Import Router", async () => {
});
test("Update secret import position", async () => {
const devImportDetails = { path: "/", envSlug: "dev" };
const prodImportDetails = { path: "/", envSlug: "prod" };
const stagingImportDetails = { path: "/", envSlug: "staging" };
const createdImport1 = await createSecretImport(devImportDetails.path, devImportDetails.envSlug);
const createdImport1 = await createSecretImport(prodImportDetails.path, prodImportDetails.envSlug);
const createdImport2 = await createSecretImport(stagingImportDetails.path, stagingImportDetails.envSlug);
const updateImportRes = await testServer.inject({
@@ -136,7 +136,7 @@ describe("Secret Import Router", async () => {
position: 2,
importEnv: expect.objectContaining({
name: expect.any(String),
slug: expect.stringMatching(devImportDetails.envSlug),
slug: expect.stringMatching(prodImportDetails.envSlug),
id: expect.any(String)
})
})
@@ -166,7 +166,7 @@ describe("Secret Import Router", async () => {
});
test("Delete secret import position", async () => {
const createdImport1 = await createSecretImport("/", "dev");
const createdImport1 = await createSecretImport("/", "prod");
const createdImport2 = await createSecretImport("/", "staging");
const deletedImport = await deleteSecretImport(createdImport1.id);
// check for default environments

View File

@@ -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: {

View File

@@ -7,10 +7,10 @@ const prompt = promptSync({ sigint: true });
const migrationName = prompt("Enter name for migration: ");
// Remove spaces from migration name and replace with hyphens
const formattedMigrationName = migrationName.replace(/\s+/g, "-");
execSync(
`npx knex migrate:make --knexfile ${path.join(
__dirname,
"../src/db/knexfile.ts"
)} -x ts ${migrationName}`,
`npx knex migrate:make --knexfile ${path.join(__dirname, "../src/db/knexfile.ts")} -x ts ${formattedMigrationName}`,
{ stdio: "inherit" }
);

View File

@@ -5,6 +5,7 @@ import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-se
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 { TGroupServiceFactory } from "@app/ee/services/group/group-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";
@@ -25,6 +26,7 @@ import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
@@ -89,6 +91,8 @@ declare module "fastify" {
orgRole: TOrgRoleServiceFactory;
superAdmin: TSuperAdminServiceFactory;
user: TUserServiceFactory;
group: TGroupServiceFactory;
groupProject: TGroupProjectServiceFactory;
apiKey: TApiKeyServiceFactory;
project: TProjectServiceFactory;
projectMembership: TProjectMembershipServiceFactory;

View File

@@ -29,6 +29,15 @@ import {
TGitAppOrg,
TGitAppOrgInsert,
TGitAppOrgUpdate,
TGroupProjectMembershipRoles,
TGroupProjectMembershipRolesInsert,
TGroupProjectMembershipRolesUpdate,
TGroupProjectMemberships,
TGroupProjectMembershipsInsert,
TGroupProjectMembershipsUpdate,
TGroups,
TGroupsInsert,
TGroupsUpdate,
TIdentities,
TIdentitiesInsert,
TIdentitiesUpdate,
@@ -188,6 +197,9 @@ import {
TUserEncryptionKeys,
TUserEncryptionKeysInsert,
TUserEncryptionKeysUpdate,
TUserGroupMembership,
TUserGroupMembershipInsert,
TUserGroupMembershipUpdate,
TUsers,
TUsersInsert,
TUsersUpdate,
@@ -199,6 +211,22 @@ import {
declare module "knex/types/tables" {
interface Tables {
[TableName.Users]: Knex.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>;
[TableName.Groups]: Knex.CompositeTableType<TGroups, TGroupsInsert, TGroupsUpdate>;
[TableName.UserGroupMembership]: Knex.CompositeTableType<
TUserGroupMembership,
TUserGroupMembershipInsert,
TUserGroupMembershipUpdate
>;
[TableName.GroupProjectMembership]: Knex.CompositeTableType<
TGroupProjectMemberships,
TGroupProjectMembershipsInsert,
TGroupProjectMembershipsUpdate
>;
[TableName.GroupProjectMembershipRole]: Knex.CompositeTableType<
TGroupProjectMembershipRoles,
TGroupProjectMembershipRolesInsert,
TGroupProjectMembershipRolesUpdate
>;
[TableName.UserAliases]: Knex.CompositeTableType<TUserAliases, TUserAliasesInsert, TUserAliasesUpdate>;
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
TUserEncryptionKeys,

View File

@@ -0,0 +1,111 @@
import { Knex } from "knex";
import { z } from "zod";
import { TableName, TOrgMemberships } from "../schemas";
const validateOrgMembership = (membershipToValidate: TOrgMemberships, firstMembership: TOrgMemberships) => {
const firstOrgId = firstMembership.orgId;
const firstUserId = firstMembership.userId;
if (membershipToValidate.id === firstMembership.id) {
return;
}
if (membershipToValidate.inviteEmail !== firstMembership.inviteEmail) {
throw new Error(`Invite emails are different for the same userId and orgId: ${firstUserId}, ${firstOrgId}`);
}
if (membershipToValidate.orgId !== firstMembership.orgId) {
throw new Error(`OrgIds are different for the same userId and orgId: ${firstUserId}, ${firstOrgId}`);
}
if (membershipToValidate.role !== firstMembership.role) {
throw new Error(`Roles are different for the same userId and orgId: ${firstUserId}, ${firstOrgId}`);
}
if (membershipToValidate.roleId !== firstMembership.roleId) {
throw new Error(`RoleIds are different for the same userId and orgId: ${firstUserId}, ${firstOrgId}`);
}
if (membershipToValidate.status !== firstMembership.status) {
throw new Error(`Statuses are different for the same userId and orgId: ${firstUserId}, ${firstOrgId}`);
}
if (membershipToValidate.userId !== firstMembership.userId) {
throw new Error(`UserIds are different for the same userId and orgId: ${firstUserId}, ${firstOrgId}`);
}
};
export async function up(knex: Knex): Promise<void> {
const RowSchema = z.object({
userId: z.string(),
orgId: z.string(),
cnt: z.string()
});
// Transactional find and delete duplicate rows
await knex.transaction(async (tx) => {
const duplicateRows = await tx(TableName.OrgMembership)
.select("userId", "orgId") // Select the userId and orgId so we can group by them
.count("* as cnt") // Count the number of rows for each userId and orgId, so we can make sure there are more than 1 row (a duplicate)
.groupBy("userId", "orgId")
.havingRaw("count(*) > ?", [1]); // Using havingRaw for direct SQL expressions
// Parse the rows to ensure they are in the correct format, and for type safety
const parsedRows = RowSchema.array().parse(duplicateRows);
// For each of the duplicate rows, loop through and find the actual memberships to delete
for (const row of parsedRows) {
const count = Number(row.cnt);
// An extra check to ensure that the count is actually a number, and the number is greater than 2
if (typeof count !== "number" || count < 2) {
// eslint-disable-next-line no-continue
continue;
}
// Find all the organization memberships that have the same userId and orgId
// eslint-disable-next-line no-await-in-loop
const rowsToDelete = await tx(TableName.OrgMembership).where({
userId: row.userId,
orgId: row.orgId
});
// Ensure that all the rows have exactly the same value, except id, createdAt, updatedAt
for (const rowToDelete of rowsToDelete) {
validateOrgMembership(rowToDelete, rowsToDelete[0]);
}
// Find the row with the latest createdAt, which we will keep
let lowestCreatedAt: number | null = null;
let latestCreatedRow: TOrgMemberships | null = null;
for (const rowToDelete of rowsToDelete) {
if (lowestCreatedAt === null || rowToDelete.createdAt.getTime() < lowestCreatedAt) {
lowestCreatedAt = rowToDelete.createdAt.getTime();
latestCreatedRow = rowToDelete;
}
}
if (!latestCreatedRow) {
throw new Error("Failed to find last created membership");
}
// Filter out the latest row from the rows to delete
const membershipIdsToDelete = rowsToDelete.map((r) => r.id).filter((id) => id !== latestCreatedRow!.id);
// eslint-disable-next-line no-await-in-loop
const numberOfRowsDeleted = await tx(TableName.OrgMembership).whereIn("id", membershipIdsToDelete).delete();
// eslint-disable-next-line no-console
console.log(
`Deleted ${numberOfRowsDeleted} duplicate organization memberships for ${row.userId} and ${row.orgId}`
);
}
});
await knex.schema.alterTable(TableName.OrgMembership, (table) => {
table.unique(["userId", "orgId"]);
});
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.OrgMembership, (table) => {
table.dropUnique(["userId", "orgId"]);
});
}

View File

@@ -0,0 +1,82 @@
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.Groups))) {
await knex.schema.createTable(TableName.Groups, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.string("name").notNullable();
t.string("slug").notNullable();
t.unique(["orgId", "slug"]);
t.string("role").notNullable();
t.uuid("roleId");
t.foreign("roleId").references("id").inTable(TableName.OrgRoles);
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.Groups);
if (!(await knex.schema.hasTable(TableName.UserGroupMembership))) {
await knex.schema.createTable(TableName.UserGroupMembership, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); // link to user and link to groups cascade on groups
t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.uuid("groupId").notNullable();
t.foreign("groupId").references("id").inTable(TableName.Groups).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.UserGroupMembership);
if (!(await knex.schema.hasTable(TableName.GroupProjectMembership))) {
await knex.schema.createTable(TableName.GroupProjectMembership, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.uuid("groupId").notNullable();
t.foreign("groupId").references("id").inTable(TableName.Groups).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.GroupProjectMembership);
if (!(await knex.schema.hasTable(TableName.GroupProjectMembershipRole))) {
await knex.schema.createTable(TableName.GroupProjectMembershipRole, (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.GroupProjectMembership).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.GroupProjectMembershipRole);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.GroupProjectMembershipRole);
await dropOnUpdateTrigger(knex, TableName.GroupProjectMembershipRole);
await knex.schema.dropTableIfExists(TableName.UserGroupMembership);
await dropOnUpdateTrigger(knex, TableName.UserGroupMembership);
await knex.schema.dropTableIfExists(TableName.GroupProjectMembership);
await dropOnUpdateTrigger(knex, TableName.GroupProjectMembership);
await knex.schema.dropTableIfExists(TableName.Groups);
await dropOnUpdateTrigger(knex, TableName.Groups);
}

View 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 GroupProjectMembershipRolesSchema = 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 TGroupProjectMembershipRoles = z.infer<typeof GroupProjectMembershipRolesSchema>;
export type TGroupProjectMembershipRolesInsert = Omit<
z.input<typeof GroupProjectMembershipRolesSchema>,
TImmutableDBKeys
>;
export type TGroupProjectMembershipRolesUpdate = Partial<
Omit<z.input<typeof GroupProjectMembershipRolesSchema>, TImmutableDBKeys>
>;

View File

@@ -0,0 +1,22 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const GroupProjectMembershipsSchema = z.object({
id: z.string().uuid(),
projectId: z.string(),
groupId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TGroupProjectMemberships = z.infer<typeof GroupProjectMembershipsSchema>;
export type TGroupProjectMembershipsInsert = Omit<z.input<typeof GroupProjectMembershipsSchema>, TImmutableDBKeys>;
export type TGroupProjectMembershipsUpdate = Partial<
Omit<z.input<typeof GroupProjectMembershipsSchema>, TImmutableDBKeys>
>;

View File

@@ -0,0 +1,23 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const GroupsSchema = z.object({
id: z.string().uuid(),
orgId: z.string().uuid(),
name: z.string(),
slug: z.string(),
role: z.string(),
roleId: z.string().uuid().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TGroups = z.infer<typeof GroupsSchema>;
export type TGroupsInsert = Omit<z.input<typeof GroupsSchema>, TImmutableDBKeys>;
export type TGroupsUpdate = Partial<Omit<z.input<typeof GroupsSchema>, TImmutableDBKeys>>;

View File

@@ -7,6 +7,9 @@ export * from "./dynamic-secret-leases";
export * from "./dynamic-secrets";
export * from "./git-app-install-sessions";
export * from "./git-app-org";
export * from "./group-project-membership-roles";
export * from "./group-project-memberships";
export * from "./groups";
export * from "./identities";
export * from "./identity-access-tokens";
export * from "./identity-org-memberships";
@@ -61,5 +64,6 @@ export * from "./trusted-ips";
export * from "./user-actions";
export * from "./user-aliases";
export * from "./user-encryption-keys";
export * from "./user-group-membership";
export * from "./users";
export * from "./webhooks";

View File

@@ -2,6 +2,10 @@ import { z } from "zod";
export enum TableName {
Users = "users",
Groups = "groups",
GroupProjectMembership = "group_project_memberships",
GroupProjectMembershipRole = "group_project_membership_roles",
UserGroupMembership = "user_group_membership",
UserAliases = "user_aliases",
UserEncryptionKey = "user_encryption_keys",
AuthTokens = "auth_tokens",

View File

@@ -0,0 +1,20 @@
// 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 UserGroupMembershipSchema = z.object({
id: z.string().uuid(),
userId: z.string().uuid(),
groupId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TUserGroupMembership = z.infer<typeof UserGroupMembershipSchema>;
export type TUserGroupMembershipInsert = Omit<z.input<typeof UserGroupMembershipSchema>, TImmutableDBKeys>;
export type TUserGroupMembershipUpdate = Partial<Omit<z.input<typeof UserGroupMembershipSchema>, TImmutableDBKeys>>;

View File

@@ -5,14 +5,18 @@ 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({
url: "/",
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
dynamicSecretName: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.dynamicSecretName).toLowerCase(),
@@ -55,8 +59,11 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
});
server.route({
url: "/:leaseId",
method: "DELETE",
url: "/:leaseId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.DELETE.leaseId)
@@ -94,8 +101,11 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
});
server.route({
url: "/:leaseId/renew",
method: "POST",
url: "/:leaseId/renew",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.leaseId)
@@ -146,6 +156,9 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
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)

View File

@@ -7,14 +7,18 @@ import { DynamicSecretProviderSchema } from "@app/ee/services/dynamic-secret/pro
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({
url: "/",
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.CREATE.projectSlug),
@@ -74,8 +78,11 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
});
server.route({
url: "/:name",
method: "PATCH",
url: "/:name",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.UPDATE.name)
@@ -138,8 +145,11 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
});
server.route({
url: "/:name",
method: "DELETE",
url: "/:name",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.DELETE.name)
@@ -173,6 +183,9 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
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)
@@ -207,6 +220,9 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
server.route({
url: "/",
method: "GET",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST.projectSlug),
@@ -235,6 +251,9 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
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)

View File

@@ -0,0 +1,220 @@
import slugify from "@sindresorhus/slugify";
import { z } from "zod";
import { GroupsSchema, OrgMembershipRole, UsersSchema } from "@app/db/schemas";
import { GROUPS } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerGroupRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
name: z.string().trim().min(1).max(50).describe(GROUPS.CREATE.name),
slug: z
.string()
.min(5)
.max(36)
.refine((v) => slugify(v) === v, {
message: "Slug must be a valid slug"
})
.optional()
.describe(GROUPS.CREATE.slug),
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess).describe(GROUPS.CREATE.role)
}),
response: {
200: GroupsSchema
}
},
handler: async (req) => {
const group = await server.services.group.createGroup({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
return group;
}
});
server.route({
url: "/:currentSlug",
method: "PATCH",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
currentSlug: z.string().trim().describe(GROUPS.UPDATE.currentSlug)
}),
body: z
.object({
name: z.string().trim().min(1).describe(GROUPS.UPDATE.name),
slug: z
.string()
.min(5)
.max(36)
.refine((v) => slugify(v) === v, {
message: "Slug must be a valid slug"
})
.describe(GROUPS.UPDATE.slug),
role: z.string().trim().min(1).describe(GROUPS.UPDATE.role)
})
.partial(),
response: {
200: GroupsSchema
}
},
handler: async (req) => {
const group = await server.services.group.updateGroup({
currentSlug: req.params.currentSlug,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
return group;
}
});
server.route({
url: "/:slug",
method: "DELETE",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
slug: z.string().trim().describe(GROUPS.DELETE.slug)
}),
response: {
200: GroupsSchema
}
},
handler: async (req) => {
const group = await server.services.group.deleteGroup({
groupSlug: req.params.slug,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return group;
}
});
server.route({
method: "GET",
url: "/:slug/users",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
slug: z.string().trim().describe(GROUPS.LIST_USERS.slug)
}),
querystring: z.object({
offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset),
limit: z.coerce.number().min(1).max(100).default(10).describe(GROUPS.LIST_USERS.limit),
username: z.string().optional().describe(GROUPS.LIST_USERS.username)
}),
response: {
200: z.object({
users: UsersSchema.pick({
email: true,
username: true,
firstName: true,
lastName: true,
id: true
})
.merge(
z.object({
isPartOfGroup: z.boolean()
})
)
.array(),
totalCount: z.number()
})
}
},
handler: async (req) => {
const { users, totalCount } = await server.services.group.listGroupUsers({
groupSlug: req.params.slug,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query
});
return { users, totalCount };
}
});
server.route({
method: "POST",
url: "/:slug/users/:username",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
slug: z.string().trim().describe(GROUPS.ADD_USER.slug),
username: z.string().trim().describe(GROUPS.ADD_USER.username)
}),
response: {
200: UsersSchema.pick({
email: true,
username: true,
firstName: true,
lastName: true,
id: true
})
}
},
handler: async (req) => {
const user = await server.services.group.addUserToGroup({
groupSlug: req.params.slug,
username: req.params.username,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return user;
}
});
server.route({
method: "DELETE",
url: "/:slug/users/:username",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
slug: z.string().trim().describe(GROUPS.DELETE_USER.slug),
username: z.string().trim().describe(GROUPS.DELETE_USER.username)
}),
response: {
200: UsersSchema.pick({
email: true,
username: true,
firstName: true,
lastName: true,
id: true
})
}
},
handler: async (req) => {
const user = await server.services.group.removeUserFromGroup({
groupSlug: req.params.slug,
username: req.params.username,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return user;
}
});
};

View File

@@ -9,13 +9,17 @@ import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/service
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({
url: "/permanent",
method: "POST",
url: "/permanent",
config: {
rateLimit: writeLimit
},
schema: {
description: "Create a permanent or a non expiry specific privilege for identity.",
security: [
@@ -31,11 +35,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
.min(1)
.max(60)
.trim()
.default(slugify(alphaNumericNanoId(12)))
.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)
}),
@@ -53,6 +57,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
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))
});
@@ -61,8 +66,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
});
server.route({
url: "/temporary",
method: "POST",
url: "/temporary",
config: {
rateLimit: writeLimit
},
schema: {
description: "Create a temporary or a expiring specific privilege for identity.",
security: [
@@ -78,11 +86,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
.min(1)
.max(60)
.trim()
.default(slugify(alphaNumericNanoId(12)))
.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
@@ -111,6 +119,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
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))
});
@@ -119,8 +128,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
});
server.route({
url: "/",
method: "PATCH",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
description: "Update a specific privilege of an identity.",
security: [
@@ -188,8 +200,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
});
server.route({
url: "/",
method: "DELETE",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
description: "Delete a specific privilege of an identity.",
security: [
@@ -224,8 +239,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
});
server.route({
url: "/:privilegeSlug",
method: "GET",
url: "/:privilegeSlug",
config: {
rateLimit: readLimit
},
schema: {
description: "Retrieve details of a specific privilege by privilege slug.",
security: [
@@ -261,8 +279,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
});
server.route({
url: "/",
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
description: "List of a specific privilege of an identity in a project.",
security: [

View File

@@ -1,5 +1,6 @@
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
import { registerGroupRouter } from "./group-router";
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
import { registerLdapRouter } from "./ldap-router";
import { registerLicenseRouter } from "./license-router";
@@ -53,6 +54,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
await server.register(registerGroupRouter, { prefix: "/groups" });
await server.register(
async (privilegeRouter) => {
await privilegeRouter.register(registerUserAdditionalPrivilegeRouter, { prefix: "/users" });

View File

@@ -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({
@@ -130,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({
@@ -164,6 +171,9 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/config",
method: "PATCH",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z

View File

@@ -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() }),
@@ -32,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: {
@@ -54,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() }),
@@ -77,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() }),
@@ -103,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: {
@@ -123,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: {
@@ -145,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: {
@@ -167,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: {
@@ -189,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({
@@ -217,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: {
@@ -239,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({
@@ -267,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(),
@@ -293,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()
@@ -317,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()
@@ -347,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(),
@@ -373,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()
@@ -397,8 +446,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:organizationId/licenses",
method: "GET",
url: "/:organizationId/licenses",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
organizationId: z.string().trim()

View File

@@ -2,6 +2,7 @@ import slugify from "@sindresorhus/slugify";
import { z } from "zod";
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";
@@ -9,6 +10,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:organizationId/roles",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
organizationId: z.string().trim()
@@ -51,6 +55,9 @@ 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(),
@@ -95,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(),
@@ -122,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()
@@ -151,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()

View File

@@ -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()
@@ -41,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(),
@@ -76,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(),
@@ -104,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()
@@ -134,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()
@@ -155,6 +171,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
req.permission.authMethod,
req.permission.orgId
);
return { data: { permissions, membership } };
}
});

View File

@@ -3,7 +3,8 @@ 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 { getLastMidnightDateISO, 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";
@@ -11,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: [
@@ -51,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()
@@ -83,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: [
@@ -135,6 +145,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actorAuthMethod: req.permission.authMethod,
projectId: req.params.workspaceId,
...req.query,
startDate: req.query.endDate || getLastMidnightDateISO(),
auditLogActor: req.query.actor,
actor: req.permission.type
});
@@ -145,6 +156,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()

View File

@@ -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({
@@ -240,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({
@@ -270,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

View File

@@ -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({
@@ -51,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({
@@ -78,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({
@@ -196,7 +206,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
schema: {
body: z.object({
schemas: z.array(z.string()),
userName: z.string().trim().email(),
userName: z.string().trim(),
name: z.object({
familyName: z.string().trim(),
givenName: z.string().trim()
@@ -217,7 +227,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
200: z.object({
schemas: z.array(z.string()),
id: z.string().trim(),
userName: z.string().trim().email(),
userName: z.string().trim(),
name: z.object({
familyName: z.string().trim(),
givenName: z.string().trim()
@@ -252,38 +262,257 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/Users/:userId",
method: "PATCH",
method: "DELETE",
schema: {
params: z.object({
userId: z.string().trim()
}),
body: z.object({
schemas: z.array(z.string()),
Operations: z.array(
z.object({
op: z.string().trim(),
path: z.string().trim().optional(),
value: z.union([
z.object({
active: z.boolean()
}),
z.string().trim()
])
})
)
}),
response: {
200: z.object({})
}
},
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => {
const user = await req.server.services.scim.updateScimUser({
const user = await req.server.services.scim.deleteScimUser({
userId: req.params.userId,
orgId: req.permission.orgId
});
return user;
}
});
server.route({
url: "/Groups",
method: "POST",
schema: {
body: z.object({
schemas: z.array(z.string()),
displayName: z.string().trim(),
members: z.array(z.any()).length(0).optional() // okta-specific
}),
response: {
200: z.object({
schemas: z.array(z.string()),
id: z.string().trim(),
displayName: z.string().trim(),
members: z.array(z.any()).length(0),
meta: z.object({
resourceType: z.string().trim()
})
})
}
},
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => {
const group = await req.server.services.scim.createScimGroup({
displayName: req.body.displayName,
orgId: req.permission.orgId
});
return group;
}
});
server.route({
url: "/Groups",
method: "GET",
schema: {
querystring: z.object({
startIndex: z.coerce.number().default(1),
count: z.coerce.number().default(20),
filter: z.string().trim().optional()
}),
response: {
200: z.object({
Resources: z.array(
z.object({
schemas: z.array(z.string()),
id: z.string().trim(),
displayName: z.string().trim(),
members: z.array(z.any()).length(0),
meta: z.object({
resourceType: z.string().trim()
})
})
),
itemsPerPage: z.number(),
schemas: z.array(z.string()),
startIndex: z.number(),
totalResults: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => {
const groups = await req.server.services.scim.listScimGroups({
orgId: req.permission.orgId,
offset: req.query.startIndex,
limit: req.query.count
});
return groups;
}
});
server.route({
url: "/Groups/:groupId",
method: "GET",
schema: {
params: z.object({
groupId: z.string().trim()
}),
response: {
200: z.object({
schemas: z.array(z.string()),
id: z.string().trim(),
displayName: z.string().trim(),
members: z.array(
z.object({
value: z.string(),
display: z.string()
})
),
meta: z.object({
resourceType: z.string().trim()
})
})
}
},
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => {
const group = await req.server.services.scim.getScimGroup({
groupId: req.params.groupId,
orgId: req.permission.orgId
});
return group;
}
});
server.route({
url: "/Groups/:groupId",
method: "PUT",
schema: {
params: z.object({
groupId: z.string().trim()
}),
body: z.object({
schemas: z.array(z.string()),
id: z.string().trim(),
displayName: z.string().trim(),
members: z.array(z.any()).length(0)
}),
response: {
200: z.object({
schemas: z.array(z.string()),
id: z.string().trim(),
displayName: z.string().trim(),
members: z.array(
z.object({
value: z.string(),
display: z.string()
})
),
meta: z.object({
resourceType: z.string().trim()
})
})
}
},
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => {
const group = await req.server.services.scim.updateScimGroupNamePut({
groupId: req.params.groupId,
orgId: req.permission.orgId,
displayName: req.body.displayName
});
return group;
}
});
server.route({
url: "/Groups/:groupId",
method: "PATCH",
schema: {
params: z.object({
groupId: z.string().trim()
}),
body: z.object({
schemas: z.array(z.string()),
Operations: z.array(
z.union([
z.object({
op: z.literal("replace"),
value: z.object({
id: z.string().trim(),
displayName: z.string().trim()
})
}),
z.object({
op: z.literal("remove"),
path: z.string().trim()
}),
z.object({
op: z.literal("add"),
value: z.object({
value: z.string().trim(),
display: z.string().trim().optional()
})
})
])
)
}),
response: {
200: z.object({
schemas: z.array(z.string()),
id: z.string().trim(),
displayName: z.string().trim(),
members: z.array(
z.object({
value: z.string(),
display: z.string()
})
),
meta: z.object({
resourceType: z.string().trim()
})
})
}
},
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => {
// console.log("PATCH /Groups/:groupId req.body: ", req.body);
// console.log("PATCH /Groups/:groupId req.body: ", req.body.Operations[0]);
const group = await req.server.services.scim.updateScimGroupNamePatch({
groupId: req.params.groupId,
orgId: req.permission.orgId,
operations: req.body.Operations
});
return user;
return group;
}
});
server.route({
url: "/Groups/:groupId",
method: "DELETE",
schema: {
params: z.object({
groupId: z.string().trim()
}),
response: {
200: z.object({})
}
},
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => {
const group = await req.server.services.scim.deleteScimGroup({
groupId: req.params.groupId,
orgId: req.permission.orgId
});
return group;
}
});

View File

@@ -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({
@@ -47,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()
@@ -85,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()
@@ -111,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()
@@ -137,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(),

View File

@@ -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(),
@@ -62,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()
@@ -93,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()
@@ -117,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()
@@ -147,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()
@@ -203,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()

View File

@@ -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()

View File

@@ -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(),
@@ -52,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()
@@ -86,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()
@@ -136,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()

View File

@@ -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: {
@@ -31,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(),
@@ -56,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: {
@@ -80,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: {
@@ -100,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) }),

View File

@@ -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()

View File

@@ -2,6 +2,7 @@ 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";
@@ -9,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()
@@ -58,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: [

View File

@@ -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()
@@ -33,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()
@@ -78,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(),
@@ -124,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(),

View File

@@ -6,6 +6,7 @@ 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";
@@ -13,6 +14,9 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
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),
@@ -21,11 +25,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
.min(1)
.max(60)
.trim()
.default(slugify(alphaNumericNanoId(12)))
.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)
}),
@@ -43,6 +47,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
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)
});
@@ -51,8 +56,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
});
server.route({
url: "/temporary",
method: "POST",
url: "/temporary",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId),
@@ -61,11 +69,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
.min(1)
.max(60)
.trim()
.default(`privilege-${slugify(alphaNumericNanoId(12))}`)
.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
@@ -94,6 +102,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
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)
});
@@ -102,8 +111,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
});
server.route({
url: "/:privilegeId",
method: "PATCH",
url: "/:privilegeId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
privilegeId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.privilegeId)
@@ -156,8 +168,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
});
server.route({
url: "/:privilegeId",
method: "DELETE",
url: "/:privilegeId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
privilegeId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.DELETE.privilegeId)
@@ -182,8 +197,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
});
server.route({
url: "/",
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
projectMembershipId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.LIST.projectMembershipId)
@@ -208,8 +226,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
});
server.route({
url: "/:privilegeId",
method: "GET",
url: "/:privilegeId",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
privilegeId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.GET_BY_PRIVILEGEID.privilegeId)

View File

@@ -249,7 +249,7 @@ export const dynamicSecretLeaseServiceFactory = ({
if ((revokeResponse as { error?: Error })?.error) {
const { error } = revokeResponse as { error?: Error };
logger.error("Failed to revoke lease", { error: error?.message });
logger.error(error?.message, "Failed to revoke lease");
const deletedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
status: DynamicSecretLeaseStatus.FailedDeletion,
statusDetails: error?.message?.slice(0, 255)

View File

@@ -1,7 +1,8 @@
import { z } from "zod";
export enum SqlProviders {
Postgres = "postgres"
Postgres = "postgres",
MySQL = "mysql2"
}
export const DynamicSecretSqlDBSchema = z.object({
@@ -13,7 +14,7 @@ export const DynamicSecretSqlDBSchema = z.object({
password: z.string(),
creationStatement: z.string(),
revocationStatement: z.string(),
renewStatement: z.string(),
renewStatement: z.string().optional(),
ca: z.string().optional()
});

View File

@@ -48,10 +48,10 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
host: providerInputs.host,
user: providerInputs.username,
password: providerInputs.password,
connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT,
ssl,
pool: { min: 0, max: 1 }
}
},
acquireConnectionTimeout: EXTERNAL_REQUEST_TIMEOUT
});
return db;
};
@@ -73,15 +73,25 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
const username = alphaNumericNanoId(32);
const password = generatePassword();
const { database } = providerInputs;
const expiration = new Date(expireAt).toISOString();
const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({
username,
password,
expiration
expiration,
database
});
await db.raw(creationStatement.toString());
await db.transaction(async (tx) =>
Promise.all(
creationStatement
.toString()
.split(";")
.filter(Boolean)
.map((query) => tx.raw(query))
)
);
await db.destroy();
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
};
@@ -91,9 +101,18 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
const db = await getClient(providerInputs);
const username = entityId;
const { database } = providerInputs;
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username });
await db.raw(revokeStatement);
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username, database });
await db.transaction(async (tx) =>
Promise.all(
revokeStatement
.toString()
.split(";")
.filter(Boolean)
.map((query) => tx.raw(query))
)
);
await db.destroy();
return { entityId: username };
@@ -105,9 +124,19 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
const username = entityId;
const expiration = new Date(expireAt).toISOString();
const { database } = providerInputs;
const renewStatement = handlebars.compile(providerInputs.renewStatement)({ username, expiration });
await db.raw(renewStatement);
const renewStatement = handlebars.compile(providerInputs.renewStatement)({ username, expiration, database });
if (renewStatement)
await db.transaction(async (tx) =>
Promise.all(
renewStatement
.toString()
.split(";")
.filter(Boolean)
.map((query) => tx.raw(query))
)
);
await db.destroy();
return { entityId: username };

View File

@@ -0,0 +1,157 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TGroups } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex";
export type TGroupDALFactory = ReturnType<typeof groupDALFactory>;
export const groupDALFactory = (db: TDbClient) => {
const groupOrm = ormify(db, TableName.Groups);
const findGroups = async (filter: TFindFilter<TGroups>, { offset, limit, sort, tx }: TFindOpt<TGroups> = {}) => {
try {
const query = (tx || db)(TableName.Groups)
// eslint-disable-next-line
.where(buildFindFilter(filter))
.select(selectAllTableCols(TableName.Groups));
if (limit) void query.limit(limit);
if (offset) void query.limit(offset);
if (sort) {
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
}
const res = await query;
return res;
} catch (err) {
throw new DatabaseError({ error: err, name: "Find groups" });
}
};
const findByOrgId = async (orgId: string, tx?: Knex) => {
try {
const docs = await (tx || db)(TableName.Groups)
.where(`${TableName.Groups}.orgId`, orgId)
.leftJoin(TableName.OrgRoles, `${TableName.Groups}.roleId`, `${TableName.OrgRoles}.id`)
.select(selectAllTableCols(TableName.Groups))
// cr stands for custom role
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
.select(db.ref("name").as("crName").withSchema(TableName.OrgRoles))
.select(db.ref("slug").as("crSlug").withSchema(TableName.OrgRoles))
.select(db.ref("description").as("crDescription").withSchema(TableName.OrgRoles))
.select(db.ref("permissions").as("crPermission").withSchema(TableName.OrgRoles));
return docs.map(({ crId, crDescription, crSlug, crPermission, crName, ...el }) => ({
...el,
customRole: el.roleId
? {
id: crId,
name: crName,
slug: crSlug,
permissions: crPermission,
description: crDescription
}
: undefined
}));
} catch (error) {
throw new DatabaseError({ error, name: "FindByOrgId" });
}
};
const countAllGroupMembers = async ({ orgId, groupId }: { orgId: string; groupId: string }) => {
try {
interface CountResult {
count: string;
}
const doc = await db<CountResult>(TableName.OrgMembership)
.where(`${TableName.OrgMembership}.orgId`, orgId)
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.UserGroupMembership, function () {
this.on(`${TableName.UserGroupMembership}.userId`, "=", `${TableName.Users}.id`).andOn(
`${TableName.UserGroupMembership}.groupId`,
"=",
db.raw("?", [groupId])
);
})
.where({ isGhost: false })
.count(`${TableName.Users}.id`)
.first();
return parseInt((doc?.count as string) || "0", 10);
} catch (err) {
throw new DatabaseError({ error: err, name: "Count all group members" });
}
};
// special query
const findAllGroupMembers = async ({
orgId,
groupId,
offset = 0,
limit,
username
}: {
orgId: string;
groupId: string;
offset?: number;
limit?: number;
username?: string;
}) => {
try {
let query = db(TableName.OrgMembership)
.where(`${TableName.OrgMembership}.orgId`, orgId)
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.UserGroupMembership, function () {
this.on(`${TableName.UserGroupMembership}.userId`, "=", `${TableName.Users}.id`).andOn(
`${TableName.UserGroupMembership}.groupId`,
"=",
db.raw("?", [groupId])
);
})
.select(
db.ref("id").withSchema(TableName.OrgMembership),
db.ref("groupId").withSchema(TableName.UserGroupMembership),
db.ref("email").withSchema(TableName.Users),
db.ref("username").withSchema(TableName.Users),
db.ref("firstName").withSchema(TableName.Users),
db.ref("lastName").withSchema(TableName.Users),
db.ref("id").withSchema(TableName.Users).as("userId")
)
.where({ isGhost: false })
.offset(offset);
if (limit) {
query = query.limit(limit);
}
if (username) {
query = query.andWhere(`${TableName.Users}.username`, "ilike", `%${username}%`);
}
const members = await query;
return members.map(
({ email, username: memberUsername, firstName, lastName, userId, groupId: memberGroupId }) => ({
id: userId,
email,
username: memberUsername,
firstName,
lastName,
isPartOfGroup: !!memberGroupId
})
);
} catch (error) {
throw new DatabaseError({ error, name: "Find all org members" });
}
};
return {
findGroups,
findByOrgId,
countAllGroupMembers,
findAllGroupMembers,
...groupOrm
};
};

View File

@@ -0,0 +1,474 @@
import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import { OrgMembershipRole, SecretKeyEncoding, TOrgRoles } from "@app/db/schemas";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { decryptAsymmetric, encryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TGroupProjectDALFactory } from "../../../services/group-project/group-project-dal";
import { TOrgDALFactory } from "../../../services/org/org-dal";
import { TProjectDALFactory } from "../../../services/project/project-dal";
import { TProjectBotDALFactory } from "../../../services/project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "../../../services/project-key/project-key-dal";
import { TUserDALFactory } from "../../../services/user/user-dal";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TGroupDALFactory } from "./group-dal";
import {
TAddUserToGroupDTO,
TCreateGroupDTO,
TDeleteGroupDTO,
TListGroupUsersDTO,
TRemoveUserFromGroupDTO,
TUpdateGroupDTO
} from "./group-types";
import { TUserGroupMembershipDALFactory } from "./user-group-membership-dal";
type TGroupServiceFactoryDep = {
userDAL: Pick<TUserDALFactory, "findOne" | "findUserEncKeyByUsername">;
groupDAL: Pick<
TGroupDALFactory,
"create" | "findOne" | "update" | "delete" | "findAllGroupMembers" | "countAllGroupMembers"
>;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
orgDAL: Pick<TOrgDALFactory, "findMembership">;
userGroupMembershipDAL: Pick<
TUserGroupMembershipDALFactory,
"findOne" | "create" | "delete" | "filterProjectsByUserMembership"
>;
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "create" | "delete" | "findLatestProjectKey">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
export type TGroupServiceFactory = ReturnType<typeof groupServiceFactory>;
export const groupServiceFactory = ({
userDAL,
groupDAL,
groupProjectDAL,
orgDAL,
userGroupMembershipDAL,
projectDAL,
projectBotDAL,
projectKeyDAL,
permissionService,
licenseService
}: TGroupServiceFactoryDep) => {
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Groups);
const plan = await licenseService.getPlan(actorOrgId);
if (!plan.groups)
throw new BadRequestError({
message: "Failed to create group due to plan restriction. Upgrade plan to create group."
});
const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole(
role,
actorOrgId
);
const isCustomRole = Boolean(customRole);
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasRequiredPriviledges) throw new BadRequestError({ message: "Failed to create a more privileged group" });
const group = await groupDAL.create({
name,
slug: slug || slugify(`${name}-${alphaNumericNanoId(4)}`),
orgId: actorOrgId,
role: isCustomRole ? OrgMembershipRole.Custom : role,
roleId: customRole?.id
});
return group;
};
const updateGroup = async ({
currentSlug,
name,
slug,
role,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TUpdateGroupDTO) => {
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups);
const plan = await licenseService.getPlan(actorOrgId);
if (!plan.groups)
throw new BadRequestError({
message: "Failed to update group due to plan restrictio Upgrade plan to update group."
});
const group = await groupDAL.findOne({ orgId: actorOrgId, slug: currentSlug });
if (!group) throw new BadRequestError({ message: `Failed to find group with slug ${currentSlug}` });
let customRole: TOrgRoles | undefined;
if (role) {
const { permission: rolePermission, role: customOrgRole } = await permissionService.getOrgPermissionByRole(
role,
group.orgId
);
const isCustomRole = Boolean(customOrgRole);
const hasRequiredNewRolePermission = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasRequiredNewRolePermission)
throw new BadRequestError({ message: "Failed to create a more privileged group" });
if (isCustomRole) customRole = customOrgRole;
}
const [updatedGroup] = await groupDAL.update(
{
orgId: actorOrgId,
slug: currentSlug
},
{
name,
slug: slug ? slugify(slug) : undefined,
...(role
? {
role: customRole ? OrgMembershipRole.Custom : role,
roleId: customRole?.id ?? null
}
: {})
}
);
return updatedGroup;
};
const deleteGroup = async ({ groupSlug, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteGroupDTO) => {
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Groups);
const plan = await licenseService.getPlan(actorOrgId);
if (!plan.groups)
throw new BadRequestError({
message: "Failed to delete group due to plan restriction. Upgrade plan to delete group."
});
const [group] = await groupDAL.delete({
orgId: actorOrgId,
slug: groupSlug
});
return group;
};
const listGroupUsers = async ({
groupSlug,
offset,
limit,
username,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TListGroupUsersDTO) => {
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
const group = await groupDAL.findOne({
orgId: actorOrgId,
slug: groupSlug
});
if (!group)
throw new BadRequestError({
message: `Failed to find group with slug ${groupSlug}`
});
const users = await groupDAL.findAllGroupMembers({
orgId: group.orgId,
groupId: group.id,
offset,
limit,
username
});
const totalCount = await groupDAL.countAllGroupMembers({
orgId: group.orgId,
groupId: group.id
});
return { users, totalCount };
};
const addUserToGroup = async ({
groupSlug,
username,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TAddUserToGroupDTO) => {
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups);
// check if group with slug exists
const group = await groupDAL.findOne({
orgId: actorOrgId,
slug: groupSlug
});
if (!group)
throw new BadRequestError({
message: `Failed to find group with slug ${groupSlug}`
});
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
// check if user has broader or equal to privileges than group
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, groupRolePermission);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to add user to more privileged group" });
// get user with username
const user = await userDAL.findUserEncKeyByUsername({
username
});
if (!user)
throw new BadRequestError({
message: `Failed to find user with username ${username}`
});
// check if user group membership already exists
const existingUserGroupMembership = await userGroupMembershipDAL.findOne({
groupId: group.id,
userId: user.userId
});
if (existingUserGroupMembership)
throw new BadRequestError({
message: `User ${username} is already part of the group ${groupSlug}`
});
// check if user is even part of the organization
const existingUserOrgMembership = await orgDAL.findMembership({
userId: user.userId,
orgId: actorOrgId
});
if (!existingUserOrgMembership)
throw new BadRequestError({
message: `User ${username} is not part of the organization`
});
await userGroupMembershipDAL.create({
userId: user.userId,
groupId: group.id
});
// check which projects the group is part of
const projectIds = (
await groupProjectDAL.find({
groupId: group.id
})
).map((gp) => gp.projectId);
const keys = await projectKeyDAL.find({
receiverId: user.userId,
$in: {
projectId: projectIds
}
});
const keysSet = new Set(keys.map((k) => k.projectId));
const projectsToAddKeyFor = projectIds.filter((p) => !keysSet.has(p));
for await (const projectId of projectsToAddKeyFor) {
const ghostUser = await projectDAL.findProjectGhostUser(projectId);
if (!ghostUser) {
throw new BadRequestError({
message: "Failed to find sudo user"
});
}
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId);
if (!ghostUserLatestKey) {
throw new BadRequestError({
message: "Failed to find sudo user latest key"
});
}
const bot = await projectBotDAL.findOne({ projectId });
if (!bot) {
throw new BadRequestError({
message: "Failed to find bot"
});
}
const botPrivateKey = infisicalSymmetricDecrypt({
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
iv: bot.iv,
tag: bot.tag,
ciphertext: bot.encryptedPrivateKey
});
const plaintextProjectKey = decryptAsymmetric({
ciphertext: ghostUserLatestKey.encryptedKey,
nonce: ghostUserLatestKey.nonce,
publicKey: ghostUserLatestKey.sender.publicKey,
privateKey: botPrivateKey
});
const { ciphertext: encryptedKey, nonce } = encryptAsymmetric(plaintextProjectKey, user.publicKey, botPrivateKey);
await projectKeyDAL.create({
encryptedKey,
nonce,
senderId: ghostUser.id,
receiverId: user.userId,
projectId
});
}
return user;
};
const removeUserFromGroup = async ({
groupSlug,
username,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TRemoveUserFromGroupDTO) => {
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups);
// check if group with slug exists
const group = await groupDAL.findOne({
orgId: actorOrgId,
slug: groupSlug
});
if (!group)
throw new BadRequestError({
message: `Failed to find group with slug ${groupSlug}`
});
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
// check if user has broader or equal to privileges than group
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, groupRolePermission);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to delete user from more privileged group" });
const user = await userDAL.findOne({
username
});
if (!user)
throw new BadRequestError({
message: `Failed to find user with username ${username}`
});
// check if user group membership already exists
const existingUserGroupMembership = await userGroupMembershipDAL.findOne({
groupId: group.id,
userId: user.id
});
if (!existingUserGroupMembership)
throw new BadRequestError({
message: `User ${username} is not part of the group ${groupSlug}`
});
const projectIds = (
await groupProjectDAL.find({
groupId: group.id
})
).map((gp) => gp.projectId);
const t = await userGroupMembershipDAL.filterProjectsByUserMembership(user.id, group.id, projectIds);
const projectsToDeleteKeyFor = projectIds.filter((p) => !t.has(p));
if (projectsToDeleteKeyFor.length) {
await projectKeyDAL.delete({
receiverId: user.id,
$in: {
projectId: projectsToDeleteKeyFor
}
});
}
await userGroupMembershipDAL.delete({
groupId: group.id,
userId: user.id
});
return user;
};
return {
createGroup,
updateGroup,
deleteGroup,
listGroupUsers,
addUserToGroup,
removeUserFromGroup
};
};

View File

@@ -0,0 +1,37 @@
import { TGenericPermission } from "@app/lib/types";
export type TCreateGroupDTO = {
name: string;
slug?: string;
role: string;
} & TGenericPermission;
export type TUpdateGroupDTO = {
currentSlug: string;
} & Partial<{
name: string;
slug: string;
role: string;
}> &
TGenericPermission;
export type TDeleteGroupDTO = {
groupSlug: string;
} & TGenericPermission;
export type TListGroupUsersDTO = {
groupSlug: string;
offset: number;
limit: number;
username?: string;
} & TGenericPermission;
export type TAddUserToGroupDTO = {
groupSlug: string;
username: string;
} & TGenericPermission;
export type TRemoveUserFromGroupDTO = {
groupSlug: string;
username: string;
} & TGenericPermission;

View File

@@ -0,0 +1,125 @@
import { TDbClient } from "@app/db";
import { TableName, TUserEncryptionKeys } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
export type TUserGroupMembershipDALFactory = ReturnType<typeof userGroupMembershipDALFactory>;
export const userGroupMembershipDALFactory = (db: TDbClient) => {
const userGroupMembershipOrm = ormify(db, TableName.UserGroupMembership);
/**
* Returns a sub-set of projectIds fed into this function corresponding to projects where either:
* - The user is a direct member of the project.
* - The user is a member of a group that is a member of the project, excluding projects that they are part of
* through the group with id [groupId].
*/
const filterProjectsByUserMembership = async (userId: string, groupId: string, projectIds: string[]) => {
const userProjectMemberships: string[] = await db(TableName.ProjectMembership)
.where(`${TableName.ProjectMembership}.userId`, userId)
.whereIn(`${TableName.ProjectMembership}.projectId`, projectIds)
.pluck(`${TableName.ProjectMembership}.projectId`);
const userGroupMemberships: string[] = await db(TableName.UserGroupMembership)
.where(`${TableName.UserGroupMembership}.userId`, userId)
.whereNot(`${TableName.UserGroupMembership}.groupId`, groupId)
.join(
TableName.GroupProjectMembership,
`${TableName.UserGroupMembership}.groupId`,
`${TableName.GroupProjectMembership}.groupId`
)
.whereIn(`${TableName.GroupProjectMembership}.projectId`, projectIds)
.pluck(`${TableName.GroupProjectMembership}.projectId`);
return new Set(userProjectMemberships.concat(userGroupMemberships));
};
// special query
const findUserGroupMembershipsInProject = async (usernames: string[], projectId: string) => {
try {
const usernameDocs: string[] = await db(TableName.UserGroupMembership)
.join(
TableName.GroupProjectMembership,
`${TableName.UserGroupMembership}.groupId`,
`${TableName.GroupProjectMembership}.groupId`
)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.whereIn(`${TableName.Users}.username`, usernames) // TODO: pluck usernames
.pluck(`${TableName.Users}.id`);
return usernameDocs;
} catch (error) {
throw new DatabaseError({ error, name: "Find user group members in project" });
}
};
/**
* Return list of users that are part of the group with id [groupId]
* that have not yet been added individually to project with id [projectId].
*
* Note: Filters out users that are part of other groups in the project.
* @param groupId
* @param projectId
* @returns
*/
const findGroupMembersNotInProject = async (groupId: string, projectId: string) => {
try {
// get list of groups in the project with id [projectId]
// that that are not the group with id [groupId]
const groups: string[] = await db(TableName.GroupProjectMembership)
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.whereNot(`${TableName.GroupProjectMembership}.groupId`, groupId)
.pluck(`${TableName.GroupProjectMembership}.groupId`);
// main query
const members = await db(TableName.UserGroupMembership)
.where(`${TableName.UserGroupMembership}.groupId`, groupId)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.ProjectMembership, function () {
this.on(`${TableName.Users}.id`, "=", `${TableName.ProjectMembership}.userId`).andOn(
`${TableName.ProjectMembership}.projectId`,
"=",
db.raw("?", [projectId])
);
})
.whereNull(`${TableName.ProjectMembership}.userId`)
.leftJoin<TUserEncryptionKeys>(
TableName.UserEncryptionKey,
`${TableName.UserEncryptionKey}.userId`,
`${TableName.Users}.id`
)
.select(
db.ref("id").withSchema(TableName.UserGroupMembership),
db.ref("groupId").withSchema(TableName.UserGroupMembership),
db.ref("email").withSchema(TableName.Users),
db.ref("username").withSchema(TableName.Users),
db.ref("firstName").withSchema(TableName.Users),
db.ref("lastName").withSchema(TableName.Users),
db.ref("id").withSchema(TableName.Users).as("userId"),
db.ref("publicKey").withSchema(TableName.UserEncryptionKey)
)
.where({ isGhost: false }) // MAKE SURE USER IS NOT A GHOST USER
.whereNotIn(`${TableName.UserGroupMembership}.userId`, function () {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.select(`${TableName.UserGroupMembership}.userId`)
.from(TableName.UserGroupMembership)
.whereIn(`${TableName.UserGroupMembership}.groupId`, groups);
});
return members.map(({ email, username, firstName, lastName, userId, publicKey, ...data }) => ({
...data,
user: { email, username, firstName, lastName, id: userId, publicKey }
}));
} catch (error) {
throw new DatabaseError({ error, name: "Find group members not in project" });
}
};
return {
...userGroupMembershipOrm,
filterProjectsByUserMembership,
findUserGroupMembershipsInProject,
findGroupMembersNotInProject
};
};

View File

@@ -12,7 +12,6 @@ import {
infisicalSymmetricEncypt
} from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { TOrgPermission } from "@app/lib/types";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
@@ -24,7 +23,7 @@ import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TLdapConfigDALFactory } from "./ldap-config-dal";
import { TCreateLdapCfgDTO, TLdapLoginDTO, TUpdateLdapCfgDTO } from "./ldap-config-types";
import { TCreateLdapCfgDTO, TGetLdapCfgDTO, TLdapLoginDTO, TUpdateLdapCfgDTO } from "./ldap-config-types";
type TLdapConfigServiceFactoryDep = {
ldapConfigDAL: TLdapConfigDALFactory;
@@ -282,7 +281,7 @@ export const ldapConfigServiceFactory = ({
orgId,
actorAuthMethod,
actorOrgId
}: TOrgPermission) => {
}: TGetLdapCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
return getLdapCfg({

View File

@@ -1,6 +1,7 @@
import { TOrgPermission } from "@app/lib/types";
export type TCreateLdapCfgDTO = {
orgId: string;
isActive: boolean;
url: string;
bindDN: string;
@@ -9,7 +10,9 @@ export type TCreateLdapCfgDTO = {
caCert: string;
} & TOrgPermission;
export type TUpdateLdapCfgDTO = Partial<{
export type TUpdateLdapCfgDTO = {
orgId: string;
} & Partial<{
isActive: boolean;
url: string;
bindDN: string;
@@ -19,6 +22,10 @@ export type TUpdateLdapCfgDTO = Partial<{
}> &
TOrgPermission;
export type TGetLdapCfgDTO = {
orgId: string;
} & TOrgPermission;
export type TLdapLoginDTO = {
externalId: string;
username: string;

View File

@@ -20,6 +20,7 @@ export const getDefaultOnPremFeatures = () => {
samlSSO: false,
scim: false,
ldap: false,
groups: false,
status: null,
trial_end: null,
has_used_trial: true,

View File

@@ -27,6 +27,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
samlSSO: false,
scim: false,
ldap: false,
groups: false,
status: null,
trial_end: null,
has_used_trial: true,

View File

@@ -43,6 +43,7 @@ export type TFeatureSet = {
samlSSO: false;
scim: false;
ldap: false;
groups: false;
status: null;
trial_end: null;
has_used_trial: true;

View File

@@ -18,6 +18,7 @@ export enum OrgPermissionSubjects {
Sso = "sso",
Scim = "scim",
Ldap = "ldap",
Groups = "groups",
Billing = "billing",
SecretScanning = "secret-scanning",
Identity = "identity"
@@ -33,6 +34,7 @@ export type OrgPermissionSet =
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
| [OrgPermissionActions, OrgPermissionSubjects.Groups]
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
@@ -83,6 +85,11 @@ const buildAdminPermission = () => {
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Ldap);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Groups);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Groups);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
@@ -105,6 +112,7 @@ const buildMemberPermission = () => {
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);

View File

@@ -45,6 +45,43 @@ export const permissionDALFactory = (db: TDbClient) => {
const getProjectPermission = async (userId: string, projectId: string) => {
try {
const groups: string[] = await db(TableName.GroupProjectMembership)
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.pluck(`${TableName.GroupProjectMembership}.groupId`);
const groupDocs = await db(TableName.UserGroupMembership)
.where(`${TableName.UserGroupMembership}.userId`, userId)
.whereIn(`${TableName.UserGroupMembership}.groupId`, groups)
.join(
TableName.GroupProjectMembership,
`${TableName.GroupProjectMembership}.groupId`,
`${TableName.UserGroupMembership}.groupId`
)
.join(
TableName.GroupProjectMembershipRole,
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
`${TableName.GroupProjectMembership}.id`
)
.leftJoin(
TableName.ProjectRoles,
`${TableName.GroupProjectMembershipRole}.customRoleId`,
`${TableName.ProjectRoles}.id`
)
.join(TableName.Project, `${TableName.GroupProjectMembership}.projectId`, `${TableName.Project}.id`)
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
.select(selectAllTableCols(TableName.GroupProjectMembershipRole))
.select(
db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"),
// TODO(roll-forward-migration): remove this field when we drop this in next migration after a week
db.ref("createdAt").withSchema(TableName.GroupProjectMembership).as("membershipCreatedAt"),
db.ref("updatedAt").withSchema(TableName.GroupProjectMembership).as("membershipUpdatedAt"),
db.ref("projectId").withSchema(TableName.GroupProjectMembership),
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("orgId").withSchema(TableName.Project),
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
)
.select("permissions");
const docs = await db(TableName.ProjectMembership)
.join(
TableName.ProjectUserMembershipRole,
@@ -69,9 +106,9 @@ export const permissionDALFactory = (db: TDbClient) => {
.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("projectId").withSchema(TableName.ProjectMembership),
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("orgId").withSchema(TableName.Project),
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
@@ -93,19 +130,12 @@ export const permissionDALFactory = (db: TDbClient) => {
const permission = sqlNestRelationships({
data: docs,
key: "membershipId",
parentMapper: ({
orgId,
orgAuthEnforced,
membershipId,
membershipCreatedAt,
membershipUpdatedAt,
oldRoleField
}) => ({
key: "projectId",
parentMapper: ({ orgId, orgAuthEnforced, membershipId, membershipCreatedAt, membershipUpdatedAt, role }) => ({
orgId,
orgAuthEnforced,
userId,
role: oldRoleField,
role,
id: membershipId,
projectId,
createdAt: membershipCreatedAt,
@@ -145,19 +175,66 @@ export const permissionDALFactory = (db: TDbClient) => {
]
});
if (!permission?.[0]) return undefined;
const groupPermission = groupDocs.length
? sqlNestRelationships({
data: groupDocs,
key: "projectId",
parentMapper: ({
orgId,
orgAuthEnforced,
membershipId,
membershipCreatedAt,
membershipUpdatedAt,
role
}) => ({
orgId,
orgAuthEnforced,
userId,
role,
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)
}
]
})
: [];
if (!permission?.[0] && !groupPermission[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 activeRoles =
permission?.[0]?.roles?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
) ?? [];
const activeGroupRoles =
groupPermission?.[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 };
return {
...(permission[0] || groupPermission[0]),
roles: [...activeRoles, ...activeGroupRoles],
additionalPrivileges: activeAdditionalPrivileges
};
} catch (error) {
throw new DatabaseError({ error, name: "GetProjectPermission" });
}

View File

@@ -5,9 +5,13 @@ 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].includes(
actorAuthMethod
);
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"]) {

View File

@@ -12,6 +12,7 @@ export enum ProjectPermissionActions {
export enum ProjectPermissionSub {
Role = "role",
Member = "member",
Groups = "groups",
Settings = "settings",
Integrations = "integrations",
Webhooks = "webhooks",
@@ -41,6 +42,7 @@ export type ProjectPermissionSet =
| [ProjectPermissionActions, ProjectPermissionSub.Role]
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
| [ProjectPermissionActions, ProjectPermissionSub.Member]
| [ProjectPermissionActions, ProjectPermissionSub.Groups]
| [ProjectPermissionActions, ProjectPermissionSub.Integrations]
| [ProjectPermissionActions, ProjectPermissionSub.Webhooks]
| [ProjectPermissionActions, ProjectPermissionSub.AuditLogs]
@@ -82,6 +84,11 @@ const buildAdminPermissionRules = () => {
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Groups);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Groups);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Groups);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
@@ -157,6 +164,8 @@ const buildMemberPermissionRules = () => {
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
@@ -209,6 +218,7 @@ const buildViewerPermissionRules = () => {
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);

View File

@@ -319,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(

View File

@@ -5,7 +5,8 @@ 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 = {

View File

@@ -1,4 +1,4 @@
import { TListScimUsers, TScimUser } from "./scim-types";
import { TListScimGroups, TListScimUsers, TScimGroup, TScimUser } from "./scim-types";
export const buildScimUserList = ({
scimUsers,
@@ -62,3 +62,47 @@ export const buildScimUser = ({
return scimUser;
};
export const buildScimGroupList = ({
scimGroups,
offset,
limit
}: {
scimGroups: TScimGroup[];
offset: number;
limit: number;
}): TListScimGroups => {
return {
Resources: scimGroups,
itemsPerPage: limit,
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
startIndex: offset,
totalResults: scimGroups.length
};
};
export const buildScimGroup = ({
groupId,
name,
members
}: {
groupId: string;
name: string;
members: {
value: string;
display: string;
}[];
}): TScimGroup => {
const scimGroup = {
schemas: ["urn:ietf:params:scim:schemas:core:2.0:Group"],
id: groupId,
displayName: name,
members,
meta: {
resourceType: "Group",
location: null
}
};
return scimGroup;
};

View File

@@ -1,10 +1,13 @@
import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import jwt from "jsonwebtoken";
import { OrgMembershipRole, OrgMembershipStatus, TableName } from "@app/db/schemas";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups } from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TOrgPermission } from "@app/lib/types";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { TOrgDALFactory } from "@app/services/org/org-dal";
@@ -17,16 +20,23 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { buildScimUser, buildScimUserList } from "./scim-fns";
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList } from "./scim-fns";
import {
TCreateScimGroupDTO,
TCreateScimTokenDTO,
TCreateScimUserDTO,
TDeleteScimGroupDTO,
TDeleteScimTokenDTO,
TDeleteScimUserDTO,
TGetScimGroupDTO,
TGetScimUserDTO,
TListScimGroupsDTO,
TListScimUsers,
TListScimUsersDTO,
TReplaceScimUserDTO,
TScimTokenJwtPayload,
TUpdateScimGroupNamePatchDTO,
TUpdateScimGroupNamePutDTO,
TUpdateScimUserDTO
} from "./scim-types";
@@ -39,6 +49,7 @@ type TScimServiceFactoryDep = {
>;
projectDAL: Pick<TProjectDALFactory, "find">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete">;
groupDAL: Pick<TGroupDALFactory, "create" | "findOne" | "findAllGroupMembers" | "update" | "delete" | "findGroups">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
smtpService: TSmtpService;
@@ -53,6 +64,7 @@ export const scimServiceFactory = ({
orgDAL,
projectDAL,
projectMembershipDAL,
groupDAL,
permissionService,
smtpService
}: TScimServiceFactoryDep) => {
@@ -423,6 +435,221 @@ export const scimServiceFactory = ({
});
};
const deleteScimUser = async ({ userId, orgId }: TDeleteScimUserDTO) => {
const [membership] = await orgDAL
.findMembership({
userId,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
})
.catch(() => {
throw new ScimRequestError({
detail: "User not found",
status: 404
});
});
if (!membership)
throw new ScimRequestError({
detail: "User not found",
status: 404
});
if (!membership.scimEnabled) {
throw new ScimRequestError({
detail: "SCIM is disabled for the organization",
status: 403
});
}
await deleteOrgMembership({
orgMembershipId: membership.id,
orgId: membership.orgId,
orgDAL,
projectDAL,
projectMembershipDAL
});
return {}; // intentionally return empty object upon success
};
const listScimGroups = async ({ orgId, offset, limit }: TListScimGroupsDTO) => {
const org = await orgDAL.findById(orgId);
if (!org.scimEnabled)
throw new ScimRequestError({
detail: "SCIM is disabled for the organization",
status: 403
});
const groups = await groupDAL.findGroups({
orgId
});
const scimGroups = groups.map((group) =>
buildScimGroup({
groupId: group.id,
name: group.name,
members: []
})
);
return buildScimGroupList({
scimGroups,
offset,
limit
});
};
const createScimGroup = async ({ displayName, orgId }: TCreateScimGroupDTO) => {
const org = await orgDAL.findById(orgId);
if (!org.scimEnabled)
throw new ScimRequestError({
detail: "SCIM is disabled for the organization",
status: 403
});
const group = await groupDAL.create({
name: displayName,
slug: slugify(`${displayName}-${alphaNumericNanoId(4)}`),
orgId,
role: OrgMembershipRole.NoAccess
});
return buildScimGroup({
groupId: group.id,
name: group.name,
members: []
});
};
const getScimGroup = async ({ groupId, orgId }: TGetScimGroupDTO) => {
const group = await groupDAL.findOne({
id: groupId,
orgId
});
if (!group) {
throw new ScimRequestError({
detail: "Group Not Found",
status: 404
});
}
const users = await groupDAL.findAllGroupMembers({
orgId: group.orgId,
groupId: group.id
});
return buildScimGroup({
groupId: group.id,
name: group.name,
members: users
.filter((user) => user.isPartOfGroup)
.map((user) => ({
value: user.id,
display: `${user.firstName} ${user.lastName}`
}))
});
};
const updateScimGroupNamePut = async ({ groupId, orgId, displayName }: TUpdateScimGroupNamePutDTO) => {
const [group] = await groupDAL.update(
{
id: groupId,
orgId
},
{
name: displayName
}
);
if (!group) {
throw new ScimRequestError({
detail: "Group Not Found",
status: 404
});
}
return buildScimGroup({
groupId: group.id,
name: group.name,
members: []
});
};
// TODO: add support for add/remove op
const updateScimGroupNamePatch = async ({ groupId, orgId, operations }: TUpdateScimGroupNamePatchDTO) => {
const org = await orgDAL.findById(orgId);
if (!org.scimEnabled)
throw new ScimRequestError({
detail: "SCIM is disabled for the organization",
status: 403
});
let group: TGroups | undefined;
for await (const operation of operations) {
switch (operation.op) {
case "replace": {
await groupDAL.update(
{
id: groupId,
orgId
},
{
name: operation.value.displayName
}
);
break;
}
case "add": {
// TODO
break;
}
case "remove": {
// TODO
break;
}
default: {
throw new ScimRequestError({
detail: "Invalid Operation",
status: 400
});
}
}
}
if (!group) {
throw new ScimRequestError({
detail: "Group Not Found",
status: 404
});
}
return buildScimGroup({
groupId: group.id,
name: group.name,
members: []
});
};
const deleteScimGroup = async ({ groupId, orgId }: TDeleteScimGroupDTO) => {
const [group] = await groupDAL.delete({
id: groupId,
orgId
});
if (!group) {
throw new ScimRequestError({
detail: "Group Not Found",
status: 404
});
}
return {}; // intentionally return empty object upon success
};
const fnValidateScimToken = async (token: TScimTokenJwtPayload) => {
const scimToken = await scimDAL.findById(token.scimTokenId);
if (!scimToken) throw new UnauthorizedError();
@@ -455,6 +682,13 @@ export const scimServiceFactory = ({
createScimUser,
updateScimUser,
replaceScimUser,
deleteScimUser,
listScimGroups,
createScimGroup,
getScimGroup,
deleteScimGroup,
updateScimGroupNamePut,
updateScimGroupNamePatch,
fnValidateScimToken
};
};

View File

@@ -59,6 +59,73 @@ export type TReplaceScimUserDTO = {
orgId: string;
};
export type TDeleteScimUserDTO = {
userId: string;
orgId: string;
};
export type TListScimGroupsDTO = {
offset: number;
limit: number;
orgId: string;
};
export type TListScimGroups = {
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"];
totalResults: number;
Resources: TScimGroup[];
itemsPerPage: number;
startIndex: number;
};
export type TCreateScimGroupDTO = {
displayName: string;
orgId: string;
};
export type TGetScimGroupDTO = {
groupId: string;
orgId: string;
};
export type TUpdateScimGroupNamePutDTO = {
groupId: string;
orgId: string;
displayName: string;
};
export type TUpdateScimGroupNamePatchDTO = {
groupId: string;
orgId: string;
operations: (TRemoveOp | TReplaceOp | TAddOp)[];
};
type TReplaceOp = {
op: "replace";
value: {
id: string;
displayName: string;
};
};
type TRemoveOp = {
op: "remove";
path: string;
};
type TAddOp = {
op: "add";
value: {
value: string;
display?: string;
};
};
export type TDeleteScimGroupDTO = {
groupId: string;
orgId: string;
};
export type TScimTokenJwtPayload = {
scimTokenId: string;
authTokenType: string;
@@ -86,3 +153,17 @@ export type TScimUser = {
location: null;
};
};
export type TScimGroup = {
schemas: string[];
id: string;
displayName: string;
members: {
value: string;
display: string;
}[];
meta: {
resourceType: string;
location: null;
};
};

View File

@@ -1,3 +1,34 @@
export const GROUPS = {
CREATE: {
name: "The name of the group to create.",
slug: "The slug of the group to create.",
role: "The role of the group to create."
},
UPDATE: {
currentSlug: "The current slug of the group to update.",
name: "The new name of the group to update to.",
slug: "The new slug of the group to update to.",
role: "The new role of the group to update to."
},
DELETE: {
slug: "The slug of the group to delete"
},
LIST_USERS: {
slug: "The slug of the group to list users for",
offset: "The offset to start from. If you enter 10, it will start from the 10th user.",
limit: "The number of users to return.",
username: "The username to search for."
},
ADD_USER: {
slug: "The slug of the group to add the user to.",
username: "The username of the user to add to the group."
},
DELETE_USER: {
slug: "The slug of the group to remove the user from.",
username: "The username of the user to remove from the group."
}
} as const;
export const IDENTITIES = {
CREATE: {
name: "The name of the identity to create.",
@@ -79,6 +110,9 @@ export const ORGANIZATIONS = {
},
GET_PROJECTS: {
organizationId: "The ID of the organization to get projects from."
},
LIST_GROUPS: {
organizationId: "The ID of the organization to list groups for."
}
} as const;
@@ -141,6 +175,29 @@ export const PROJECTS = {
},
ROLLBACK_TO_SNAPSHOT: {
secretSnapshotId: "The ID of the snapshot to rollback to."
},
ADD_GROUP_TO_PROJECT: {
projectSlug: "The slug of the project to add the group to.",
groupSlug: "The slug of the group to add to the project.",
role: "The role for the group to assume in the project."
},
UPDATE_GROUP_IN_PROJECT: {
projectSlug: "The slug of the project to update the group in.",
groupSlug: "The slug of the group to update in the project.",
roles: "A list of roles to update the group to."
},
REMOVE_GROUP_FROM_PROJECT: {
projectSlug: "The slug of the project to delete the group from.",
groupSlug: "The slug of the group to delete from the project."
},
LIST_GROUPS_IN_PROJECT: {
projectSlug: "The slug of the project to list groups for."
},
LIST_INTEGRATION: {
workspaceId: "The ID of the project to list integrations for."
},
LIST_INTEGRATION_AUTHORIZATION: {
workspaceId: "The ID of the project to list integration auths for."
}
} as const;
@@ -215,6 +272,8 @@ export const SECRETS = {
export const RAW_SECRETS = {
LIST: {
recursive:
"Whether or not to fetch all secrets from the specified base path, and all of its subdirectories. Note, the max depth is 20 deep.",
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.",
@@ -501,11 +560,8 @@ export const INTEGRATION_AUTH = {
url: "",
namespace: "",
refreshToken: "The refresh token for integration authorization."
},
LIST_AUTHORIZATION: {
workspaceId: "The ID of the project to list integration auths for."
}
};
} as const;
export const INTEGRATION = {
CREATE: {

View File

@@ -0,0 +1,2 @@
export const getLastMidnightDateISO = (last = 1) =>
`${new Date(new Date().setDate(new Date().getDate() - last)).toISOString().slice(0, 10)}T00:00:00Z`;

View File

@@ -2,5 +2,6 @@
// Full credits goes to https://github.com/rayapps to those functions
// Code taken to keep in in house and to adjust somethings for our needs
export * from "./array";
export * from "./dates";
export * from "./object";
export * from "./string";

View File

@@ -1,5 +1,17 @@
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
export type TGenericPermission = {
actor: ActorType;
actorId: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId: string | undefined;
};
/**
* TODO(dangtony98): ideally move service fns to use TGenericPermission
* because TOrgPermission [orgId] is not as relevant anymore with the
* introduction of organizationIds bound to all user tokens
*/
export type TOrgPermission = {
actor: ActorType;
actorId: string;
@@ -16,6 +28,15 @@ export type TProjectPermission = {
actorOrgId: string;
};
// same as TProjectPermission but with projectSlug requirement instead of projectId
export type TProjectSlugPermission = {
actor: ActorType;
actorId: string;
projectSlug: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId: string;
};
export type RequiredKeys<T> = {
[K in keyof T]-?: undefined extends T[K] ? never : K;
}[keyof T];

View File

@@ -61,11 +61,11 @@ export type TQueueJobTypes = {
};
[QueueName.SecretWebhook]: {
name: QueueJobs.SecWebhook;
payload: { projectId: string; environment: string; secretPath: string };
payload: { projectId: string; environment: string; secretPath: string; depth?: number };
};
[QueueName.IntegrationSync]: {
name: QueueJobs.IntegrationSync;
payload: { projectId: string; environment: string; secretPath: string };
payload: { projectId: string; environment: string; secretPath: string; depth?: number };
};
[QueueName.SecretFullRepoScan]: {
name: QueueJobs.SecretScan;

View File

@@ -18,14 +18,43 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
};
};
export const authRateLimit: RateLimitOptions = {
// GET endpoints
export const readLimit: RateLimitOptions = {
timeWindow: 60 * 1000,
max: 600,
keyGenerator: (req) => req.realIp
};
export const passwordRateLimit: RateLimitOptions = {
// POST, PATCH, PUT, DELETE endpoints
export const writeLimit: RateLimitOptions = {
timeWindow: 60 * 1000,
max: 50,
keyGenerator: (req) => req.realIp
};
// special endpoints
export const secretsLimit: RateLimitOptions = {
// secrets, folders, secret imports
timeWindow: 60 * 1000,
max: 600,
keyGenerator: (req) => req.realIp
};
export const authRateLimit: RateLimitOptions = {
timeWindow: 60 * 1000,
max: 60,
keyGenerator: (req) => req.realIp
};
export const inviteUserRateLimit: RateLimitOptions = {
timeWindow: 60 * 1000,
max: 30,
keyGenerator: (req) => req.realIp
};
export const creationLimit: RateLimitOptions = {
// identity, project, org
timeWindow: 60 * 1000,
max: 30,
keyGenerator: (req) => req.realIp
};

View File

@@ -4,6 +4,7 @@ import SmeeClient from "smee-client";
import { getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger";
import { writeLimit } from "@app/server/config/rateLimiter";
export const registerSecretScannerGhApp = async (server: FastifyZodProvider) => {
const probotApp = (app: Probot) => {
@@ -49,6 +50,9 @@ export const registerSecretScannerGhApp = async (server: FastifyZodProvider) =>
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
handler: async (req, res) => {
const eventName = req.headers["x-github-event"];
const signatureSHA256 = req.headers["x-hub-signature-256"] as string;

View File

@@ -11,6 +11,9 @@ import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/pro
import { dynamicSecretLeaseDALFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal";
import { dynamicSecretLeaseQueueServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue";
import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
import { groupDALFactory } from "@app/ee/services/group/group-dal";
import { groupServiceFactory } from "@app/ee/services/group/group-service";
import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
import { identityProjectAdditionalPrivilegeDALFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-dal";
import { identityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
import { ldapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
@@ -49,6 +52,7 @@ import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { TQueueServiceFactory } from "@app/queue";
import { readLimit } from "@app/server/config/rateLimiter";
import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal";
import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service";
import { authDALFactory } from "@app/services/auth/auth-dal";
@@ -57,6 +61,9 @@ import { authPaswordServiceFactory } from "@app/services/auth/auth-password-serv
import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service";
import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal";
import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal";
import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service";
import { identityDALFactory } from "@app/services/identity/identity-dal";
import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
import { identityServiceFactory } from "@app/services/identity/identity-service";
@@ -206,6 +213,10 @@ export const registerRoutes = async (
const gitAppInstallSessionDAL = gitAppInstallSessionDALFactory(db);
const gitAppOrgDAL = gitAppDALFactory(db);
const groupDAL = groupDALFactory(db);
const groupProjectDAL = groupProjectDALFactory(db);
const groupProjectMembershipRoleDAL = groupProjectMembershipRoleDALFactory(db);
const userGroupMembershipDAL = userGroupMembershipDALFactory(db);
const secretScanningDAL = secretScanningDALFactory(db);
const licenseDAL = licenseDALFactory(db);
const dynamicSecretDAL = dynamicSecretDALFactory(db);
@@ -248,6 +259,29 @@ export const registerRoutes = async (
samlConfigDAL,
licenseService
});
const groupService = groupServiceFactory({
userDAL,
groupDAL,
groupProjectDAL,
orgDAL,
userGroupMembershipDAL,
projectDAL,
projectBotDAL,
projectKeyDAL,
permissionService,
licenseService
});
const groupProjectService = groupProjectServiceFactory({
groupDAL,
groupProjectDAL,
groupProjectMembershipRoleDAL,
userGroupMembershipDAL,
projectDAL,
projectKeyDAL,
projectBotDAL,
projectRoleDAL,
permissionService
});
const scimService = scimServiceFactory({
licenseService,
scimDAL,
@@ -255,6 +289,7 @@ export const registerRoutes = async (
orgDAL,
projectDAL,
projectMembershipDAL,
groupDAL,
permissionService,
smtpService
});
@@ -301,6 +336,7 @@ export const registerRoutes = async (
projectKeyDAL,
smtpService,
userDAL,
groupDAL,
orgBotDAL
});
const signupService = authSignupServiceFactory({
@@ -346,6 +382,7 @@ export const registerRoutes = async (
projectBotDAL,
orgDAL,
userDAL,
userGroupMembershipDAL,
smtpService,
projectKeyDAL,
projectRoleDAL,
@@ -398,7 +435,8 @@ export const registerRoutes = async (
folderDAL,
licenseService,
projectUserMembershipRoleDAL,
identityProjectMembershipRoleDAL
identityProjectMembershipRoleDAL,
keyStore
});
const projectEnvService = projectEnvServiceFactory({
@@ -409,7 +447,12 @@ export const registerRoutes = async (
folderDAL
});
const projectRoleService = projectRoleServiceFactory({ permissionService, projectRoleDAL });
const projectRoleService = projectRoleServiceFactory({
permissionService,
projectRoleDAL,
projectUserMembershipRoleDAL,
identityProjectMembershipRoleDAL
});
const snapshotService = secretSnapshotServiceFactory({
permissionService,
@@ -438,14 +481,6 @@ export const registerRoutes = async (
projectEnvDAL,
snapshotService
});
const secretImportService = secretImportServiceFactory({
projectEnvDAL,
folderDAL,
permissionService,
secretImportDAL,
projectDAL,
secretDAL
});
const integrationAuthService = integrationAuthServiceFactory({
integrationAuthDAL,
integrationDAL,
@@ -473,6 +508,15 @@ export const registerRoutes = async (
secretTagDAL,
secretVersionTagDAL
});
const secretImportService = secretImportServiceFactory({
projectEnvDAL,
folderDAL,
permissionService,
secretImportDAL,
projectDAL,
secretDAL,
secretQueueService
});
const secretBlindIndexService = secretBlindIndexServiceFactory({
permissionService,
secretDAL,
@@ -490,6 +534,7 @@ export const registerRoutes = async (
snapshotService,
secretQueueService,
secretImportDAL,
projectEnvDAL,
projectBotService
});
const sarService = secretApprovalRequestServiceFactory({
@@ -617,6 +662,8 @@ export const registerRoutes = async (
password: passwordService,
signup: signupService,
user: userService,
group: groupService,
groupProject: groupProjectService,
permission: permissionService,
org: orgService,
orgRole: orgRoleService,
@@ -669,8 +716,11 @@ export const registerRoutes = async (
await server.register(injectAuditLogInfo);
server.route({
url: "/api/status",
method: "GET",
url: "/api/status",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({

View File

@@ -3,6 +3,7 @@ import { z } from "zod";
import { OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { UnauthorizedError } from "@app/lib/errors";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -11,8 +12,11 @@ import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
export const registerAdminRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/config",
method: "GET",
url: "/config",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({
@@ -30,8 +34,11 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/config",
method: "PATCH",
url: "/config",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
allowSignUp: z.boolean().optional(),
@@ -55,8 +62,11 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/signup",
method: "POST",
url: "/signup",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
email: z.string().email().trim(),

View File

@@ -3,7 +3,7 @@ import { z } from "zod";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { authRateLimit } from "@app/server/config/rateLimiter";
import { authRateLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
@@ -38,8 +38,11 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
});
server.route({
url: "/checkAuth",
method: "POST",
url: "/checkAuth",
config: {
rateLimit: writeLimit
},
schema: {
response: {
200: z.object({
@@ -52,8 +55,11 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
});
server.route({
url: "/token",
method: "POST",
url: "/token",
config: {
rateLimit: writeLimit
},
schema: {
response: {
200: z.object({

View File

@@ -1,13 +1,17 @@
import { z } from "zod";
import { ProjectBotsSchema } 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";
export const registerProjectBotRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:projectId",
method: "GET",
url: "/:projectId",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
@@ -38,8 +42,11 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:botId/active",
method: "PATCH",
url: "/:botId/active",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
isActive: z.boolean(),

View File

@@ -1,11 +1,15 @@
import { z } from "zod";
import { UNIVERSAL_AUTH } from "@app/lib/api-docs";
import { writeLimit } from "@app/server/config/rateLimiter";
export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/token/renew",
method: "POST",
config: {
rateLimit: writeLimit
},
schema: {
description: "Renew access token",
body: z.object({

View File

@@ -3,6 +3,7 @@ import { z } from "zod";
import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { IDENTITIES } from "@app/lib/api-docs";
import { creationLimit, writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -12,6 +13,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: creationLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Create identity",
@@ -71,6 +75,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
server.route({
method: "PATCH",
url: "/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Update identity",
@@ -121,6 +128,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
server.route({
method: "DELETE",
url: "/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete identity",

View File

@@ -3,6 +3,7 @@ import { z } from "zod";
import { IdentityUaClientSecretsSchema, IdentityUniversalAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { UNIVERSAL_AUTH } 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";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
@@ -22,8 +23,11 @@ export const sanitizedClientSecretSchema = IdentityUaClientSecretsSchema.pick({
export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/universal-auth/login",
method: "POST",
url: "/universal-auth/login",
config: {
rateLimit: writeLimit
},
schema: {
description: "Login with Universal Auth",
body: z.object({
@@ -66,8 +70,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/universal-auth/identities/:identityId",
method: "POST",
url: "/universal-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Attach Universal Auth configuration onto identity",
@@ -156,8 +163,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/universal-auth/identities/:identityId",
method: "PATCH",
url: "/universal-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Update Universal Auth configuration on identity",
@@ -239,8 +249,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/universal-auth/identities/:identityId",
method: "GET",
url: "/universal-auth/identities/:identityId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Retrieve Universal Auth configuration on identity",
@@ -283,8 +296,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/universal-auth/identities/:identityId/client-secrets",
method: "POST",
url: "/universal-auth/identities/:identityId/client-secrets",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Create Universal Auth Client Secret for identity",
@@ -335,8 +351,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/universal-auth/identities/:identityId/client-secrets",
method: "GET",
url: "/universal-auth/identities/:identityId/client-secrets",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "List Universal Auth Client Secrets for identity",
@@ -378,8 +397,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke",
method: "POST",
url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Revoke Universal Auth Client Secrets for identity",

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { INTEGRATION_AUTH } 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";
@@ -9,8 +10,11 @@ import { integrationAuthPubSchema } from "../sanitizedSchemas";
export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/integration-options",
method: "GET",
url: "/integration-options",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "List of integrations available.",
@@ -43,8 +47,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId",
method: "GET",
url: "/:integrationAuthId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Get details of an integration authorization by auth object id.",
@@ -75,8 +82,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/",
method: "DELETE",
url: "/",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Remove all integration's auth object from the project.",
@@ -121,8 +131,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId",
method: "DELETE",
url: "/:integrationAuthId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Remove an integration auth object by object id.",
@@ -165,8 +178,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/oauth-token",
method: "POST",
url: "/oauth-token",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
@@ -206,8 +222,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/access-token",
method: "POST",
url: "/access-token",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Create the integration authentication object required for syncing secrets.",
@@ -256,8 +275,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/apps",
method: "GET",
url: "/:integrationAuthId/apps",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -293,8 +315,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/teams",
method: "GET",
url: "/:integrationAuthId/teams",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -324,8 +349,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/vercel/branches",
method: "GET",
url: "/:integrationAuthId/vercel/branches",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -354,8 +382,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/checkly/groups",
method: "GET",
url: "/:integrationAuthId/checkly/groups",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -384,8 +415,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/github/orgs",
method: "GET",
url: "/:integrationAuthId/github/orgs",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -412,8 +446,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/github/envs",
method: "GET",
url: "/:integrationAuthId/github/envs",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -446,8 +483,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/qovery/orgs",
method: "GET",
url: "/:integrationAuthId/qovery/orgs",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -472,8 +512,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/qovery/projects",
method: "GET",
url: "/:integrationAuthId/qovery/projects",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -502,8 +545,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/qovery/environments",
method: "GET",
url: "/:integrationAuthId/qovery/environments",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -532,8 +578,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/qovery/apps",
method: "GET",
url: "/:integrationAuthId/qovery/apps",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -562,8 +611,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/qovery/containers",
method: "GET",
url: "/:integrationAuthId/qovery/containers",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -592,8 +644,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/qovery/jobs",
method: "GET",
url: "/:integrationAuthId/qovery/jobs",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -622,8 +677,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/heroku/pipelines",
method: "GET",
url: "/:integrationAuthId/heroku/pipelines",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -654,8 +712,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/railway/environments",
method: "GET",
url: "/:integrationAuthId/railway/environments",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -684,8 +745,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/railway/services",
method: "GET",
url: "/:integrationAuthId/railway/services",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -714,8 +778,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/bitbucket/workspaces",
method: "GET",
url: "/:integrationAuthId/bitbucket/workspaces",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -750,8 +817,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/northflank/secret-groups",
method: "GET",
url: "/:integrationAuthId/northflank/secret-groups",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -785,8 +855,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:integrationAuthId/teamcity/build-configs",
method: "GET",
url: "/:integrationAuthId/teamcity/build-configs",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({

View File

@@ -4,6 +4,7 @@ import { IntegrationsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { INTEGRATION } from "@app/lib/api-docs";
import { removeTrailingSlash, shake } from "@app/lib/fn";
import { writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -11,8 +12,11 @@ import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telem
export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
description: "Create an integration to sync secrets.",
security: [
@@ -112,8 +116,11 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:integrationId",
method: "PATCH",
url: "/:integrationId",
config: {
rateLimit: writeLimit
},
schema: {
description: "Update an integration by integration id",
security: [
@@ -159,8 +166,11 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:integrationId",
method: "DELETE",
url: "/:integrationId",
config: {
rateLimit: writeLimit
},
schema: {
description: "Remove an integration using the integration object ID",
security: [

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { UsersSchema } from "@app/db/schemas";
import { inviteUserRateLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
@@ -9,6 +10,9 @@ import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/signup",
config: {
rateLimit: inviteUserRateLimit
},
method: "POST",
schema: {
body: z.object({
@@ -52,6 +56,9 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/verify",
method: "POST",
config: {
rateLimit: inviteUserRateLimit
},
schema: {
body: z.object({
email: z.string().trim().email(),

View File

@@ -1,6 +1,15 @@
import { z } from "zod";
import { IncidentContactsSchema, OrganizationsSchema, OrgMembershipsSchema, UsersSchema } from "@app/db/schemas";
import {
GroupsSchema,
IncidentContactsSchema,
OrganizationsSchema,
OrgMembershipsSchema,
OrgRolesSchema,
UsersSchema
} from "@app/db/schemas";
import { ORGANIZATIONS } 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 +17,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({
@@ -25,6 +37,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:organizationId",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
organizationId: z.string().trim()
@@ -50,6 +65,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:organizationId/users",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
organizationId: z.string().trim()
@@ -87,6 +105,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "PATCH",
url: "/:organizationId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({ organizationId: z.string().trim() }),
body: z.object({
@@ -128,6 +149,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:organizationId/incidentContactOrg",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({ organizationId: z.string().trim() }),
response: {
@@ -151,6 +175,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:organizationId/incidentContactOrg",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({ organizationId: z.string().trim() }),
body: z.object({ email: z.string().email().trim() }),
@@ -176,6 +203,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "DELETE",
url: "/:organizationId/incidentContactOrg/:incidentContactId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({ organizationId: z.string().trim(), incidentContactId: z.string().trim() }),
response: {
@@ -196,4 +226,41 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
return { incidentContactsOrg };
}
});
server.route({
method: "GET",
url: "/:organizationId/groups",
schema: {
params: z.object({
organizationId: z.string().trim().describe(ORGANIZATIONS.LIST_GROUPS.organizationId)
}),
response: {
200: z.object({
groups: GroupsSchema.merge(
z.object({
customRole: OrgRolesSchema.pick({
id: true,
name: true,
slug: true,
permissions: true,
description: true
}).optional()
})
).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const groups = await server.services.org.getOrgGroups({
actor: req.permission.type,
actorId: req.permission.id,
orgId: req.params.organizationId,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return { groups };
}
});
};

View File

@@ -2,7 +2,7 @@ import { z } from "zod";
import { BackupPrivateKeySchema, UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { passwordRateLimit } from "@app/server/config/rateLimiter";
import { authRateLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { validateSignUpAuthorization } from "@app/services/auth/auth-fns";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -12,7 +12,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
method: "POST",
url: "/srp1",
config: {
rateLimit: passwordRateLimit
rateLimit: authRateLimit
},
schema: {
body: z.object({
@@ -39,7 +39,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
method: "POST",
url: "/change-password",
config: {
rateLimit: passwordRateLimit
rateLimit: authRateLimit
},
schema: {
body: z.object({
@@ -78,7 +78,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
method: "POST",
url: "/email/password-reset",
config: {
rateLimit: passwordRateLimit
rateLimit: authRateLimit
},
schema: {
body: z.object({
@@ -103,7 +103,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
method: "POST",
url: "/email/password-reset-verify",
config: {
rateLimit: passwordRateLimit
rateLimit: authRateLimit
},
schema: {
body: z.object({
@@ -133,7 +133,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
method: "POST",
url: "/backup-private-key",
config: {
rateLimit: passwordRateLimit
rateLimit: authRateLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
@@ -168,7 +168,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
method: "GET",
url: "/backup-private-key",
config: {
rateLimit: passwordRateLimit
rateLimit: authRateLimit
},
schema: {
response: {
@@ -190,6 +190,9 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/password-reset",
config: {
rateLimit: authRateLimit
},
schema: {
body: z.object({
protectedKey: z.string().trim(),

View File

@@ -3,13 +3,17 @@ import { z } from "zod";
import { ProjectEnvironmentsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ENVIRONMENTS } from "@app/lib/api-docs";
import { 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 registerProjectEnvRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/environments",
method: "POST",
url: "/:workspaceId/environments",
config: {
rateLimit: writeLimit
},
schema: {
description: "Create environment",
security: [
@@ -64,8 +68,11 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:workspaceId/environments/:id",
method: "PATCH",
url: "/:workspaceId/environments/:id",
config: {
rateLimit: writeLimit
},
schema: {
description: "Update environment",
security: [
@@ -128,8 +135,11 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:workspaceId/environments/:id",
method: "DELETE",
url: "/:workspaceId/environments/:id",
config: {
rateLimit: writeLimit
},
schema: {
description: "Delete environment",
security: [

View File

@@ -1,5 +1,6 @@
import { z } from "zod";
import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -7,6 +8,9 @@ export const registerProjectKeyRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/key",
method: "POST",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()

View File

@@ -10,14 +10,18 @@ import {
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
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";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/memberships",
method: "GET",
url: "/:workspaceId/memberships",
config: {
rateLimit: readLimit
},
schema: {
description: "Return project user memberships",
security: [
@@ -75,8 +79,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
});
server.route({
url: "/:workspaceId/memberships",
method: "POST",
url: "/:workspaceId/memberships",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()
@@ -126,8 +133,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
});
server.route({
url: "/:workspaceId/memberships/:membershipId",
method: "PATCH",
url: "/:workspaceId/memberships/:membershipId",
config: {
rateLimit: writeLimit
},
schema: {
description: "Update project user membership",
security: [
@@ -197,8 +207,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
});
server.route({
url: "/:workspaceId/memberships/:membershipId",
method: "DELETE",
url: "/:workspaceId/memberships/:membershipId",
config: {
rateLimit: writeLimit
},
schema: {
description: "Delete project user membership",
security: [

View File

@@ -7,7 +7,8 @@ import {
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { INTEGRATION_AUTH, PROJECTS } from "@app/lib/api-docs";
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";
import { ProjectFilterType } from "@app/services/project/project-types";
@@ -24,8 +25,11 @@ const projectWithEnv = ProjectsSchema.merge(
export const registerProjectRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/keys",
method: "GET",
url: "/:workspaceId/keys",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()
@@ -55,8 +59,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:workspaceId/users",
method: "GET",
url: "/:workspaceId/users",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()
@@ -108,8 +115,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/",
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({
@@ -125,8 +135,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:workspaceId",
method: "GET",
url: "/:workspaceId",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim().describe(PROJECTS.GET.workspaceId)
@@ -154,8 +167,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:workspaceId",
method: "DELETE",
url: "/:workspaceId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim().describe(PROJECTS.DELETE.workspaceId)
@@ -185,6 +201,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:workspaceId/name",
method: "POST",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()
@@ -217,8 +236,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:workspaceId",
method: "PATCH",
url: "/:workspaceId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim().describe(PROJECTS.UPDATE.workspaceId)
@@ -261,8 +283,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:workspaceId/auto-capitalization",
method: "POST",
url: "/:workspaceId/auto-capitalization",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()
@@ -295,11 +320,20 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:workspaceId/integrations",
method: "GET",
url: "/:workspaceId/integrations",
config: {
rateLimit: readLimit
},
schema: {
description: "List integrations for a project.",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim()
workspaceId: z.string().trim().describe(PROJECTS.LIST_INTEGRATION.workspaceId)
}),
response: {
200: z.object({
@@ -315,7 +349,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const integrations = await server.services.integration.listIntegrationByProject({
actorId: req.permission.id,
@@ -329,8 +363,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:workspaceId/authorizations",
method: "GET",
url: "/:workspaceId/authorizations",
config: {
rateLimit: readLimit
},
schema: {
description: "List integration auth objects for a workspace.",
security: [
@@ -339,7 +376,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
workspaceId: z.string().trim().describe(INTEGRATION_AUTH.LIST_AUTHORIZATION.workspaceId)
workspaceId: z.string().trim().describe(PROJECTS.LIST_INTEGRATION_AUTHORIZATION.workspaceId)
}),
response: {
200: z.object({
@@ -361,8 +398,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:workspaceId/service-token-data",
method: "GET",
url: "/:workspaceId/service-token-data",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()

View File

@@ -4,6 +4,7 @@ import { SecretFoldersSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { FOLDERS } from "@app/lib/api-docs";
import { removeTrailingSlash } from "@app/lib/fn";
import { readLimit, secretsLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -11,6 +12,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
server.route({
url: "/",
method: "POST",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Create folders",
security: [
@@ -65,6 +69,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
server.route({
url: "/:folderId",
method: "PATCH",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Update folder",
security: [
@@ -124,8 +131,11 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
// TODO(daniel): Expose this route in api reference and write docs for it.
server.route({
url: "/:folderIdOrName",
method: "DELETE",
url: "/:folderIdOrName",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Delete a folder",
security: [
@@ -181,8 +191,11 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
});
server.route({
url: "/",
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
description: "Get folders",
security: [

View File

@@ -4,13 +4,17 @@ import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { SECRET_IMPORTS } from "@app/lib/api-docs";
import { removeTrailingSlash } from "@app/lib/fn";
import { readLimit, secretsLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerSecretImportRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
url: "/",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Create secret imports",
security: [
@@ -71,8 +75,11 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
});
server.route({
url: "/:secretImportId",
method: "PATCH",
url: "/:secretImportId",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Update secret imports",
security: [
@@ -143,8 +150,11 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
});
server.route({
url: "/:secretImportId",
method: "DELETE",
url: "/:secretImportId",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Delete secret imports",
security: [
@@ -204,8 +214,11 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
});
server.route({
url: "/",
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
description: "Get secret imports",
security: [
@@ -262,6 +275,9 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
server.route({
url: "/secrets",
method: "GET",
config: {
rateLimit: secretsLimit
},
schema: {
querystring: z.object({
workspaceId: z.string().trim(),

View File

@@ -2,13 +2,17 @@ import { z } from "zod";
import { SecretTagsSchema } from "@app/db/schemas";
import { SECRET_TAGS } 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";
export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:projectId/tags",
method: "GET",
url: "/:projectId/tags",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.LIST.projectId)
@@ -33,8 +37,11 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:projectId/tags",
method: "POST",
url: "/:projectId/tags",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.CREATE.projectId)
@@ -65,8 +72,11 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:projectId/tags/:tagId",
method: "DELETE",
url: "/:projectId/tags/:tagId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.DELETE.projectId),

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { UserActionsSchema } 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 registerUserActionRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
action: z.string().trim()
@@ -29,6 +33,9 @@ export const registerUserActionRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "GET",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
action: z.string().trim()

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { UserEncryptionKeysSchema, UsersSchema } 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";
@@ -8,6 +9,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({

View File

@@ -3,6 +3,7 @@ import { z } from "zod";
import { WebhooksSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
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";
@@ -27,6 +28,9 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
@@ -75,6 +79,9 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
server.route({
method: "PATCH",
url: "/:webhookId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -122,6 +129,9 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
server.route({
method: "DELETE",
url: "/:webhookId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -159,6 +169,9 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:webhookId/test",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
@@ -186,6 +199,9 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
querystring: z.object({

View File

@@ -0,0 +1,201 @@
import ms from "ms";
import { z } from "zod";
import {
GroupProjectMembershipsSchema,
GroupsSchema,
ProjectMembershipRole,
ProjectUserMembershipRolesSchema
} from "@app/db/schemas";
import { PROJECTS } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerGroupProjectRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:projectSlug/groups/:groupSlug",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Add group to project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectSlug: z.string().trim().describe(PROJECTS.ADD_GROUP_TO_PROJECT.projectSlug),
groupSlug: z.string().trim().describe(PROJECTS.ADD_GROUP_TO_PROJECT.groupSlug)
}),
body: z.object({
role: z
.string()
.trim()
.min(1)
.default(ProjectMembershipRole.NoAccess)
.describe(PROJECTS.ADD_GROUP_TO_PROJECT.role)
}),
response: {
200: z.object({
groupMembership: GroupProjectMembershipsSchema
})
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.addGroupToProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
groupSlug: req.params.groupSlug,
projectSlug: req.params.projectSlug,
role: req.body.role
});
return { groupMembership };
}
});
server.route({
method: "PATCH",
url: "/:projectSlug/groups/:groupSlug",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Update group in project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectSlug: z.string().trim().describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.projectSlug),
groupSlug: z.string().trim().describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.groupSlug)
}),
body: z.object({
roles: z
.array(
z.union([
z.object({
role: z.string(),
isTemporary: z.literal(false).default(false)
}),
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
])
)
.min(1)
.describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.roles)
}),
response: {
200: z.object({
roles: ProjectUserMembershipRolesSchema.array()
})
}
},
handler: async (req) => {
const roles = await server.services.groupProject.updateGroupInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
groupSlug: req.params.groupSlug,
projectSlug: req.params.projectSlug,
roles: req.body.roles
});
return { roles };
}
});
server.route({
method: "DELETE",
url: "/:projectSlug/groups/:groupSlug",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Remove group from project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectSlug: z.string().trim().describe(PROJECTS.REMOVE_GROUP_FROM_PROJECT.projectSlug),
groupSlug: z.string().trim().describe(PROJECTS.REMOVE_GROUP_FROM_PROJECT.groupSlug)
}),
response: {
200: z.object({
groupMembership: GroupProjectMembershipsSchema
})
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.removeGroupFromProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
groupSlug: req.params.groupSlug,
projectSlug: req.params.projectSlug
});
return { groupMembership };
}
});
server.route({
method: "GET",
url: "/:projectSlug/groups",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Return list of groups in project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectSlug: z.string().trim().describe(PROJECTS.LIST_GROUPS_IN_PROJECT.projectSlug)
}),
response: {
200: z.object({
groupMemberships: z
.object({
id: z.string(),
groupId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
),
group: GroupsSchema.pick({ name: true, id: true, slug: true })
})
.array()
})
}
},
handler: async (req) => {
const groupMemberships = await server.services.groupProject.listGroupsInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectSlug: req.params.projectSlug
});
return { groupMemberships };
}
});
};

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
import { ORGANIZATIONS } from "@app/lib/api-docs";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -9,6 +10,9 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:orgId/identity-memberships",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Return organization identity memberships",
@@ -46,6 +50,7 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
actorOrgId: req.permission.orgId,
orgId: req.params.orgId
});
return { identityMemberships };
}
});

View File

@@ -8,6 +8,7 @@ import {
ProjectUserMembershipRolesSchema
} 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";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
@@ -16,6 +17,9 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
server.route({
method: "POST",
url: "/:projectId/identity-memberships/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
params: z.object({
@@ -48,6 +52,9 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
server.route({
method: "PATCH",
url: "/:projectId/identity-memberships/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Update project identity memberships",
@@ -103,6 +110,9 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
server.route({
method: "DELETE",
url: "/:projectId/identity-memberships/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete project identity memberships",
@@ -137,6 +147,9 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
server.route({
method: "GET",
url: "/:projectId/identity-memberships",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Return project identity memberships",

View File

@@ -1,3 +1,4 @@
import { registerGroupProjectRouter } from "./group-project-router";
import { registerIdentityOrgRouter } from "./identity-org-router";
import { registerIdentityProjectRouter } from "./identity-project-router";
import { registerMfaRouter } from "./mfa-router";
@@ -22,6 +23,7 @@ export const registerV2Routes = async (server: FastifyZodProvider) => {
async (projectServer) => {
await projectServer.register(registerProjectRouter);
await projectServer.register(registerIdentityProjectRouter);
await projectServer.register(registerGroupProjectRouter);
await projectServer.register(registerProjectMembershipRouter);
},
{ prefix: "/workspace" }

View File

@@ -2,6 +2,7 @@ import jwt from "jsonwebtoken";
import { z } from "zod";
import { getConfig } from "@app/lib/config/env";
import { writeLimit } from "@app/server/config/rateLimiter";
import { AuthModeMfaJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
export const registerMfaRouter = async (server: FastifyZodProvider) => {
@@ -30,8 +31,11 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/mfa/send",
method: "POST",
url: "/mfa/send",
config: {
rateLimit: writeLimit
},
schema: {
response: {
200: z.object({
@@ -48,6 +52,9 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/mfa/verify",
method: "POST",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
mfaToken: z.string().trim()

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import { OrganizationsSchema, OrgMembershipsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import { ORGANIZATIONS } from "@app/lib/api-docs";
import { creationLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
@@ -9,6 +10,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:organizationId/memberships",
config: {
rateLimit: readLimit
},
schema: {
description: "Return organization user memberships",
security: [
@@ -55,6 +59,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:organizationId/workspaces",
config: {
rateLimit: readLimit
},
schema: {
description: "Return projects in organization that user is part of",
security: [
@@ -101,6 +108,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "PATCH",
url: "/:organizationId/memberships/:membershipId",
config: {
rateLimit: writeLimit
},
schema: {
description: "Update organization user memberships",
security: [
@@ -141,6 +151,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "DELETE",
url: "/:organizationId/memberships/:membershipId",
config: {
rateLimit: writeLimit
},
schema: {
description: "Delete organization user memberships",
security: [
@@ -177,6 +190,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: creationLimit
},
schema: {
body: z.object({
name: z.string().trim()
@@ -204,6 +220,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "DELETE",
url: "/:organizationId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
organizationId: z.string().trim()

View File

@@ -3,6 +3,7 @@ import { z } from "zod";
import { ProjectMembershipsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { PROJECTS } from "@app/lib/api-docs";
import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -10,6 +11,9 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
server.route({
method: "POST",
url: "/:projectId/memberships",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().describe(PROJECTS.INVITE_MEMBER.projectId)
@@ -56,6 +60,9 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
server.route({
method: "DELETE",
url: "/:projectId/memberships",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().describe(PROJECTS.REMOVE_MEMBER.projectId)

View File

@@ -4,7 +4,7 @@ import { z } from "zod";
import { ProjectKeysSchema, ProjectsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { PROJECTS } from "@app/lib/api-docs";
import { authRateLimit } from "@app/server/config/rateLimiter";
import { creationLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -29,8 +29,11 @@ const slugSchema = z
export const registerProjectRouter = async (server: FastifyZodProvider) => {
/* Get project key */
server.route({
url: "/:workspaceId/encrypted-key",
method: "GET",
url: "/:workspaceId/encrypted-key",
config: {
rateLimit: readLimit
},
schema: {
description: "Return encrypted project key",
security: [
@@ -78,8 +81,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
/* Start upgrade of a project */
server.route({
url: "/:projectId/upgrade",
method: "POST",
url: "/:projectId/upgrade",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
@@ -108,6 +114,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:projectId/upgrade/status",
method: "GET",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
@@ -137,7 +146,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
method: "POST",
url: "/",
config: {
rateLimit: authRateLimit
rateLimit: creationLimit
},
schema: {
body: z.object({
@@ -187,6 +196,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
server.route({
method: "DELETE",
url: "/:slug",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
slug: slugSchema.describe("The slug of the project to delete.")
@@ -218,6 +230,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:slug",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
slug: slugSchema.describe("The slug of the project to get.")
@@ -248,6 +263,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
server.route({
method: "PATCH",
url: "/:slug",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
slug: slugSchema.describe("The slug of the project to update.")

View File

@@ -3,6 +3,7 @@ import { z } from "zod";
import { ServiceTokensSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
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";
@@ -17,8 +18,11 @@ export const sanitizedServiceTokenSchema = ServiceTokensSchema.omit({
export const registerServiceTokenRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.SERVICE_TOKEN]),
schema: {
description: "Return Infisical Token data",
@@ -69,8 +73,11 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) =>
});
server.route({
url: "/",
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
@@ -122,8 +129,11 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) =>
});
server.route({
url: "/:serviceTokenId",
method: "DELETE",
url: "/:serviceTokenId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({

View File

@@ -2,13 +2,17 @@ import { z } from "zod";
import { AuthTokenSessionsSchema, OrganizationsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import { ApiKeysSchema } from "@app/db/schemas/api-keys";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMethod, AuthMode } from "@app/services/auth/auth-type";
export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/me/mfa",
method: "PATCH",
url: "/me/mfa",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
isMfaEnabled: z.boolean()
@@ -27,8 +31,11 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/me/name",
method: "PATCH",
url: "/me/name",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
firstName: z.string().trim(),
@@ -48,8 +55,11 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/me/auth-methods",
method: "PUT",
url: "/me/auth-methods",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
authMethods: z.nativeEnum(AuthMethod).array().min(1)
@@ -70,6 +80,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/me/organizations",
config: {
rateLimit: readLimit
},
schema: {
description: "Return organizations that current user is part of",
security: [
@@ -93,6 +106,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/me/api-keys",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: ApiKeysSchema.omit({ secretHash: true }).array()
@@ -108,6 +124,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/me/api-keys",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
name: z.string().trim(),
@@ -130,6 +149,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "DELETE",
url: "/me/api-keys/:apiKeyDataId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
apiKeyDataId: z.string().trim()
@@ -150,6 +172,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/me/sessions",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: AuthTokenSessionsSchema.array()
@@ -165,6 +190,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "DELETE",
url: "/me/sessions",
config: {
rateLimit: writeLimit
},
schema: {
response: {
200: z.object({
@@ -184,6 +212,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/me",
config: {
rateLimit: readLimit
},
schema: {
description: "Retrieve the current user on the request",
security: [
@@ -207,6 +238,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "DELETE",
url: "/me",
config: {
rateLimit: writeLimit
},
schema: {
response: {
200: z.object({

View File

@@ -1,13 +1,17 @@
import { z } from "zod";
import { SecretsSchema } 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";
export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:projectId/secrets/blind-index-status",
method: "GET",
url: "/:projectId/secrets/blind-index-status",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
@@ -30,8 +34,11 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:projectId/secrets",
method: "GET",
url: "/:projectId/secrets",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
@@ -63,8 +70,11 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider)
});
server.route({
url: "/:projectId/secrets/names",
method: "POST",
url: "/:projectId/secrets/names",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().trim()

View File

@@ -13,6 +13,7 @@ import { CommitType } from "@app/ee/services/secret-approval-request/secret-appr
import { RAW_SECRETS, SECRETS } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn";
import { secretsLimit, writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { getUserAgentType } from "@app/server/plugins/audit-log";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -24,8 +25,11 @@ import { secretRawSchema } from "../sanitizedSchemas";
export const registerSecretRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/tags/:secretName",
method: "POST",
url: "/tags/:secretName",
config: {
rateLimit: writeLimit
},
schema: {
description: "Attach tags to a secret",
security: [
@@ -83,8 +87,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/tags/:secretName",
method: "DELETE",
url: "/tags/:secretName",
config: {
rateLimit: writeLimit
},
schema: {
description: "Detach tags from a secret",
security: [
@@ -142,8 +149,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/raw",
method: "GET",
url: "/raw",
config: {
rateLimit: secretsLimit
},
schema: {
description: "List secrets",
security: [
@@ -157,6 +167,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug),
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath),
recursive: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
.describe(RAW_SECRETS.LIST.recursive),
include_imports: z
.enum(["true", "false"])
.default("false")
@@ -165,7 +180,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
secrets: secretRawSchema.array(),
secrets: secretRawSchema
.extend({
secretPath: z.string().optional()
})
.array(),
imports: z
.object({
secretPath: z.string(),
@@ -218,7 +237,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
actorAuthMethod: req.permission.authMethod,
projectId: workspaceId,
path: secretPath,
includeImports: req.query.include_imports
includeImports: req.query.include_imports,
recursive: req.query.recursive
});
await server.services.auditLog.createAuditLog({
@@ -251,8 +271,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/raw/:secretName",
method: "GET",
url: "/raw/:secretName",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Get a secret by name",
security: [
@@ -343,8 +366,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/raw/:secretName",
method: "POST",
url: "/raw/:secretName",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Create secret",
security: [
@@ -429,8 +455,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/raw/:secretName",
method: "PATCH",
url: "/raw/:secretName",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Update secret",
security: [
@@ -512,8 +541,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/raw/:secretName",
method: "DELETE",
url: "/raw/:secretName",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Delete secret",
security: [
@@ -589,13 +621,20 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/",
method: "GET",
url: "/",
config: {
rateLimit: secretsLimit
},
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
recursive: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true"),
include_imports: z
.enum(["true", "false"])
.default("false")
@@ -604,19 +643,18 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
response: {
200: z.object({
secrets: SecretsSchema.omit({ secretBlindIndex: true })
.merge(
z.object({
_id: z.string(),
workspace: z.string(),
environment: z.string(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,
name: true,
color: true
}).array()
})
)
.extend({
_id: z.string(),
workspace: z.string(),
environment: z.string(),
secretPath: z.string().optional(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,
name: true,
color: true
}).array()
})
.array(),
imports: z
.object({
@@ -648,7 +686,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
environment: req.query.environment,
projectId: req.query.workspaceId,
path: req.query.secretPath,
includeImports: req.query.include_imports
includeImports: req.query.include_imports,
recursive: req.query.recursive
});
await server.services.auditLog.createAuditLog({
@@ -697,8 +736,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:secretName",
method: "GET",
url: "/:secretName",
config: {
rateLimit: secretsLimit
},
schema: {
params: z.object({
secretName: z.string().trim()
@@ -775,6 +817,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:secretName",
method: "POST",
config: {
rateLimit: secretsLimit
},
schema: {
body: z.object({
workspaceId: z.string().trim(),
@@ -941,8 +986,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:secretName",
method: "PATCH",
url: "/:secretName",
config: {
rateLimit: secretsLimit
},
schema: {
params: z.object({
secretName: z.string()
@@ -1125,8 +1173,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:secretName",
method: "DELETE",
url: "/:secretName",
config: {
rateLimit: secretsLimit
},
schema: {
params: z.object({
secretName: z.string()
@@ -1246,8 +1297,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/batch",
method: "POST",
url: "/batch",
config: {
rateLimit: secretsLimit
},
schema: {
body: z.object({
workspaceId: z.string().trim(),
@@ -1369,8 +1423,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/batch",
method: "PATCH",
url: "/batch",
config: {
rateLimit: secretsLimit
},
schema: {
body: z.object({
workspaceId: z.string().trim(),
@@ -1492,8 +1549,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/batch",
method: "DELETE",
url: "/batch",
config: {
rateLimit: secretsLimit
},
schema: {
body: z.object({
workspaceId: z.string().trim(),

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { ApiKeysSchema } from "@app/db/schemas/api-keys";
import { readLimit } 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 registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/me/api-keys",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({

Some files were not shown because too many files have changed in this diff Show More