mirror of
https://github.com/Infisical/infisical.git
synced 2025-08-21 05:08:50 +00:00
Compare commits
212 Commits
infisical/
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
|
a8264b17e4 | ||
|
cb66733e6d | ||
|
40a0691ccb | ||
|
6410d51033 | ||
|
a0259712df | ||
|
1132d07dea | ||
|
1f0b1964b9 | ||
|
fbc7b34786 | ||
|
9e6641c058 | ||
|
d035403af1 | ||
|
1af0d958dd | ||
|
66a51658d7 | ||
|
28dc3a4b1c | ||
|
b27cadb651 | ||
|
3dca82ad2f | ||
|
1c90df9dd4 | ||
|
e15c9e72c6 | ||
|
702cd0d403 | ||
|
75267987fc | ||
|
d734a3f6f4 | ||
|
cbb749e34a | ||
|
4535c1069a | ||
|
747acfe070 | ||
|
fa1b236f26 | ||
|
c98ef0eca8 | ||
|
9f23106c6c | ||
|
1e7744b498 | ||
|
44c736facd | ||
|
51928ddb47 | ||
|
c7cded4af6 | ||
|
8b56e20b42 | ||
|
39c2c37cc0 | ||
|
3131ae7dae | ||
|
5315a67d74 | ||
|
79de7f9f5b | ||
|
71ffed026d | ||
|
ee98b15e2b | ||
|
945d81ad4b | ||
|
ff8354605c | ||
|
09b63eee90 | ||
|
d175256bb4 | ||
|
ee0c79d018 | ||
|
d5d7564550 | ||
|
0db682c5f0 | ||
|
a01a995585 | ||
|
2ac785493a | ||
|
85489a81ff | ||
|
7116c85f2c | ||
|
31e4da0dd3 | ||
|
f255d891ae | ||
|
4774469244 | ||
|
e143a31e79 | ||
|
f6cc20b08b | ||
|
90e125454e | ||
|
fbdf3dc9ce | ||
|
f333c905d9 | ||
|
71e60df39a | ||
|
8b4d050d05 | ||
|
3b4bb591a3 | ||
|
54f1a4416b | ||
|
47e3f1b510 | ||
|
5810b76027 | ||
|
246e6c64d1 | ||
|
4e836c5dca | ||
|
63a289c3be | ||
|
0a52bbd55d | ||
|
593bdf74b8 | ||
|
1f3742e619 | ||
|
d6e5ac2133 | ||
|
fea48518a3 | ||
|
94d509eb01 | ||
|
055fd34c33 | ||
|
dc0d3b860e | ||
|
74fefa9879 | ||
|
ff2c8d017f | ||
|
ba1f8f4564 | ||
|
e26df005c2 | ||
|
aca9b47f82 | ||
|
a16ce8899b | ||
|
b61511d100 | ||
|
f8ea421a0e | ||
|
a945bdfc4c | ||
|
f7b8345da4 | ||
|
f6d7ec52c2 | ||
|
3f6999b2e3 | ||
|
9128461409 | ||
|
893235c40f | ||
|
d3cdaa8449 | ||
|
e0f655ae30 | ||
|
93aeca3a38 | ||
|
1edebdf8a5 | ||
|
1017707642 | ||
|
5639306303 | ||
|
b3a9661755 | ||
|
72f50ec399 | ||
|
effc7a3627 | ||
|
175ce865aa | ||
|
51f220ba2c | ||
|
51819e57d1 | ||
|
510c91cef1 | ||
|
9be5d89fcf | ||
|
94f4497903 | ||
|
e1d9f779b2 | ||
|
b5af5646ee | ||
|
1554618167 | ||
|
5fbfcdda30 | ||
|
cdbb3b9c47 | ||
|
0042a95b21 | ||
|
53233e05d4 | ||
|
4f15f9c8d3 | ||
|
97223fabe6 | ||
|
04b312cbe4 | ||
|
40bb9668fe | ||
|
97e5069cf5 | ||
|
93146fcd96 | ||
|
87d98de4c1 | ||
|
26f647b948 | ||
|
80b3cdd128 | ||
|
8dd85a0d65 | ||
|
17995d301a | ||
|
094b48a2b1 | ||
|
abd62867eb | ||
|
179573a269 | ||
|
457edef5fe | ||
|
f0b84d5bc9 | ||
|
7b8bfe38f0 | ||
|
9903f7c4a0 | ||
|
42cd98d4d9 | ||
|
4b203e9ad3 | ||
|
36bf1b2abc | ||
|
42fb732955 | ||
|
da2dcb347a | ||
|
b9482966cf | ||
|
1e4b4591ed | ||
|
9fddcea3db | ||
|
4a325d6d96 | ||
|
5e20573110 | ||
|
f623c8159d | ||
|
4323407da7 | ||
|
4c496d5e3d | ||
|
0c2e566184 | ||
|
d68dc4c3e0 | ||
|
e64c579dfd | ||
|
d0c0d5835c | ||
|
af2dcdd0c7 | ||
|
6c628a7265 | ||
|
00f2d40803 | ||
|
0a66cbe729 | ||
|
7fec7c9bf5 | ||
|
38adc83f2b | ||
|
f2e5f1bb10 | ||
|
d1afec4f9a | ||
|
9460eafd91 | ||
|
31ad6b0c86 | ||
|
8afecac7d8 | ||
|
bf13b81c0f | ||
|
c753a91958 | ||
|
695a4a34b5 | ||
|
372f71f2b0 | ||
|
e46256f45b | ||
|
64e868a151 | ||
|
c8cbcaf10c | ||
|
51716336c2 | ||
|
6b51c7269a | ||
|
f551a4158d | ||
|
e850b82fb3 | ||
|
8f85f292db | ||
|
5f84de039f | ||
|
8529fac098 | ||
|
81cf19cb4a | ||
|
edbe1c8eae | ||
|
a5039494cd | ||
|
a908471e66 | ||
|
84204c3c37 | ||
|
4931e8579c | ||
|
20dc243fd9 | ||
|
785a1389d9 | ||
|
5a3fc3568a | ||
|
497601e398 | ||
|
0da6262ead | ||
|
8db019d2fe | ||
|
07d1d91110 | ||
|
bb506fff9f | ||
|
7a561bcbdf | ||
|
8784f80fc1 | ||
|
0793e70c26 | ||
|
99f8799ff4 | ||
|
3f05c8b7ae | ||
|
6bd624a0f6 | ||
|
4a11096ea8 | ||
|
1589eb8e03 | ||
|
b370d6e415 | ||
|
65937d6a17 | ||
|
4f05e4ce93 | ||
|
2e8680c5d4 | ||
|
e5136c9ef5 | ||
|
812fe5cf31 | ||
|
50082e192c | ||
|
1e1b5d655e | ||
|
3befd90723 | ||
|
88549f4030 | ||
|
46a638cc63 | ||
|
566f7e4c61 | ||
|
9ff3210ed6 | ||
|
f91a6683c2 | ||
|
c29cb667d7 | ||
|
8ffbaa2f6c | ||
|
796d5e3540 | ||
|
686b88fc97 | ||
|
2a134b9dc2 | ||
|
d8d63ecaec | ||
|
efc186ae6c |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -59,6 +59,8 @@ yarn-error.log*
|
|||||||
# Infisical init
|
# Infisical init
|
||||||
.infisical.json
|
.infisical.json
|
||||||
|
|
||||||
|
.infisicalignore
|
||||||
|
|
||||||
# Editor specific
|
# Editor specific
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|
||||||
|
@@ -1 +1,5 @@
|
|||||||
.github/resources/docker-compose.be-test.yml:generic-api-key:16
|
.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
|
@@ -1,6 +1,7 @@
|
|||||||
ARG POSTHOG_HOST=https://app.posthog.com
|
ARG POSTHOG_HOST=https://app.posthog.com
|
||||||
ARG POSTHOG_API_KEY=posthog-api-key
|
ARG POSTHOG_API_KEY=posthog-api-key
|
||||||
ARG INTERCOM_ID=intercom-id
|
ARG INTERCOM_ID=intercom-id
|
||||||
|
ARG SAML_ORG_SLUG=saml-org-slug-default
|
||||||
|
|
||||||
FROM node:20-alpine AS base
|
FROM node:20-alpine AS base
|
||||||
|
|
||||||
@@ -35,6 +36,8 @@ ARG INTERCOM_ID
|
|||||||
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
|
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
|
||||||
ARG INFISICAL_PLATFORM_VERSION
|
ARG INFISICAL_PLATFORM_VERSION
|
||||||
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION $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
|
# Build
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
@@ -100,6 +103,9 @@ ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
|
|||||||
ARG INTERCOM_ID=intercom-id
|
ARG INTERCOM_ID=intercom-id
|
||||||
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
|
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
|
||||||
BAKED_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 /
|
WORKDIR /
|
||||||
|
|
||||||
|
@@ -46,7 +46,7 @@ const deleteSecretImport = async (id: string) => {
|
|||||||
|
|
||||||
describe("Secret Import Router", async () => {
|
describe("Secret Import Router", async () => {
|
||||||
test.each([
|
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
|
{ importEnv: "staging", importPath: "/" } // then create a deep one creating intermediate ones
|
||||||
])("Create secret import $importEnv with path $importPath", async ({ importPath, importEnv }) => {
|
])("Create secret import $importEnv with path $importPath", async ({ importPath, importEnv }) => {
|
||||||
// check for default environments
|
// check for default environments
|
||||||
@@ -66,7 +66,7 @@ describe("Secret Import Router", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("Get secret imports", async () => {
|
test("Get secret imports", async () => {
|
||||||
const createdImport1 = await createSecretImport("/", "dev");
|
const createdImport1 = await createSecretImport("/", "prod");
|
||||||
const createdImport2 = await createSecretImport("/", "staging");
|
const createdImport2 = await createSecretImport("/", "staging");
|
||||||
const res = await testServer.inject({
|
const res = await testServer.inject({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -103,10 +103,10 @@ describe("Secret Import Router", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("Update secret import position", async () => {
|
test("Update secret import position", async () => {
|
||||||
const devImportDetails = { path: "/", envSlug: "dev" };
|
const prodImportDetails = { path: "/", envSlug: "prod" };
|
||||||
const stagingImportDetails = { path: "/", envSlug: "staging" };
|
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 createdImport2 = await createSecretImport(stagingImportDetails.path, stagingImportDetails.envSlug);
|
||||||
|
|
||||||
const updateImportRes = await testServer.inject({
|
const updateImportRes = await testServer.inject({
|
||||||
@@ -136,7 +136,7 @@ describe("Secret Import Router", async () => {
|
|||||||
position: 2,
|
position: 2,
|
||||||
importEnv: expect.objectContaining({
|
importEnv: expect.objectContaining({
|
||||||
name: expect.any(String),
|
name: expect.any(String),
|
||||||
slug: expect.stringMatching(devImportDetails.envSlug),
|
slug: expect.stringMatching(prodImportDetails.envSlug),
|
||||||
id: expect.any(String)
|
id: expect.any(String)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -166,7 +166,7 @@ describe("Secret Import Router", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("Delete secret import position", async () => {
|
test("Delete secret import position", async () => {
|
||||||
const createdImport1 = await createSecretImport("/", "dev");
|
const createdImport1 = await createSecretImport("/", "prod");
|
||||||
const createdImport2 = await createSecretImport("/", "staging");
|
const createdImport2 = await createSecretImport("/", "staging");
|
||||||
const deletedImport = await deleteSecretImport(createdImport1.id);
|
const deletedImport = await deleteSecretImport(createdImport1.id);
|
||||||
// check for default environments
|
// check for default environments
|
||||||
|
@@ -103,11 +103,15 @@ export const ${dalName} = (db: TDbClient) => {
|
|||||||
`import { z } from "zod";
|
`import { z } from "zod";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
|
||||||
export const register${pascalCase}Router = async (server: FastifyZodProvider) => {
|
export const register${pascalCase}Router = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({}),
|
params: z.object({}),
|
||||||
response: {
|
response: {
|
||||||
|
@@ -7,10 +7,10 @@ const prompt = promptSync({ sigint: true });
|
|||||||
|
|
||||||
const migrationName = prompt("Enter name for migration: ");
|
const migrationName = prompt("Enter name for migration: ");
|
||||||
|
|
||||||
|
// Remove spaces from migration name and replace with hyphens
|
||||||
|
const formattedMigrationName = migrationName.replace(/\s+/g, "-");
|
||||||
|
|
||||||
execSync(
|
execSync(
|
||||||
`npx knex migrate:make --knexfile ${path.join(
|
`npx knex migrate:make --knexfile ${path.join(__dirname, "../src/db/knexfile.ts")} -x ts ${formattedMigrationName}`,
|
||||||
__dirname,
|
|
||||||
"../src/db/knexfile.ts"
|
|
||||||
)} -x ts ${migrationName}`,
|
|
||||||
{ stdio: "inherit" }
|
{ stdio: "inherit" }
|
||||||
);
|
);
|
||||||
|
12
backend/src/@types/fastify.d.ts
vendored
12
backend/src/@types/fastify.d.ts
vendored
@@ -3,9 +3,14 @@ import "fastify";
|
|||||||
import { TUsers } from "@app/db/schemas";
|
import { TUsers } from "@app/db/schemas";
|
||||||
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
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 { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service";
|
||||||
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
||||||
import { TScimServiceFactory } from "@app/ee/services/scim/scim-service";
|
import { TScimServiceFactory } from "@app/ee/services/scim/scim-service";
|
||||||
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||||
@@ -21,8 +26,7 @@ import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
|
|||||||
import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service";
|
import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service";
|
||||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
import { TDynamicSecretServiceFactory } from "@app/services/dynamic-secret/dynamic-secret-service";
|
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
||||||
import { TDynamicSecretLeaseServiceFactory } from "@app/services/dynamic-secret-lease/dynamic-secret-lease-service";
|
|
||||||
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
||||||
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||||
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
||||||
@@ -87,6 +91,8 @@ declare module "fastify" {
|
|||||||
orgRole: TOrgRoleServiceFactory;
|
orgRole: TOrgRoleServiceFactory;
|
||||||
superAdmin: TSuperAdminServiceFactory;
|
superAdmin: TSuperAdminServiceFactory;
|
||||||
user: TUserServiceFactory;
|
user: TUserServiceFactory;
|
||||||
|
group: TGroupServiceFactory;
|
||||||
|
groupProject: TGroupProjectServiceFactory;
|
||||||
apiKey: TApiKeyServiceFactory;
|
apiKey: TApiKeyServiceFactory;
|
||||||
project: TProjectServiceFactory;
|
project: TProjectServiceFactory;
|
||||||
projectMembership: TProjectMembershipServiceFactory;
|
projectMembership: TProjectMembershipServiceFactory;
|
||||||
@@ -121,6 +127,8 @@ declare module "fastify" {
|
|||||||
telemetry: TTelemetryServiceFactory;
|
telemetry: TTelemetryServiceFactory;
|
||||||
dynamicSecret: TDynamicSecretServiceFactory;
|
dynamicSecret: TDynamicSecretServiceFactory;
|
||||||
dynamicSecretLease: TDynamicSecretLeaseServiceFactory;
|
dynamicSecretLease: TDynamicSecretLeaseServiceFactory;
|
||||||
|
projectUserAdditionalPrivilege: TProjectUserAdditionalPrivilegeServiceFactory;
|
||||||
|
identityProjectAdditionalPrivilege: TIdentityProjectAdditionalPrivilegeServiceFactory;
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
44
backend/src/@types/knex.d.ts
vendored
44
backend/src/@types/knex.d.ts
vendored
@@ -29,6 +29,15 @@ import {
|
|||||||
TGitAppOrg,
|
TGitAppOrg,
|
||||||
TGitAppOrgInsert,
|
TGitAppOrgInsert,
|
||||||
TGitAppOrgUpdate,
|
TGitAppOrgUpdate,
|
||||||
|
TGroupProjectMembershipRoles,
|
||||||
|
TGroupProjectMembershipRolesInsert,
|
||||||
|
TGroupProjectMembershipRolesUpdate,
|
||||||
|
TGroupProjectMemberships,
|
||||||
|
TGroupProjectMembershipsInsert,
|
||||||
|
TGroupProjectMembershipsUpdate,
|
||||||
|
TGroups,
|
||||||
|
TGroupsInsert,
|
||||||
|
TGroupsUpdate,
|
||||||
TIdentities,
|
TIdentities,
|
||||||
TIdentitiesInsert,
|
TIdentitiesInsert,
|
||||||
TIdentitiesUpdate,
|
TIdentitiesUpdate,
|
||||||
@@ -38,6 +47,9 @@ import {
|
|||||||
TIdentityOrgMemberships,
|
TIdentityOrgMemberships,
|
||||||
TIdentityOrgMembershipsInsert,
|
TIdentityOrgMembershipsInsert,
|
||||||
TIdentityOrgMembershipsUpdate,
|
TIdentityOrgMembershipsUpdate,
|
||||||
|
TIdentityProjectAdditionalPrivilege,
|
||||||
|
TIdentityProjectAdditionalPrivilegeInsert,
|
||||||
|
TIdentityProjectAdditionalPrivilegeUpdate,
|
||||||
TIdentityProjectMembershipRole,
|
TIdentityProjectMembershipRole,
|
||||||
TIdentityProjectMembershipRoleInsert,
|
TIdentityProjectMembershipRoleInsert,
|
||||||
TIdentityProjectMembershipRoleUpdate,
|
TIdentityProjectMembershipRoleUpdate,
|
||||||
@@ -92,6 +104,9 @@ import {
|
|||||||
TProjects,
|
TProjects,
|
||||||
TProjectsInsert,
|
TProjectsInsert,
|
||||||
TProjectsUpdate,
|
TProjectsUpdate,
|
||||||
|
TProjectUserAdditionalPrivilege,
|
||||||
|
TProjectUserAdditionalPrivilegeInsert,
|
||||||
|
TProjectUserAdditionalPrivilegeUpdate,
|
||||||
TProjectUserMembershipRoles,
|
TProjectUserMembershipRoles,
|
||||||
TProjectUserMembershipRolesInsert,
|
TProjectUserMembershipRolesInsert,
|
||||||
TProjectUserMembershipRolesUpdate,
|
TProjectUserMembershipRolesUpdate,
|
||||||
@@ -182,6 +197,9 @@ import {
|
|||||||
TUserEncryptionKeys,
|
TUserEncryptionKeys,
|
||||||
TUserEncryptionKeysInsert,
|
TUserEncryptionKeysInsert,
|
||||||
TUserEncryptionKeysUpdate,
|
TUserEncryptionKeysUpdate,
|
||||||
|
TUserGroupMembership,
|
||||||
|
TUserGroupMembershipInsert,
|
||||||
|
TUserGroupMembershipUpdate,
|
||||||
TUsers,
|
TUsers,
|
||||||
TUsersInsert,
|
TUsersInsert,
|
||||||
TUsersUpdate,
|
TUsersUpdate,
|
||||||
@@ -193,6 +211,22 @@ import {
|
|||||||
declare module "knex/types/tables" {
|
declare module "knex/types/tables" {
|
||||||
interface Tables {
|
interface Tables {
|
||||||
[TableName.Users]: Knex.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>;
|
[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.UserAliases]: Knex.CompositeTableType<TUserAliases, TUserAliasesInsert, TUserAliasesUpdate>;
|
||||||
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
|
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
|
||||||
TUserEncryptionKeys,
|
TUserEncryptionKeys,
|
||||||
@@ -239,6 +273,11 @@ declare module "knex/types/tables" {
|
|||||||
TProjectUserMembershipRolesUpdate
|
TProjectUserMembershipRolesUpdate
|
||||||
>;
|
>;
|
||||||
[TableName.ProjectRoles]: Knex.CompositeTableType<TProjectRoles, TProjectRolesInsert, TProjectRolesUpdate>;
|
[TableName.ProjectRoles]: Knex.CompositeTableType<TProjectRoles, TProjectRolesInsert, TProjectRolesUpdate>;
|
||||||
|
[TableName.ProjectUserAdditionalPrivilege]: Knex.CompositeTableType<
|
||||||
|
TProjectUserAdditionalPrivilege,
|
||||||
|
TProjectUserAdditionalPrivilegeInsert,
|
||||||
|
TProjectUserAdditionalPrivilegeUpdate
|
||||||
|
>;
|
||||||
[TableName.ProjectKeys]: Knex.CompositeTableType<TProjectKeys, TProjectKeysInsert, TProjectKeysUpdate>;
|
[TableName.ProjectKeys]: Knex.CompositeTableType<TProjectKeys, TProjectKeysInsert, TProjectKeysUpdate>;
|
||||||
[TableName.Secret]: Knex.CompositeTableType<TSecrets, TSecretsInsert, TSecretsUpdate>;
|
[TableName.Secret]: Knex.CompositeTableType<TSecrets, TSecretsInsert, TSecretsUpdate>;
|
||||||
[TableName.SecretBlindIndex]: Knex.CompositeTableType<
|
[TableName.SecretBlindIndex]: Knex.CompositeTableType<
|
||||||
@@ -294,6 +333,11 @@ declare module "knex/types/tables" {
|
|||||||
TIdentityProjectMembershipRoleInsert,
|
TIdentityProjectMembershipRoleInsert,
|
||||||
TIdentityProjectMembershipRoleUpdate
|
TIdentityProjectMembershipRoleUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.IdentityProjectAdditionalPrivilege]: Knex.CompositeTableType<
|
||||||
|
TIdentityProjectAdditionalPrivilege,
|
||||||
|
TIdentityProjectAdditionalPrivilegeInsert,
|
||||||
|
TIdentityProjectAdditionalPrivilegeUpdate
|
||||||
|
>;
|
||||||
[TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>;
|
[TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>;
|
||||||
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
|
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
|
||||||
TSecretApprovalPolicies,
|
TSecretApprovalPolicies,
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.ProjectUserAdditionalPrivilege))) {
|
||||||
|
await knex.schema.createTable(TableName.ProjectUserAdditionalPrivilege, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("slug", 60).notNullable();
|
||||||
|
t.uuid("projectMembershipId").notNullable();
|
||||||
|
t.foreign("projectMembershipId").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
|
||||||
|
t.boolean("isTemporary").notNullable().defaultTo(false);
|
||||||
|
t.string("temporaryMode");
|
||||||
|
t.string("temporaryRange"); // could be cron or relative time like 1H or 1minute etc
|
||||||
|
t.datetime("temporaryAccessStartTime");
|
||||||
|
t.datetime("temporaryAccessEndTime");
|
||||||
|
t.jsonb("permissions").notNullable();
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.ProjectUserAdditionalPrivilege);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.ProjectUserAdditionalPrivilege);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.ProjectUserAdditionalPrivilege);
|
||||||
|
}
|
@@ -0,0 +1,32 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.IdentityProjectAdditionalPrivilege))) {
|
||||||
|
await knex.schema.createTable(TableName.IdentityProjectAdditionalPrivilege, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("slug", 60).notNullable();
|
||||||
|
t.uuid("projectMembershipId").notNullable();
|
||||||
|
t.foreign("projectMembershipId")
|
||||||
|
.references("id")
|
||||||
|
.inTable(TableName.IdentityProjectMembership)
|
||||||
|
.onDelete("CASCADE");
|
||||||
|
t.boolean("isTemporary").notNullable().defaultTo(false);
|
||||||
|
t.string("temporaryMode");
|
||||||
|
t.string("temporaryRange"); // could be cron or relative time like 1H or 1minute etc
|
||||||
|
t.datetime("temporaryAccessStartTime");
|
||||||
|
t.datetime("temporaryAccessEndTime");
|
||||||
|
t.jsonb("permissions").notNullable();
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.IdentityProjectAdditionalPrivilege);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.IdentityProjectAdditionalPrivilege);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.IdentityProjectAdditionalPrivilege);
|
||||||
|
}
|
@@ -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"]);
|
||||||
|
});
|
||||||
|
}
|
82
backend/src/db/migrations/20240412174842_group.ts
Normal file
82
backend/src/db/migrations/20240412174842_group.ts
Normal 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);
|
||||||
|
}
|
31
backend/src/db/schemas/group-project-membership-roles.ts
Normal file
31
backend/src/db/schemas/group-project-membership-roles.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const 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>
|
||||||
|
>;
|
22
backend/src/db/schemas/group-project-memberships.ts
Normal file
22
backend/src/db/schemas/group-project-memberships.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const 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>
|
||||||
|
>;
|
23
backend/src/db/schemas/groups.ts
Normal file
23
backend/src/db/schemas/groups.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const 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>>;
|
@@ -0,0 +1,31 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const IdentityProjectAdditionalPrivilegeSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
slug: z.string(),
|
||||||
|
projectMembershipId: z.string().uuid(),
|
||||||
|
isTemporary: z.boolean().default(false),
|
||||||
|
temporaryMode: z.string().nullable().optional(),
|
||||||
|
temporaryRange: z.string().nullable().optional(),
|
||||||
|
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||||
|
temporaryAccessEndTime: z.date().nullable().optional(),
|
||||||
|
permissions: z.unknown(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TIdentityProjectAdditionalPrivilege = z.infer<typeof IdentityProjectAdditionalPrivilegeSchema>;
|
||||||
|
export type TIdentityProjectAdditionalPrivilegeInsert = Omit<
|
||||||
|
z.input<typeof IdentityProjectAdditionalPrivilegeSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TIdentityProjectAdditionalPrivilegeUpdate = Partial<
|
||||||
|
Omit<z.input<typeof IdentityProjectAdditionalPrivilegeSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
@@ -7,9 +7,13 @@ export * from "./dynamic-secret-leases";
|
|||||||
export * from "./dynamic-secrets";
|
export * from "./dynamic-secrets";
|
||||||
export * from "./git-app-install-sessions";
|
export * from "./git-app-install-sessions";
|
||||||
export * from "./git-app-org";
|
export * from "./git-app-org";
|
||||||
|
export * from "./group-project-membership-roles";
|
||||||
|
export * from "./group-project-memberships";
|
||||||
|
export * from "./groups";
|
||||||
export * from "./identities";
|
export * from "./identities";
|
||||||
export * from "./identity-access-tokens";
|
export * from "./identity-access-tokens";
|
||||||
export * from "./identity-org-memberships";
|
export * from "./identity-org-memberships";
|
||||||
|
export * from "./identity-project-additional-privilege";
|
||||||
export * from "./identity-project-membership-role";
|
export * from "./identity-project-membership-role";
|
||||||
export * from "./identity-project-memberships";
|
export * from "./identity-project-memberships";
|
||||||
export * from "./identity-ua-client-secrets";
|
export * from "./identity-ua-client-secrets";
|
||||||
@@ -28,6 +32,7 @@ export * from "./project-environments";
|
|||||||
export * from "./project-keys";
|
export * from "./project-keys";
|
||||||
export * from "./project-memberships";
|
export * from "./project-memberships";
|
||||||
export * from "./project-roles";
|
export * from "./project-roles";
|
||||||
|
export * from "./project-user-additional-privilege";
|
||||||
export * from "./project-user-membership-roles";
|
export * from "./project-user-membership-roles";
|
||||||
export * from "./projects";
|
export * from "./projects";
|
||||||
export * from "./saml-configs";
|
export * from "./saml-configs";
|
||||||
@@ -59,5 +64,6 @@ export * from "./trusted-ips";
|
|||||||
export * from "./user-actions";
|
export * from "./user-actions";
|
||||||
export * from "./user-aliases";
|
export * from "./user-aliases";
|
||||||
export * from "./user-encryption-keys";
|
export * from "./user-encryption-keys";
|
||||||
|
export * from "./user-group-membership";
|
||||||
export * from "./users";
|
export * from "./users";
|
||||||
export * from "./webhooks";
|
export * from "./webhooks";
|
||||||
|
@@ -2,6 +2,10 @@ import { z } from "zod";
|
|||||||
|
|
||||||
export enum TableName {
|
export enum TableName {
|
||||||
Users = "users",
|
Users = "users",
|
||||||
|
Groups = "groups",
|
||||||
|
GroupProjectMembership = "group_project_memberships",
|
||||||
|
GroupProjectMembershipRole = "group_project_membership_roles",
|
||||||
|
UserGroupMembership = "user_group_membership",
|
||||||
UserAliases = "user_aliases",
|
UserAliases = "user_aliases",
|
||||||
UserEncryptionKey = "user_encryption_keys",
|
UserEncryptionKey = "user_encryption_keys",
|
||||||
AuthTokens = "auth_tokens",
|
AuthTokens = "auth_tokens",
|
||||||
@@ -20,6 +24,7 @@ export enum TableName {
|
|||||||
Environment = "project_environments",
|
Environment = "project_environments",
|
||||||
ProjectMembership = "project_memberships",
|
ProjectMembership = "project_memberships",
|
||||||
ProjectRoles = "project_roles",
|
ProjectRoles = "project_roles",
|
||||||
|
ProjectUserAdditionalPrivilege = "project_user_additional_privilege",
|
||||||
ProjectUserMembershipRole = "project_user_membership_roles",
|
ProjectUserMembershipRole = "project_user_membership_roles",
|
||||||
ProjectKeys = "project_keys",
|
ProjectKeys = "project_keys",
|
||||||
Secret = "secrets",
|
Secret = "secrets",
|
||||||
@@ -43,6 +48,7 @@ export enum TableName {
|
|||||||
IdentityOrgMembership = "identity_org_memberships",
|
IdentityOrgMembership = "identity_org_memberships",
|
||||||
IdentityProjectMembership = "identity_project_memberships",
|
IdentityProjectMembership = "identity_project_memberships",
|
||||||
IdentityProjectMembershipRole = "identity_project_membership_role",
|
IdentityProjectMembershipRole = "identity_project_membership_role",
|
||||||
|
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
|
||||||
ScimToken = "scim_tokens",
|
ScimToken = "scim_tokens",
|
||||||
SecretApprovalPolicy = "secret_approval_policies",
|
SecretApprovalPolicy = "secret_approval_policies",
|
||||||
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
||||||
|
31
backend/src/db/schemas/project-user-additional-privilege.ts
Normal file
31
backend/src/db/schemas/project-user-additional-privilege.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const ProjectUserAdditionalPrivilegeSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
slug: z.string(),
|
||||||
|
projectMembershipId: z.string().uuid(),
|
||||||
|
isTemporary: z.boolean().default(false),
|
||||||
|
temporaryMode: z.string().nullable().optional(),
|
||||||
|
temporaryRange: z.string().nullable().optional(),
|
||||||
|
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||||
|
temporaryAccessEndTime: z.date().nullable().optional(),
|
||||||
|
permissions: z.unknown(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TProjectUserAdditionalPrivilege = z.infer<typeof ProjectUserAdditionalPrivilegeSchema>;
|
||||||
|
export type TProjectUserAdditionalPrivilegeInsert = Omit<
|
||||||
|
z.input<typeof ProjectUserAdditionalPrivilegeSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TProjectUserAdditionalPrivilegeUpdate = Partial<
|
||||||
|
Omit<z.input<typeof ProjectUserAdditionalPrivilegeSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
20
backend/src/db/schemas/user-group-membership.ts
Normal file
20
backend/src/db/schemas/user-group-membership.ts
Normal 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>>;
|
@@ -5,15 +5,18 @@ import { DynamicSecretLeasesSchema } from "@app/db/schemas";
|
|||||||
import { DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs";
|
import { DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs";
|
||||||
import { daysToMillisecond } from "@app/lib/dates";
|
import { daysToMillisecond } from "@app/lib/dates";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
import { SanitizedDynamicSecretSchema } from "../sanitizedSchemas";
|
|
||||||
|
|
||||||
export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvider) => {
|
export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
dynamicSecretName: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.dynamicSecretName).toLowerCase(),
|
dynamicSecretName: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.dynamicSecretName).toLowerCase(),
|
||||||
@@ -56,8 +59,11 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:leaseId",
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
url: "/:leaseId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.DELETE.leaseId)
|
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.DELETE.leaseId)
|
||||||
@@ -95,8 +101,11 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:leaseId/renew",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/:leaseId/renew",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.leaseId)
|
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.leaseId)
|
||||||
@@ -147,6 +156,9 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:leaseId",
|
url: "/:leaseId",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.GET_BY_LEASEID.leaseId)
|
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.GET_BY_LEASEID.leaseId)
|
@@ -3,19 +3,22 @@ import ms from "ms";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
|
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
|
||||||
|
import { DynamicSecretProviderSchema } from "@app/ee/services/dynamic-secret/providers/models";
|
||||||
import { DYNAMIC_SECRETS } from "@app/lib/api-docs";
|
import { DYNAMIC_SECRETS } from "@app/lib/api-docs";
|
||||||
import { daysToMillisecond } from "@app/lib/dates";
|
import { daysToMillisecond } from "@app/lib/dates";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { DynamicSecretProviderSchema } from "@app/services/dynamic-secret/providers/models";
|
|
||||||
|
|
||||||
import { SanitizedDynamicSecretSchema } from "../sanitizedSchemas";
|
|
||||||
|
|
||||||
export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => {
|
export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.CREATE.projectSlug),
|
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.CREATE.projectSlug),
|
||||||
@@ -75,8 +78,11 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:name",
|
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
url: "/:name",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.UPDATE.name)
|
name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.UPDATE.name)
|
||||||
@@ -139,8 +145,11 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:name",
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
url: "/:name",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.DELETE.name)
|
name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.DELETE.name)
|
||||||
@@ -174,6 +183,9 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:name",
|
url: "/:name",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
name: z.string().min(1).describe(DYNAMIC_SECRETS.GET_BY_NAME.name)
|
name: z.string().min(1).describe(DYNAMIC_SECRETS.GET_BY_NAME.name)
|
||||||
@@ -208,6 +220,9 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST.projectSlug),
|
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST.projectSlug),
|
||||||
@@ -236,6 +251,9 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:name/leases",
|
url: "/:name/leases",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
name: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.name)
|
name: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.name)
|
220
backend/src/ee/routes/v1/group-router.ts
Normal file
220
backend/src/ee/routes/v1/group-router.ts
Normal 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -0,0 +1,329 @@
|
|||||||
|
import { MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import ms from "ms";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { IdentityProjectAdditionalPrivilegeSchema } from "@app/db/schemas";
|
||||||
|
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
|
||||||
|
import { ProjectPermissionSet } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/permanent",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Create a permanent or a non expiry specific privilege for identity.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
body: z.object({
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId),
|
||||||
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug),
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
|
permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilege.create({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
...req.body,
|
||||||
|
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||||
|
isTemporary: false,
|
||||||
|
permissions: JSON.stringify(packRules(req.body.permissions))
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/temporary",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Create a temporary or a expiring specific privilege for identity.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
body: z.object({
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId),
|
||||||
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug),
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
|
permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
||||||
|
temporaryMode: z
|
||||||
|
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
|
||||||
|
temporaryRange: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryRange),
|
||||||
|
temporaryAccessStartTime: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryAccessStartTime)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilege.create({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
...req.body,
|
||||||
|
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||||
|
isTemporary: true,
|
||||||
|
permissions: JSON.stringify(packRules(req.body.permissions))
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Update a specific privilege of an identity.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
body: z.object({
|
||||||
|
// disallow empty string
|
||||||
|
privilegeSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.identityId),
|
||||||
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.projectSlug),
|
||||||
|
privilegeDetails: z
|
||||||
|
.object({
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.newSlug),
|
||||||
|
permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
||||||
|
isTemporary: z.boolean().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
|
||||||
|
temporaryMode: z
|
||||||
|
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
|
||||||
|
temporaryRange: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.temporaryRange),
|
||||||
|
temporaryAccessStartTime: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.temporaryAccessStartTime)
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const updatedInfo = req.body.privilegeDetails;
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilege.updateBySlug({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
slug: req.body.privilegeSlug,
|
||||||
|
identityId: req.body.identityId,
|
||||||
|
projectSlug: req.body.projectSlug,
|
||||||
|
data: {
|
||||||
|
...updatedInfo,
|
||||||
|
permissions: updatedInfo?.permissions ? JSON.stringify(packRules(updatedInfo.permissions)) : undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Delete a specific privilege of an identity.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
body: z.object({
|
||||||
|
privilegeSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.DELETE.slug),
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.DELETE.identityId),
|
||||||
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.DELETE.projectSlug)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilege.deleteBySlug({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
slug: req.body.privilegeSlug,
|
||||||
|
identityId: req.body.identityId,
|
||||||
|
projectSlug: req.body.projectSlug
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:privilegeSlug",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Retrieve details of a specific privilege by privilege slug.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
privilegeSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.GET_BY_SLUG.slug)
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.GET_BY_SLUG.identityId),
|
||||||
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.GET_BY_SLUG.projectSlug)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilege.getPrivilegeDetailsBySlug({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
slug: req.params.privilegeSlug,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "List of a specific privilege of an identity in a project.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
querystring: z.object({
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.identityId),
|
||||||
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.projectSlug),
|
||||||
|
unpacked: z
|
||||||
|
.enum(["false", "true"])
|
||||||
|
.transform((el) => el === "true")
|
||||||
|
.default("true")
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.unpacked)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privileges: IdentityProjectAdditionalPrivilegeSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privileges = await server.services.identityProjectAdditionalPrivilege.listIdentityProjectPrivileges({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
if (req.query.unpacked) {
|
||||||
|
return {
|
||||||
|
privileges: privileges.map(({ permissions, ...el }) => ({
|
||||||
|
...el,
|
||||||
|
permissions: unpackRules(permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { privileges };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -1,3 +1,7 @@
|
|||||||
|
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 { registerLdapRouter } from "./ldap-router";
|
||||||
import { registerLicenseRouter } from "./license-router";
|
import { registerLicenseRouter } from "./license-router";
|
||||||
import { registerOrgRoleRouter } from "./org-role-router";
|
import { registerOrgRoleRouter } from "./org-role-router";
|
||||||
@@ -13,6 +17,7 @@ import { registerSecretScanningRouter } from "./secret-scanning-router";
|
|||||||
import { registerSecretVersionRouter } from "./secret-version-router";
|
import { registerSecretVersionRouter } from "./secret-version-router";
|
||||||
import { registerSnapshotRouter } from "./snapshot-router";
|
import { registerSnapshotRouter } from "./snapshot-router";
|
||||||
import { registerTrustedIpRouter } from "./trusted-ip-router";
|
import { registerTrustedIpRouter } from "./trusted-ip-router";
|
||||||
|
import { registerUserAdditionalPrivilegeRouter } from "./user-additional-privilege-router";
|
||||||
|
|
||||||
export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||||
// org role starts with organization
|
// org role starts with organization
|
||||||
@@ -34,10 +39,27 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerSecretRotationProviderRouter, {
|
await server.register(registerSecretRotationProviderRouter, {
|
||||||
prefix: "/secret-rotation-providers"
|
prefix: "/secret-rotation-providers"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await server.register(
|
||||||
|
async (dynamicSecretRouter) => {
|
||||||
|
await dynamicSecretRouter.register(registerDynamicSecretRouter);
|
||||||
|
await dynamicSecretRouter.register(registerDynamicSecretLeaseRouter, { prefix: "/leases" });
|
||||||
|
},
|
||||||
|
{ prefix: "/dynamic-secrets" }
|
||||||
|
);
|
||||||
|
|
||||||
await server.register(registerSamlRouter, { prefix: "/sso" });
|
await server.register(registerSamlRouter, { prefix: "/sso" });
|
||||||
await server.register(registerScimRouter, { prefix: "/scim" });
|
await server.register(registerScimRouter, { prefix: "/scim" });
|
||||||
await server.register(registerLdapRouter, { prefix: "/ldap" });
|
await server.register(registerLdapRouter, { prefix: "/ldap" });
|
||||||
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
|
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
|
||||||
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
|
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
|
||||||
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
|
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
|
||||||
|
await server.register(registerGroupRouter, { prefix: "/groups" });
|
||||||
|
await server.register(
|
||||||
|
async (privilegeRouter) => {
|
||||||
|
await privilegeRouter.register(registerUserAdditionalPrivilegeRouter, { prefix: "/users" });
|
||||||
|
await privilegeRouter.register(registerIdentityProjectAdditionalPrivilegeRouter, { prefix: "/identity" });
|
||||||
|
},
|
||||||
|
{ prefix: "/additional-privilege" }
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@@ -17,6 +17,7 @@ import { z } from "zod";
|
|||||||
import { LdapConfigsSchema } from "@app/db/schemas";
|
import { LdapConfigsSchema } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -97,8 +98,11 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/config",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
@@ -130,8 +134,11 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/config",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -164,6 +171,9 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
url: "/config",
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
body: z
|
body: z
|
||||||
|
@@ -3,13 +3,17 @@
|
|||||||
// TODO(akhilmhdh): Fix this when licence service gets it type
|
// TODO(akhilmhdh): Fix this when licence service gets it type
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/plans/table",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/plans/table",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({ billingCycle: z.enum(["monthly", "yearly"]) }),
|
querystring: z.object({ billingCycle: z.enum(["monthly", "yearly"]) }),
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
@@ -32,8 +36,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/plan",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/plan",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -54,8 +61,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/plans",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/plans",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
querystring: z.object({ workspaceId: z.string().trim().optional() }),
|
querystring: z.object({ workspaceId: z.string().trim().optional() }),
|
||||||
@@ -77,8 +87,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/session/trial",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/:organizationId/session/trial",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
body: z.object({ success_url: z.string().trim() }),
|
body: z.object({ success_url: z.string().trim() }),
|
||||||
@@ -103,6 +116,9 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/customer-portal-session",
|
url: "/:organizationId/customer-portal-session",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -123,8 +139,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/plan/billing",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/plan/billing",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -145,8 +164,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/plan/table",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/plan/table",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -167,8 +189,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/billing-details",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -189,8 +214,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details",
|
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
url: "/:organizationId/billing-details",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -217,8 +245,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details/payment-methods",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/billing-details/payment-methods",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -239,8 +270,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details/payment-methods",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/:organizationId/billing-details/payment-methods",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -267,8 +301,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details/payment-methods/:pmtMethodId",
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
url: "/:organizationId/billing-details/payment-methods/:pmtMethodId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim(),
|
||||||
@@ -293,8 +330,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details/tax-ids",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/billing-details/tax-ids",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -317,8 +357,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details/tax-ids",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/:organizationId/billing-details/tax-ids",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -347,8 +390,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details/tax-ids/:taxId",
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
url: "/:organizationId/billing-details/tax-ids/:taxId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim(),
|
||||||
@@ -373,8 +419,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/invoices",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/invoices",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -397,8 +446,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/licenses",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/licenses",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
|
@@ -2,6 +2,7 @@ import slugify from "@sindresorhus/slugify";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
import { OrgMembershipRole, OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -9,6 +10,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/:organizationId/roles",
|
url: "/:organizationId/roles",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -51,6 +55,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/:organizationId/roles/:roleId",
|
url: "/:organizationId/roles/:roleId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim(),
|
||||||
@@ -95,6 +102,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/:organizationId/roles/:roleId",
|
url: "/:organizationId/roles/:roleId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim(),
|
||||||
@@ -122,6 +132,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:organizationId/roles",
|
url: "/:organizationId/roles",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -151,6 +164,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:organizationId/permissions",
|
url: "/:organizationId/permissions",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
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 { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -8,6 +9,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/:projectId/roles",
|
url: "/:projectId/roles",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim()
|
projectId: z.string().trim()
|
||||||
@@ -41,6 +45,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/:projectId/roles/:roleId",
|
url: "/:projectId/roles/:roleId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim(),
|
projectId: z.string().trim(),
|
||||||
@@ -76,6 +83,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/:projectId/roles/:roleId",
|
url: "/:projectId/roles/:roleId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim(),
|
projectId: z.string().trim(),
|
||||||
@@ -104,6 +114,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:projectId/roles",
|
url: "/:projectId/roles",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim()
|
projectId: z.string().trim()
|
||||||
@@ -134,6 +147,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:projectId/permissions",
|
url: "/:projectId/permissions",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim()
|
projectId: z.string().trim()
|
||||||
@@ -155,6 +171,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
req.permission.authMethod,
|
req.permission.authMethod,
|
||||||
req.permission.orgId
|
req.permission.orgId
|
||||||
);
|
);
|
||||||
|
|
||||||
return { data: { permissions, membership } };
|
return { data: { permissions, membership } };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -3,7 +3,8 @@ import { z } from "zod";
|
|||||||
import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas";
|
import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas";
|
||||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs";
|
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 { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -11,6 +12,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:workspaceId/secret-snapshots",
|
url: "/:workspaceId/secret-snapshots",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
description: "Return project secret snapshots ids",
|
description: "Return project secret snapshots ids",
|
||||||
security: [
|
security: [
|
||||||
@@ -51,6 +55,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:workspaceId/secret-snapshots/count",
|
url: "/:workspaceId/secret-snapshots/count",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@@ -83,6 +90,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:workspaceId/audit-logs",
|
url: "/:workspaceId/audit-logs",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
description: "Return audit logs",
|
description: "Return audit logs",
|
||||||
security: [
|
security: [
|
||||||
@@ -135,6 +145,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId: req.params.workspaceId,
|
projectId: req.params.workspaceId,
|
||||||
...req.query,
|
...req.query,
|
||||||
|
startDate: req.query.endDate || getLastMidnightDateISO(),
|
||||||
auditLogActor: req.query.actor,
|
auditLogActor: req.query.actor,
|
||||||
actor: req.permission.type
|
actor: req.permission.type
|
||||||
});
|
});
|
||||||
@@ -145,6 +156,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:workspaceId/audit-logs/filters/actors",
|
url: "/:workspaceId/audit-logs/filters/actors",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
|
@@ -17,6 +17,7 @@ import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -203,8 +204,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/config",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
@@ -240,8 +244,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/config",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -270,8 +277,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
url: "/config",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
body: z
|
body: z
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ScimTokensSchema } from "@app/db/schemas";
|
import { ScimTokensSchema } from "@app/db/schemas";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -20,6 +21,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/scim-tokens",
|
url: "/scim-tokens",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -51,6 +55,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/scim-tokens",
|
url: "/scim-tokens",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
@@ -78,6 +85,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/scim-tokens/:scimTokenId",
|
url: "/scim-tokens/:scimTokenId",
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -196,7 +206,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
schemas: z.array(z.string()),
|
schemas: z.array(z.string()),
|
||||||
userName: z.string().trim().email(),
|
userName: z.string().trim(),
|
||||||
name: z.object({
|
name: z.object({
|
||||||
familyName: z.string().trim(),
|
familyName: z.string().trim(),
|
||||||
givenName: z.string().trim()
|
givenName: z.string().trim()
|
||||||
@@ -217,7 +227,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
schemas: z.array(z.string()),
|
schemas: z.array(z.string()),
|
||||||
id: z.string().trim(),
|
id: z.string().trim(),
|
||||||
userName: z.string().trim().email(),
|
userName: z.string().trim(),
|
||||||
name: z.object({
|
name: z.object({
|
||||||
familyName: z.string().trim(),
|
familyName: z.string().trim(),
|
||||||
givenName: z.string().trim()
|
givenName: z.string().trim()
|
||||||
@@ -252,38 +262,257 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/Users/:userId",
|
url: "/Users/:userId",
|
||||||
method: "PATCH",
|
method: "DELETE",
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
userId: z.string().trim()
|
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: {
|
response: {
|
||||||
200: z.object({})
|
200: z.object({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const user = await req.server.services.scim.updateScimUser({
|
const user = await req.server.services.scim.deleteScimUser({
|
||||||
userId: req.params.userId,
|
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,
|
orgId: req.permission.orgId,
|
||||||
operations: req.body.Operations
|
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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -9,6 +10,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
@@ -47,6 +51,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:sapId",
|
url: "/:sapId",
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
sapId: z.string()
|
sapId: z.string()
|
||||||
@@ -85,6 +92,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:sapId",
|
url: "/:sapId",
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
sapId: z.string()
|
sapId: z.string()
|
||||||
@@ -111,6 +121,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@@ -137,6 +150,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/board",
|
url: "/board",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
|
@@ -10,13 +10,17 @@ import {
|
|||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
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 { 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 { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerSecretApprovalRequestRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretApprovalRequestRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
@@ -62,8 +66,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/count",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/count",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@@ -93,6 +100,9 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:id/merge",
|
url: "/:id/merge",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
id: z.string()
|
id: z.string()
|
||||||
@@ -117,8 +127,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:id/review",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/:id/review",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
id: z.string()
|
id: z.string()
|
||||||
@@ -147,8 +160,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:id/status",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/:id/status",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
id: z.string()
|
id: z.string()
|
||||||
@@ -203,8 +219,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
.array()
|
.array()
|
||||||
.optional();
|
.optional();
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:id",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
id: z.string()
|
id: z.string()
|
||||||
|
@@ -1,12 +1,16 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerSecretRotationProviderRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretRotationProviderRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:workspaceId",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:workspaceId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
|
@@ -2,13 +2,17 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { SecretRotationOutputsSchema, SecretRotationsSchema, SecretsSchema } from "@app/db/schemas";
|
import { SecretRotationOutputsSchema, SecretRotationsSchema, SecretsSchema } from "@app/db/schemas";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerSecretRotationRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretRotationRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
@@ -52,6 +56,9 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/restart",
|
url: "/restart",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
id: z.string().trim()
|
id: z.string().trim()
|
||||||
@@ -86,6 +93,9 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@@ -136,8 +146,11 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:id",
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
id: z.string().trim()
|
id: z.string().trim()
|
||||||
|
@@ -2,13 +2,17 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
||||||
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
|
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerSecretScanningRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretScanningRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/create-installation-session/organization",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/create-installation-session/organization",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({ organizationId: z.string().trim() }),
|
body: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -31,8 +35,11 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/link-installation",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/link-installation",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
installationId: z.string(),
|
installationId: z.string(),
|
||||||
@@ -56,8 +63,11 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/installation-status/organization/:organizationId",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/installation-status/organization/:organizationId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -80,6 +90,9 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/organization/:organizationId/risks",
|
url: "/organization/:organizationId/risks",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -100,8 +113,11 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/organization/:organizationId/risks/:riskId/status",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/organization/:organizationId/risks/:riskId/status",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim(), riskId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim(), riskId: z.string().trim() }),
|
||||||
body: z.object({ status: z.nativeEnum(SecretScanningRiskStatus) }),
|
body: z.object({ status: z.nativeEnum(SecretScanningRiskStatus) }),
|
||||||
|
@@ -1,13 +1,17 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretVersionsSchema } from "@app/db/schemas";
|
import { SecretVersionsSchema } from "@app/db/schemas";
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerSecretVersionRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretVersionRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:secretId/secret-versions",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:secretId/secret-versions",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretId: z.string()
|
secretId: z.string()
|
||||||
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { SecretSnapshotsSchema, SecretTagsSchema, SecretVersionsSchema } from "@app/db/schemas";
|
import { SecretSnapshotsSchema, SecretTagsSchema, SecretVersionsSchema } from "@app/db/schemas";
|
||||||
import { 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 { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -9,6 +10,9 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:secretSnapshotId",
|
url: "/:secretSnapshotId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretSnapshotId: z.string().trim()
|
secretSnapshotId: z.string().trim()
|
||||||
@@ -58,6 +62,9 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/:secretSnapshotId/rollback",
|
url: "/:secretSnapshotId/rollback",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
description: "Roll back project secrets to those captured in a secret snapshot version.",
|
description: "Roll back project secrets to those captured in a secret snapshot version.",
|
||||||
security: [
|
security: [
|
||||||
|
@@ -2,13 +2,17 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { TrustedIpsSchema } from "@app/db/schemas";
|
import { TrustedIpsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
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 { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:workspaceId/trusted-ips",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:workspaceId/trusted-ips",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@@ -33,8 +37,11 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:workspaceId/trusted-ips",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/:workspaceId/trusted-ips",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@@ -78,8 +85,11 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
@@ -124,8 +134,11 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
|
256
backend/src/ee/routes/v1/user-additional-privilege-router.ts
Normal file
256
backend/src/ee/routes/v1/user-additional-privilege-router.ts
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import ms from "ms";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas";
|
||||||
|
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
|
||||||
|
import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/permanent",
|
||||||
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId),
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((v) => v.toLowerCase() === v, "Slug must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
|
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.projectUserAdditionalPrivilege.create({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
...req.body,
|
||||||
|
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||||
|
isTemporary: false,
|
||||||
|
permissions: JSON.stringify(req.body.permissions)
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/temporary",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId),
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((v) => v.toLowerCase() === v, "Slug must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
|
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
||||||
|
temporaryMode: z
|
||||||
|
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
|
||||||
|
temporaryRange: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryRange),
|
||||||
|
temporaryAccessStartTime: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryAccessStartTime)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.projectUserAdditionalPrivilege.create({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
...req.body,
|
||||||
|
slug: req.body.slug ? slugify(req.body.slug) : `privilege-${slugify(alphaNumericNanoId(12))}`,
|
||||||
|
isTemporary: true,
|
||||||
|
permissions: JSON.stringify(req.body.permissions)
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:privilegeId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
privilegeId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.privilegeId)
|
||||||
|
}),
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((v) => v.toLowerCase() === v, "Slug must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
||||||
|
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
||||||
|
isTemporary: z.boolean().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
|
||||||
|
temporaryMode: z
|
||||||
|
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
|
||||||
|
temporaryRange: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryRange),
|
||||||
|
temporaryAccessStartTime: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryAccessStartTime)
|
||||||
|
})
|
||||||
|
.partial(),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.projectUserAdditionalPrivilege.updateById({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
...req.body,
|
||||||
|
permissions: req.body.permissions ? JSON.stringify(req.body.permissions) : undefined,
|
||||||
|
privilegeId: req.params.privilegeId
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:privilegeId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
privilegeId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.DELETE.privilegeId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.projectUserAdditionalPrivilege.deleteById({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
privilegeId: req.params.privilegeId
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
querystring: z.object({
|
||||||
|
projectMembershipId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.LIST.projectMembershipId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privileges: ProjectUserAdditionalPrivilegeSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privileges = await server.services.projectUserAdditionalPrivilege.listPrivileges({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
projectMembershipId: req.query.projectMembershipId
|
||||||
|
});
|
||||||
|
return { privileges };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:privilegeId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
privilegeId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.GET_BY_PRIVILEGEID.privilegeId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.projectUserAdditionalPrivilege.getPrivilegeDetailsById({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
privilegeId: req.params.privilegeId
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -8,11 +8,12 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
|
|
||||||
import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
|
import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
|
||||||
import { DynamicSecretProviders, TDynamicProviderFns } from "../dynamic-secret/providers/models";
|
import { DynamicSecretProviders, TDynamicProviderFns } from "../dynamic-secret/providers/models";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
|
||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
|
||||||
import { TDynamicSecretLeaseDALFactory } from "./dynamic-secret-lease-dal";
|
import { TDynamicSecretLeaseDALFactory } from "./dynamic-secret-lease-dal";
|
||||||
import { TDynamicSecretLeaseQueueServiceFactory } from "./dynamic-secret-lease-queue";
|
import { TDynamicSecretLeaseQueueServiceFactory } from "./dynamic-secret-lease-queue";
|
||||||
import {
|
import {
|
||||||
@@ -248,6 +249,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
|
|
||||||
if ((revokeResponse as { error?: Error })?.error) {
|
if ((revokeResponse as { error?: Error })?.error) {
|
||||||
const { error } = revokeResponse as { error?: Error };
|
const { error } = revokeResponse as { error?: Error };
|
||||||
|
logger.error(error?.message, "Failed to revoke lease");
|
||||||
const deletedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
|
const deletedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
|
||||||
status: DynamicSecretLeaseStatus.FailedDeletion,
|
status: DynamicSecretLeaseStatus.FailedDeletion,
|
||||||
statusDetails: error?.message?.slice(0, 255)
|
statusDetails: error?.message?.slice(0, 255)
|
@@ -6,11 +6,11 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
|
|
||||||
import { TDynamicSecretLeaseDALFactory } from "../dynamic-secret-lease/dynamic-secret-lease-dal";
|
import { TDynamicSecretLeaseDALFactory } from "../dynamic-secret-lease/dynamic-secret-lease-dal";
|
||||||
import { TDynamicSecretLeaseQueueServiceFactory } from "../dynamic-secret-lease/dynamic-secret-lease-queue";
|
import { TDynamicSecretLeaseQueueServiceFactory } from "../dynamic-secret-lease/dynamic-secret-lease-queue";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
|
||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
|
||||||
import { TDynamicSecretDALFactory } from "./dynamic-secret-dal";
|
import { TDynamicSecretDALFactory } from "./dynamic-secret-dal";
|
||||||
import {
|
import {
|
||||||
DynamicSecretStatus,
|
DynamicSecretStatus,
|
@@ -1,7 +1,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export enum SqlProviders {
|
export enum SqlProviders {
|
||||||
Postgres = "postgres"
|
Postgres = "postgres",
|
||||||
|
MySQL = "mysql2"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DynamicSecretSqlDBSchema = z.object({
|
export const DynamicSecretSqlDBSchema = z.object({
|
||||||
@@ -13,7 +14,7 @@ export const DynamicSecretSqlDBSchema = z.object({
|
|||||||
password: z.string(),
|
password: z.string(),
|
||||||
creationStatement: z.string(),
|
creationStatement: z.string(),
|
||||||
revocationStatement: z.string(),
|
revocationStatement: z.string(),
|
||||||
renewStatement: z.string(),
|
renewStatement: z.string().optional(),
|
||||||
ca: z.string().optional()
|
ca: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
@@ -23,7 +23,17 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);
|
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);
|
||||||
|
|
||||||
const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs);
|
const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs);
|
||||||
if (providerInputs.host === "localhost" || providerInputs.host === "127.0.0.1" || dbHost === providerInputs.host)
|
if (
|
||||||
|
// localhost
|
||||||
|
providerInputs.host === "localhost" ||
|
||||||
|
providerInputs.host === "127.0.0.1" ||
|
||||||
|
// database infisical uses
|
||||||
|
dbHost === providerInputs.host ||
|
||||||
|
// internal ips
|
||||||
|
providerInputs.host === "host.docker.internal" ||
|
||||||
|
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
|
||||||
|
providerInputs.host.match(/^192\.168\.\d+\.\d+/)
|
||||||
|
)
|
||||||
throw new BadRequestError({ message: "Invalid db host" });
|
throw new BadRequestError({ message: "Invalid db host" });
|
||||||
return providerInputs;
|
return providerInputs;
|
||||||
};
|
};
|
||||||
@@ -38,10 +48,10 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
host: providerInputs.host,
|
host: providerInputs.host,
|
||||||
user: providerInputs.username,
|
user: providerInputs.username,
|
||||||
password: providerInputs.password,
|
password: providerInputs.password,
|
||||||
connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT,
|
|
||||||
ssl,
|
ssl,
|
||||||
pool: { min: 0, max: 1 }
|
pool: { min: 0, max: 1 }
|
||||||
}
|
},
|
||||||
|
acquireConnectionTimeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
});
|
});
|
||||||
return db;
|
return db;
|
||||||
};
|
};
|
||||||
@@ -63,15 +73,25 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
|
|
||||||
const username = alphaNumericNanoId(32);
|
const username = alphaNumericNanoId(32);
|
||||||
const password = generatePassword();
|
const password = generatePassword();
|
||||||
|
const { database } = providerInputs;
|
||||||
const expiration = new Date(expireAt).toISOString();
|
const expiration = new Date(expireAt).toISOString();
|
||||||
|
|
||||||
const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({
|
const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({
|
||||||
username,
|
username,
|
||||||
password,
|
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();
|
await db.destroy();
|
||||||
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
|
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
|
||||||
};
|
};
|
||||||
@@ -81,9 +101,18 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
const db = await getClient(providerInputs);
|
const db = await getClient(providerInputs);
|
||||||
|
|
||||||
const username = entityId;
|
const username = entityId;
|
||||||
|
const { database } = providerInputs;
|
||||||
|
|
||||||
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username });
|
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username, database });
|
||||||
await db.raw(revokeStatement);
|
await db.transaction(async (tx) =>
|
||||||
|
Promise.all(
|
||||||
|
revokeStatement
|
||||||
|
.toString()
|
||||||
|
.split(";")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((query) => tx.raw(query))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
return { entityId: username };
|
return { entityId: username };
|
||||||
@@ -95,9 +124,19 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
|
|
||||||
const username = entityId;
|
const username = entityId;
|
||||||
const expiration = new Date(expireAt).toISOString();
|
const expiration = new Date(expireAt).toISOString();
|
||||||
|
const { database } = providerInputs;
|
||||||
|
|
||||||
const renewStatement = handlebars.compile(providerInputs.renewStatement)({ username, expiration });
|
const renewStatement = handlebars.compile(providerInputs.renewStatement)({ username, expiration, database });
|
||||||
await db.raw(renewStatement);
|
if (renewStatement)
|
||||||
|
await db.transaction(async (tx) =>
|
||||||
|
Promise.all(
|
||||||
|
renewStatement
|
||||||
|
.toString()
|
||||||
|
.split(";")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((query) => tx.raw(query))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
return { entityId: username };
|
return { entityId: username };
|
157
backend/src/ee/services/group/group-dal.ts
Normal file
157
backend/src/ee/services/group/group-dal.ts
Normal 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
|
||||||
|
};
|
||||||
|
};
|
474
backend/src/ee/services/group/group-service.ts
Normal file
474
backend/src/ee/services/group/group-service.ts
Normal 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
|
||||||
|
};
|
||||||
|
};
|
37
backend/src/ee/services/group/group-types.ts
Normal file
37
backend/src/ee/services/group/group-types.ts
Normal 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;
|
125
backend/src/ee/services/group/user-group-membership-dal.ts
Normal file
125
backend/src/ee/services/group/user-group-membership-dal.ts
Normal 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
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,12 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TIdentityProjectAdditionalPrivilegeDALFactory = ReturnType<
|
||||||
|
typeof identityProjectAdditionalPrivilegeDALFactory
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const identityProjectAdditionalPrivilegeDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.IdentityProjectAdditionalPrivilege);
|
||||||
|
return orm;
|
||||||
|
};
|
@@ -0,0 +1,297 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import ms from "ms";
|
||||||
|
|
||||||
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
|
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
||||||
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
|
import { TIdentityProjectAdditionalPrivilegeDALFactory } from "./identity-project-additional-privilege-dal";
|
||||||
|
import {
|
||||||
|
IdentityProjectAdditionalPrivilegeTemporaryMode,
|
||||||
|
TCreateIdentityPrivilegeDTO,
|
||||||
|
TDeleteIdentityPrivilegeDTO,
|
||||||
|
TGetIdentityPrivilegeDetailsDTO,
|
||||||
|
TListIdentityPrivilegesDTO,
|
||||||
|
TUpdateIdentityPrivilegeDTO
|
||||||
|
} from "./identity-project-additional-privilege-types";
|
||||||
|
|
||||||
|
type TIdentityProjectAdditionalPrivilegeServiceFactoryDep = {
|
||||||
|
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeDALFactory;
|
||||||
|
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
|
||||||
|
typeof identityProjectAdditionalPrivilegeServiceFactory
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||||
|
identityProjectAdditionalPrivilegeDAL,
|
||||||
|
identityProjectDAL,
|
||||||
|
permissionService,
|
||||||
|
projectDAL
|
||||||
|
}: TIdentityProjectAdditionalPrivilegeServiceFactoryDep) => {
|
||||||
|
const create = async ({
|
||||||
|
slug,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityId,
|
||||||
|
projectSlug,
|
||||||
|
permissions: customPermission,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
...dto
|
||||||
|
}: TCreateIdentityPrivilegeDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
const projectId = project.id;
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
|
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug,
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||||
|
|
||||||
|
if (!dto.isTemporary) {
|
||||||
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||||
|
projectMembershipId: identityProjectMembership.id,
|
||||||
|
slug,
|
||||||
|
permissions: customPermission
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
||||||
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||||
|
projectMembershipId: identityProjectMembership.id,
|
||||||
|
slug,
|
||||||
|
permissions: customPermission,
|
||||||
|
isTemporary: true,
|
||||||
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
|
||||||
|
temporaryRange: dto.temporaryRange,
|
||||||
|
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||||
|
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateBySlug = async ({
|
||||||
|
projectSlug,
|
||||||
|
slug,
|
||||||
|
identityId,
|
||||||
|
data,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TUpdateIdentityPrivilegeDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
const projectId = project.id;
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityProjectMembership.identityId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug,
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
|
||||||
|
if (data?.slug) {
|
||||||
|
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug: data.slug,
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
if (existingSlug && existingSlug.id !== identityPrivilege.id)
|
||||||
|
throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
||||||
|
if (isTemporary) {
|
||||||
|
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
|
||||||
|
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
|
||||||
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||||
|
...data,
|
||||||
|
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||||
|
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||||
|
...data,
|
||||||
|
isTemporary: false,
|
||||||
|
temporaryAccessStartTime: null,
|
||||||
|
temporaryAccessEndTime: null,
|
||||||
|
temporaryRange: null,
|
||||||
|
temporaryMode: null
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteBySlug = async ({
|
||||||
|
actorId,
|
||||||
|
slug,
|
||||||
|
identityId,
|
||||||
|
projectSlug,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TDeleteIdentityPrivilegeDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
const projectId = project.id;
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityProjectMembership.identityId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to edit more privileged identity" });
|
||||||
|
|
||||||
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug,
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
|
||||||
|
|
||||||
|
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
||||||
|
return deletedPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPrivilegeDetailsBySlug = async ({
|
||||||
|
projectSlug,
|
||||||
|
identityId,
|
||||||
|
slug,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TGetIdentityPrivilegeDetailsDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
const projectId = project.id;
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
|
||||||
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug,
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
|
||||||
|
|
||||||
|
return identityPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listIdentityProjectPrivileges = async ({
|
||||||
|
identityId,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
projectSlug
|
||||||
|
}: TListIdentityPrivilegesDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
const projectId = project.id;
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
|
||||||
|
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find({
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
return identityPrivileges;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
create,
|
||||||
|
updateBySlug,
|
||||||
|
deleteBySlug,
|
||||||
|
getPrivilegeDetailsBySlug,
|
||||||
|
listIdentityProjectPrivileges
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,54 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
||||||
|
Relative = "relative"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TCreateIdentityPrivilegeDTO = {
|
||||||
|
permissions: unknown;
|
||||||
|
identityId: string;
|
||||||
|
projectSlug: string;
|
||||||
|
slug: string;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
isTemporary: false;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
isTemporary: true;
|
||||||
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
temporaryRange: string;
|
||||||
|
temporaryAccessStartTime: string;
|
||||||
|
}
|
||||||
|
) &
|
||||||
|
Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdateIdentityPrivilegeDTO = { slug: string; identityId: string; projectSlug: string } & Omit<
|
||||||
|
TProjectPermission,
|
||||||
|
"projectId"
|
||||||
|
> & {
|
||||||
|
data: Partial<{
|
||||||
|
permissions: unknown;
|
||||||
|
slug: string;
|
||||||
|
isTemporary: boolean;
|
||||||
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
temporaryRange: string;
|
||||||
|
temporaryAccessStartTime: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDeleteIdentityPrivilegeDTO = Omit<TProjectPermission, "projectId"> & {
|
||||||
|
slug: string;
|
||||||
|
identityId: string;
|
||||||
|
projectSlug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetIdentityPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & {
|
||||||
|
slug: string;
|
||||||
|
identityId: string;
|
||||||
|
projectSlug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TListIdentityPrivilegesDTO = Omit<TProjectPermission, "projectId"> & {
|
||||||
|
identityId: string;
|
||||||
|
projectSlug: string;
|
||||||
|
};
|
@@ -12,7 +12,6 @@ import {
|
|||||||
infisicalSymmetricEncypt
|
infisicalSymmetricEncypt
|
||||||
} from "@app/lib/crypto/encryption";
|
} from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { TOrgPermission } from "@app/lib/types";
|
|
||||||
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-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 { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { TLdapConfigDALFactory } from "./ldap-config-dal";
|
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 = {
|
type TLdapConfigServiceFactoryDep = {
|
||||||
ldapConfigDAL: TLdapConfigDALFactory;
|
ldapConfigDAL: TLdapConfigDALFactory;
|
||||||
@@ -282,7 +281,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
orgId,
|
orgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
}: TOrgPermission) => {
|
}: TGetLdapCfgDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
|
||||||
return getLdapCfg({
|
return getLdapCfg({
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
|
|
||||||
export type TCreateLdapCfgDTO = {
|
export type TCreateLdapCfgDTO = {
|
||||||
|
orgId: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
bindDN: string;
|
bindDN: string;
|
||||||
@@ -9,7 +10,9 @@ export type TCreateLdapCfgDTO = {
|
|||||||
caCert: string;
|
caCert: string;
|
||||||
} & TOrgPermission;
|
} & TOrgPermission;
|
||||||
|
|
||||||
export type TUpdateLdapCfgDTO = Partial<{
|
export type TUpdateLdapCfgDTO = {
|
||||||
|
orgId: string;
|
||||||
|
} & Partial<{
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
bindDN: string;
|
bindDN: string;
|
||||||
@@ -19,6 +22,10 @@ export type TUpdateLdapCfgDTO = Partial<{
|
|||||||
}> &
|
}> &
|
||||||
TOrgPermission;
|
TOrgPermission;
|
||||||
|
|
||||||
|
export type TGetLdapCfgDTO = {
|
||||||
|
orgId: string;
|
||||||
|
} & TOrgPermission;
|
||||||
|
|
||||||
export type TLdapLoginDTO = {
|
export type TLdapLoginDTO = {
|
||||||
externalId: string;
|
externalId: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
@@ -20,6 +20,7 @@ export const getDefaultOnPremFeatures = () => {
|
|||||||
samlSSO: false,
|
samlSSO: false,
|
||||||
scim: false,
|
scim: false,
|
||||||
ldap: false,
|
ldap: false,
|
||||||
|
groups: false,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
has_used_trial: true,
|
has_used_trial: true,
|
||||||
|
@@ -27,6 +27,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
samlSSO: false,
|
samlSSO: false,
|
||||||
scim: false,
|
scim: false,
|
||||||
ldap: false,
|
ldap: false,
|
||||||
|
groups: false,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
has_used_trial: true,
|
has_used_trial: true,
|
||||||
|
@@ -43,6 +43,7 @@ export type TFeatureSet = {
|
|||||||
samlSSO: false;
|
samlSSO: false;
|
||||||
scim: false;
|
scim: false;
|
||||||
ldap: false;
|
ldap: false;
|
||||||
|
groups: false;
|
||||||
status: null;
|
status: null;
|
||||||
trial_end: null;
|
trial_end: null;
|
||||||
has_used_trial: true;
|
has_used_trial: true;
|
||||||
|
@@ -18,6 +18,7 @@ export enum OrgPermissionSubjects {
|
|||||||
Sso = "sso",
|
Sso = "sso",
|
||||||
Scim = "scim",
|
Scim = "scim",
|
||||||
Ldap = "ldap",
|
Ldap = "ldap",
|
||||||
|
Groups = "groups",
|
||||||
Billing = "billing",
|
Billing = "billing",
|
||||||
SecretScanning = "secret-scanning",
|
SecretScanning = "secret-scanning",
|
||||||
Identity = "identity"
|
Identity = "identity"
|
||||||
@@ -33,6 +34,7 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.Groups]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
|
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
|
||||||
@@ -83,6 +85,11 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
||||||
can(OrgPermissionActions.Delete, 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.Read, OrgPermissionSubjects.Billing);
|
||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
|
||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
||||||
@@ -105,6 +112,7 @@ const buildMemberPermission = () => {
|
|||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
|
||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||||
|
@@ -45,6 +45,43 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const getProjectPermission = async (userId: string, projectId: string) => {
|
const getProjectPermission = async (userId: string, projectId: string) => {
|
||||||
try {
|
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)
|
const docs = await db(TableName.ProjectMembership)
|
||||||
.join(
|
.join(
|
||||||
TableName.ProjectUserMembershipRole,
|
TableName.ProjectUserMembershipRole,
|
||||||
@@ -56,6 +93,11 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.ProjectUserMembershipRole}.customRoleId`,
|
`${TableName.ProjectUserMembershipRole}.customRoleId`,
|
||||||
`${TableName.ProjectRoles}.id`
|
`${TableName.ProjectRoles}.id`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.ProjectUserAdditionalPrivilege,
|
||||||
|
`${TableName.ProjectUserAdditionalPrivilege}.projectMembershipId`,
|
||||||
|
`${TableName.ProjectMembership}.id`
|
||||||
|
)
|
||||||
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||||
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
||||||
.where("userId", userId)
|
.where("userId", userId)
|
||||||
@@ -64,30 +106,36 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"),
|
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
|
// 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("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"),
|
||||||
db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"),
|
db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"),
|
||||||
|
db.ref("projectId").withSchema(TableName.ProjectMembership),
|
||||||
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
||||||
db.ref("orgId").withSchema(TableName.Project),
|
db.ref("orgId").withSchema(TableName.Project),
|
||||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
|
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
|
||||||
)
|
db.ref("permissions").withSchema(TableName.ProjectRoles),
|
||||||
.select("permissions");
|
db.ref("id").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApId"),
|
||||||
|
db.ref("permissions").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApPermissions"),
|
||||||
|
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryMode"),
|
||||||
|
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApIsTemporary"),
|
||||||
|
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryRange"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessStartTime")
|
||||||
|
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||||
|
.as("userApTemporaryAccessStartTime"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessEndTime")
|
||||||
|
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||||
|
.as("userApTemporaryAccessEndTime")
|
||||||
|
);
|
||||||
|
|
||||||
const permission = sqlNestRelationships({
|
const permission = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
key: "membershipId",
|
key: "projectId",
|
||||||
parentMapper: ({
|
parentMapper: ({ orgId, orgAuthEnforced, membershipId, membershipCreatedAt, membershipUpdatedAt, role }) => ({
|
||||||
orgId,
|
|
||||||
orgAuthEnforced,
|
|
||||||
membershipId,
|
|
||||||
membershipCreatedAt,
|
|
||||||
membershipUpdatedAt,
|
|
||||||
oldRoleField
|
|
||||||
}) => ({
|
|
||||||
orgId,
|
orgId,
|
||||||
orgAuthEnforced,
|
orgAuthEnforced,
|
||||||
userId,
|
userId,
|
||||||
role: oldRoleField,
|
role,
|
||||||
id: membershipId,
|
id: membershipId,
|
||||||
projectId,
|
projectId,
|
||||||
createdAt: membershipCreatedAt,
|
createdAt: membershipCreatedAt,
|
||||||
@@ -102,15 +150,91 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
permissions: z.unknown(),
|
permissions: z.unknown(),
|
||||||
customRoleSlug: z.string().optional().nullable()
|
customRoleSlug: z.string().optional().nullable()
|
||||||
}).parse(data)
|
}).parse(data)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "userApId",
|
||||||
|
label: "additionalPrivileges" as const,
|
||||||
|
mapper: ({
|
||||||
|
userApId,
|
||||||
|
userApPermissions,
|
||||||
|
userApIsTemporary,
|
||||||
|
userApTemporaryMode,
|
||||||
|
userApTemporaryRange,
|
||||||
|
userApTemporaryAccessEndTime,
|
||||||
|
userApTemporaryAccessStartTime
|
||||||
|
}) => ({
|
||||||
|
id: userApId,
|
||||||
|
permissions: userApPermissions,
|
||||||
|
temporaryRange: userApTemporaryRange,
|
||||||
|
temporaryMode: userApTemporaryMode,
|
||||||
|
temporaryAccessEndTime: userApTemporaryAccessEndTime,
|
||||||
|
temporaryAccessStartTime: userApTemporaryAccessStartTime,
|
||||||
|
isTemporary: userApIsTemporary
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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
|
// when introducting cron mode change it here
|
||||||
const activeRoles = permission?.[0]?.roles.filter(
|
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, temporaryAccessEndTime }) =>
|
||||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
);
|
);
|
||||||
return permission?.[0] ? { ...permission[0], roles: activeRoles } : undefined;
|
|
||||||
|
return {
|
||||||
|
...(permission[0] || groupPermission[0]),
|
||||||
|
roles: [...activeRoles, ...activeGroupRoles],
|
||||||
|
additionalPrivileges: activeAdditionalPrivileges
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "GetProjectPermission" });
|
throw new DatabaseError({ error, name: "GetProjectPermission" });
|
||||||
}
|
}
|
||||||
@@ -129,6 +253,11 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
|
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
|
||||||
`${TableName.ProjectRoles}.id`
|
`${TableName.ProjectRoles}.id`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.IdentityProjectAdditionalPrivilege,
|
||||||
|
`${TableName.IdentityProjectAdditionalPrivilege}.projectMembershipId`,
|
||||||
|
`${TableName.IdentityProjectMembership}.id`
|
||||||
|
)
|
||||||
.join(
|
.join(
|
||||||
// Join the Project table to later select orgId
|
// Join the Project table to later select orgId
|
||||||
TableName.Project,
|
TableName.Project,
|
||||||
@@ -144,9 +273,28 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("role").withSchema(TableName.IdentityProjectMembership).as("oldRoleField"),
|
db.ref("role").withSchema(TableName.IdentityProjectMembership).as("oldRoleField"),
|
||||||
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
|
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
|
||||||
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
|
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
|
||||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
|
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
|
||||||
)
|
db.ref("permissions").withSchema(TableName.ProjectRoles),
|
||||||
.select("permissions");
|
db.ref("id").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApId"),
|
||||||
|
db.ref("permissions").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApPermissions"),
|
||||||
|
db
|
||||||
|
.ref("temporaryMode")
|
||||||
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
|
.as("identityApTemporaryMode"),
|
||||||
|
db.ref("isTemporary").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApIsTemporary"),
|
||||||
|
db
|
||||||
|
.ref("temporaryRange")
|
||||||
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
|
.as("identityApTemporaryRange"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessStartTime")
|
||||||
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
|
.as("identityApTemporaryAccessStartTime"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessEndTime")
|
||||||
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
|
.as("identityApTemporaryAccessEndTime")
|
||||||
|
);
|
||||||
|
|
||||||
const permission = sqlNestRelationships({
|
const permission = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
@@ -171,16 +319,44 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
permissions: z.unknown(),
|
permissions: z.unknown(),
|
||||||
customRoleSlug: z.string().optional().nullable()
|
customRoleSlug: z.string().optional().nullable()
|
||||||
}).parse(data)
|
}).parse(data)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "identityApId",
|
||||||
|
label: "additionalPrivileges" as const,
|
||||||
|
mapper: ({
|
||||||
|
identityApId,
|
||||||
|
identityApPermissions,
|
||||||
|
identityApIsTemporary,
|
||||||
|
identityApTemporaryMode,
|
||||||
|
identityApTemporaryRange,
|
||||||
|
identityApTemporaryAccessEndTime,
|
||||||
|
identityApTemporaryAccessStartTime
|
||||||
|
}) => ({
|
||||||
|
id: identityApId,
|
||||||
|
permissions: identityApPermissions,
|
||||||
|
temporaryRange: identityApTemporaryRange,
|
||||||
|
temporaryMode: identityApTemporaryMode,
|
||||||
|
temporaryAccessEndTime: identityApTemporaryAccessEndTime,
|
||||||
|
temporaryAccessStartTime: identityApTemporaryAccessStartTime,
|
||||||
|
isTemporary: identityApIsTemporary
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!permission?.[0]) return undefined;
|
||||||
|
|
||||||
// when introducting cron mode change it here
|
// when introducting cron mode change it here
|
||||||
const activeRoles = permission?.[0]?.roles.filter(
|
const activeRoles = permission?.[0]?.roles.filter(
|
||||||
({ isTemporary, temporaryAccessEndTime }) =>
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
);
|
);
|
||||||
return permission?.[0] ? { ...permission[0], roles: activeRoles } : undefined;
|
const activeAdditionalPrivileges = permission?.[0]?.additionalPrivileges?.filter(
|
||||||
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ...permission[0], roles: activeRoles, additionalPrivileges: activeAdditionalPrivileges };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "GetProjectIdentityPermission" });
|
throw new DatabaseError({ error, name: "GetProjectIdentityPermission" });
|
||||||
}
|
}
|
||||||
|
@@ -5,9 +5,13 @@ import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
|
|||||||
function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
|
function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
|
||||||
if (!actorAuthMethod) return false;
|
if (!actorAuthMethod) return false;
|
||||||
|
|
||||||
return [AuthMethod.AZURE_SAML, AuthMethod.OKTA_SAML, AuthMethod.JUMPCLOUD_SAML, AuthMethod.GOOGLE_SAML].includes(
|
return [
|
||||||
actorAuthMethod
|
AuthMethod.AZURE_SAML,
|
||||||
);
|
AuthMethod.OKTA_SAML,
|
||||||
|
AuthMethod.JUMPCLOUD_SAML,
|
||||||
|
AuthMethod.GOOGLE_SAML,
|
||||||
|
AuthMethod.KEYCLOAK_SAML
|
||||||
|
].includes(actorAuthMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateOrgSAML(actorAuthMethod: ActorAuthMethod, isSamlEnforced: TOrganizations["authEnforced"]) {
|
function validateOrgSAML(actorAuthMethod: ActorAuthMethod, isSamlEnforced: TOrganizations["authEnforced"]) {
|
||||||
|
@@ -180,10 +180,12 @@ export const permissionServiceFactory = ({
|
|||||||
authMethod: ActorAuthMethod,
|
authMethod: ActorAuthMethod,
|
||||||
userOrgId?: string
|
userOrgId?: string
|
||||||
): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
||||||
const membership = await permissionDAL.getProjectPermission(userId, projectId);
|
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
||||||
if (!membership) throw new UnauthorizedError({ name: "User not in project" });
|
if (!userProjectPermission) throw new UnauthorizedError({ name: "User not in project" });
|
||||||
|
|
||||||
if (membership.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions)) {
|
if (
|
||||||
|
userProjectPermission.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions)
|
||||||
|
) {
|
||||||
throw new BadRequestError({ name: "Custom permission not found" });
|
throw new BadRequestError({ name: "Custom permission not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,17 +194,27 @@ export const permissionServiceFactory = ({
|
|||||||
|
|
||||||
// Extra: This means that when users are using API keys to make requests, they can't use slug-based routes.
|
// Extra: This means that when users are using API keys to make requests, they can't use slug-based routes.
|
||||||
// Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization.
|
// Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization.
|
||||||
if (userOrgId !== "API_KEY" && membership.orgId !== userOrgId) {
|
if (userOrgId !== "API_KEY" && userProjectPermission.orgId !== userOrgId) {
|
||||||
throw new UnauthorizedError({ name: "You are not logged into this organization" });
|
throw new UnauthorizedError({ name: "You are not logged into this organization" });
|
||||||
}
|
}
|
||||||
|
|
||||||
validateOrgSAML(authMethod, membership.orgAuthEnforced);
|
validateOrgSAML(authMethod, userProjectPermission.orgAuthEnforced);
|
||||||
|
|
||||||
|
// join two permissions and pass to build the final permission set
|
||||||
|
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
|
const additionalPrivileges =
|
||||||
|
userProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
|
||||||
|
role: ProjectMembershipRole.Custom,
|
||||||
|
permissions
|
||||||
|
})) || [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
permission: buildProjectPermission(membership.roles),
|
permission: buildProjectPermission(rolePermissions.concat(additionalPrivileges)),
|
||||||
membership,
|
membership: userProjectPermission,
|
||||||
hasRole: (role: string) =>
|
hasRole: (role: string) =>
|
||||||
membership.roles.findIndex(({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug) !== -1
|
userProjectPermission.roles.findIndex(
|
||||||
|
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
||||||
|
) !== -1
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -226,8 +238,16 @@ export const permissionServiceFactory = ({
|
|||||||
throw new UnauthorizedError({ name: "You are not a member of this organization" });
|
throw new UnauthorizedError({ name: "You are not a member of this organization" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rolePermissions =
|
||||||
|
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
|
const additionalPrivileges =
|
||||||
|
identityProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
|
||||||
|
role: ProjectMembershipRole.Custom,
|
||||||
|
permissions
|
||||||
|
})) || [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
permission: buildProjectPermission(identityProjectPermission.roles),
|
permission: buildProjectPermission(rolePermissions.concat(additionalPrivileges)),
|
||||||
membership: identityProjectPermission,
|
membership: identityProjectPermission,
|
||||||
hasRole: (role: string) =>
|
hasRole: (role: string) =>
|
||||||
identityProjectPermission.roles.findIndex(
|
identityProjectPermission.roles.findIndex(
|
||||||
|
@@ -12,6 +12,7 @@ export enum ProjectPermissionActions {
|
|||||||
export enum ProjectPermissionSub {
|
export enum ProjectPermissionSub {
|
||||||
Role = "role",
|
Role = "role",
|
||||||
Member = "member",
|
Member = "member",
|
||||||
|
Groups = "groups",
|
||||||
Settings = "settings",
|
Settings = "settings",
|
||||||
Integrations = "integrations",
|
Integrations = "integrations",
|
||||||
Webhooks = "webhooks",
|
Webhooks = "webhooks",
|
||||||
@@ -41,6 +42,7 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Member]
|
| [ProjectPermissionActions, ProjectPermissionSub.Member]
|
||||||
|
| [ProjectPermissionActions, ProjectPermissionSub.Groups]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Integrations]
|
| [ProjectPermissionActions, ProjectPermissionSub.Integrations]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Webhooks]
|
| [ProjectPermissionActions, ProjectPermissionSub.Webhooks]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.AuditLogs]
|
| [ProjectPermissionActions, ProjectPermissionSub.AuditLogs]
|
||||||
@@ -82,6 +84,11 @@ const buildAdminPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
can(ProjectPermissionActions.Delete, 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.Read, ProjectPermissionSub.Role);
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
|
||||||
@@ -157,6 +164,8 @@ const buildMemberPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
|
||||||
@@ -209,6 +218,7 @@ const buildViewerPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TProjectUserAdditionalPrivilegeDALFactory = ReturnType<typeof projectUserAdditionalPrivilegeDALFactory>;
|
||||||
|
|
||||||
|
export const projectUserAdditionalPrivilegeDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.ProjectUserAdditionalPrivilege);
|
||||||
|
return orm;
|
||||||
|
};
|
@@ -0,0 +1,212 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import ms from "ms";
|
||||||
|
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
|
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
|
||||||
|
import {
|
||||||
|
ProjectUserAdditionalPrivilegeTemporaryMode,
|
||||||
|
TCreateUserPrivilegeDTO,
|
||||||
|
TDeleteUserPrivilegeDTO,
|
||||||
|
TGetUserPrivilegeDetailsDTO,
|
||||||
|
TListUserPrivilegesDTO,
|
||||||
|
TUpdateUserPrivilegeDTO
|
||||||
|
} from "./project-user-additional-privilege-types";
|
||||||
|
|
||||||
|
type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
|
||||||
|
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
|
||||||
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TProjectUserAdditionalPrivilegeServiceFactory = ReturnType<
|
||||||
|
typeof projectUserAdditionalPrivilegeServiceFactory
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||||
|
projectUserAdditionalPrivilegeDAL,
|
||||||
|
projectMembershipDAL,
|
||||||
|
permissionService
|
||||||
|
}: TProjectUserAdditionalPrivilegeServiceFactoryDep) => {
|
||||||
|
const create = async ({
|
||||||
|
slug,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
permissions: customPermission,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
projectMembershipId,
|
||||||
|
...dto
|
||||||
|
}: TCreateUserPrivilegeDTO) => {
|
||||||
|
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||||
|
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({ slug, projectMembershipId });
|
||||||
|
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||||
|
|
||||||
|
if (!dto.isTemporary) {
|
||||||
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||||
|
projectMembershipId,
|
||||||
|
slug,
|
||||||
|
permissions: customPermission
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
||||||
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||||
|
projectMembershipId,
|
||||||
|
slug,
|
||||||
|
permissions: customPermission,
|
||||||
|
isTemporary: true,
|
||||||
|
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
|
||||||
|
temporaryRange: dto.temporaryRange,
|
||||||
|
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||||
|
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateById = async ({
|
||||||
|
privilegeId,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
...dto
|
||||||
|
}: TUpdateUserPrivilegeDTO) => {
|
||||||
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
|
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
||||||
|
|
||||||
|
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
|
||||||
|
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
if (dto?.slug) {
|
||||||
|
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug: dto.slug,
|
||||||
|
projectMembershipId: projectMembership.id
|
||||||
|
});
|
||||||
|
if (existingSlug && existingSlug.id !== userPrivilege.id)
|
||||||
|
throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTemporary = typeof dto?.isTemporary !== "undefined" ? dto.isTemporary : userPrivilege.isTemporary;
|
||||||
|
if (isTemporary) {
|
||||||
|
const temporaryAccessStartTime = dto?.temporaryAccessStartTime || userPrivilege?.temporaryAccessStartTime;
|
||||||
|
const temporaryRange = dto?.temporaryRange || userPrivilege?.temporaryRange;
|
||||||
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
||||||
|
...dto,
|
||||||
|
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||||
|
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
||||||
|
...dto,
|
||||||
|
isTemporary: false,
|
||||||
|
temporaryAccessStartTime: null,
|
||||||
|
temporaryAccessEndTime: null,
|
||||||
|
temporaryRange: null,
|
||||||
|
temporaryMode: null
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteById = async ({ actorId, actor, actorOrgId, actorAuthMethod, privilegeId }: TDeleteUserPrivilegeDTO) => {
|
||||||
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
|
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
||||||
|
|
||||||
|
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
|
||||||
|
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
||||||
|
return deletedPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPrivilegeDetailsById = async ({
|
||||||
|
privilegeId,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TGetUserPrivilegeDetailsDTO) => {
|
||||||
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
|
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
||||||
|
|
||||||
|
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
|
||||||
|
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
return userPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listPrivileges = async ({
|
||||||
|
projectMembershipId,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TListUserPrivilegesDTO) => {
|
||||||
|
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||||
|
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find({ projectMembershipId });
|
||||||
|
return userPrivileges;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
create,
|
||||||
|
updateById,
|
||||||
|
deleteById,
|
||||||
|
getPrivilegeDetailsById,
|
||||||
|
listPrivileges
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,40 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export enum ProjectUserAdditionalPrivilegeTemporaryMode {
|
||||||
|
Relative = "relative"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TCreateUserPrivilegeDTO = (
|
||||||
|
| {
|
||||||
|
permissions: unknown;
|
||||||
|
projectMembershipId: string;
|
||||||
|
slug: string;
|
||||||
|
isTemporary: false;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
permissions: unknown;
|
||||||
|
projectMembershipId: string;
|
||||||
|
slug: string;
|
||||||
|
isTemporary: true;
|
||||||
|
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
temporaryRange: string;
|
||||||
|
temporaryAccessStartTime: string;
|
||||||
|
}
|
||||||
|
) &
|
||||||
|
Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdateUserPrivilegeDTO = { privilegeId: string } & Omit<TProjectPermission, "projectId"> &
|
||||||
|
Partial<{
|
||||||
|
permissions: unknown;
|
||||||
|
slug: string;
|
||||||
|
isTemporary: boolean;
|
||||||
|
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
temporaryRange: string;
|
||||||
|
temporaryAccessStartTime: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type TDeleteUserPrivilegeDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
|
||||||
|
|
||||||
|
export type TGetUserPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
|
||||||
|
|
||||||
|
export type TListUserPrivilegesDTO = Omit<TProjectPermission, "projectId"> & { projectMembershipId: string };
|
@@ -319,6 +319,11 @@ export const samlConfigServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
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) {
|
if (user) {
|
||||||
await userDAL.transaction(async (tx) => {
|
await userDAL.transaction(async (tx) => {
|
||||||
const [orgMembership] = await orgDAL.findMembership(
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
|
@@ -5,7 +5,8 @@ export enum SamlProviders {
|
|||||||
OKTA_SAML = "okta-saml",
|
OKTA_SAML = "okta-saml",
|
||||||
AZURE_SAML = "azure-saml",
|
AZURE_SAML = "azure-saml",
|
||||||
JUMPCLOUD_SAML = "jumpcloud-saml",
|
JUMPCLOUD_SAML = "jumpcloud-saml",
|
||||||
GOOGLE_SAML = "google-saml"
|
GOOGLE_SAML = "google-saml",
|
||||||
|
KEYCLOAK_SAML = "keycloak-saml"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TCreateSamlCfgDTO = {
|
export type TCreateSamlCfgDTO = {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { TListScimUsers, TScimUser } from "./scim-types";
|
import { TListScimGroups, TListScimUsers, TScimGroup, TScimUser } from "./scim-types";
|
||||||
|
|
||||||
export const buildScimUserList = ({
|
export const buildScimUserList = ({
|
||||||
scimUsers,
|
scimUsers,
|
||||||
@@ -62,3 +62,47 @@ export const buildScimUser = ({
|
|||||||
|
|
||||||
return scimUser;
|
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;
|
||||||
|
};
|
||||||
|
@@ -1,10 +1,13 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
import jwt from "jsonwebtoken";
|
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 { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
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 { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { buildScimUser, buildScimUserList } from "./scim-fns";
|
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList } from "./scim-fns";
|
||||||
import {
|
import {
|
||||||
|
TCreateScimGroupDTO,
|
||||||
TCreateScimTokenDTO,
|
TCreateScimTokenDTO,
|
||||||
TCreateScimUserDTO,
|
TCreateScimUserDTO,
|
||||||
|
TDeleteScimGroupDTO,
|
||||||
TDeleteScimTokenDTO,
|
TDeleteScimTokenDTO,
|
||||||
|
TDeleteScimUserDTO,
|
||||||
|
TGetScimGroupDTO,
|
||||||
TGetScimUserDTO,
|
TGetScimUserDTO,
|
||||||
|
TListScimGroupsDTO,
|
||||||
TListScimUsers,
|
TListScimUsers,
|
||||||
TListScimUsersDTO,
|
TListScimUsersDTO,
|
||||||
TReplaceScimUserDTO,
|
TReplaceScimUserDTO,
|
||||||
TScimTokenJwtPayload,
|
TScimTokenJwtPayload,
|
||||||
|
TUpdateScimGroupNamePatchDTO,
|
||||||
|
TUpdateScimGroupNamePutDTO,
|
||||||
TUpdateScimUserDTO
|
TUpdateScimUserDTO
|
||||||
} from "./scim-types";
|
} from "./scim-types";
|
||||||
|
|
||||||
@@ -39,6 +49,7 @@ type TScimServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
projectDAL: Pick<TProjectDALFactory, "find">;
|
projectDAL: Pick<TProjectDALFactory, "find">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete">;
|
||||||
|
groupDAL: Pick<TGroupDALFactory, "create" | "findOne" | "findAllGroupMembers" | "update" | "delete" | "findGroups">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
@@ -53,6 +64,7 @@ export const scimServiceFactory = ({
|
|||||||
orgDAL,
|
orgDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
|
groupDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
smtpService
|
smtpService
|
||||||
}: TScimServiceFactoryDep) => {
|
}: 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 fnValidateScimToken = async (token: TScimTokenJwtPayload) => {
|
||||||
const scimToken = await scimDAL.findById(token.scimTokenId);
|
const scimToken = await scimDAL.findById(token.scimTokenId);
|
||||||
if (!scimToken) throw new UnauthorizedError();
|
if (!scimToken) throw new UnauthorizedError();
|
||||||
@@ -455,6 +682,13 @@ export const scimServiceFactory = ({
|
|||||||
createScimUser,
|
createScimUser,
|
||||||
updateScimUser,
|
updateScimUser,
|
||||||
replaceScimUser,
|
replaceScimUser,
|
||||||
|
deleteScimUser,
|
||||||
|
listScimGroups,
|
||||||
|
createScimGroup,
|
||||||
|
getScimGroup,
|
||||||
|
deleteScimGroup,
|
||||||
|
updateScimGroupNamePut,
|
||||||
|
updateScimGroupNamePatch,
|
||||||
fnValidateScimToken
|
fnValidateScimToken
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -59,6 +59,73 @@ export type TReplaceScimUserDTO = {
|
|||||||
orgId: string;
|
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 = {
|
export type TScimTokenJwtPayload = {
|
||||||
scimTokenId: string;
|
scimTokenId: string;
|
||||||
authTokenType: string;
|
authTokenType: string;
|
||||||
@@ -86,3 +153,17 @@ export type TScimUser = {
|
|||||||
location: null;
|
location: null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TScimGroup = {
|
||||||
|
schemas: string[];
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
members: {
|
||||||
|
value: string;
|
||||||
|
display: string;
|
||||||
|
}[];
|
||||||
|
meta: {
|
||||||
|
resourceType: string;
|
||||||
|
location: null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@@ -90,7 +90,17 @@ export const secretRotationDbFn = async ({
|
|||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const ssl = ca ? { rejectUnauthorized: false, ca } : undefined;
|
const ssl = ca ? { rejectUnauthorized: false, ca } : undefined;
|
||||||
if (host === "localhost" || host === "127.0.0.1" || getDbConnectionHost(appCfg.DB_CONNECTION_URI) === host)
|
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);
|
||||||
|
if (
|
||||||
|
host === "localhost" ||
|
||||||
|
host === "127.0.0.1" ||
|
||||||
|
// database infisical uses
|
||||||
|
dbHost === host ||
|
||||||
|
// internal ips
|
||||||
|
host === "host.docker.internal" ||
|
||||||
|
host.match(/^10\.\d+\.\d+\.\d+/) ||
|
||||||
|
host.match(/^192\.168\.\d+\.\d+/)
|
||||||
|
)
|
||||||
throw new Error("Invalid db host");
|
throw new Error("Invalid db host");
|
||||||
|
|
||||||
const db = knex({
|
const db = knex({
|
||||||
|
@@ -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 = {
|
export const IDENTITIES = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
name: "The name of the identity to create.",
|
name: "The name of the identity to create.",
|
||||||
@@ -79,6 +110,9 @@ export const ORGANIZATIONS = {
|
|||||||
},
|
},
|
||||||
GET_PROJECTS: {
|
GET_PROJECTS: {
|
||||||
organizationId: "The ID of the organization to get projects from."
|
organizationId: "The ID of the organization to get projects from."
|
||||||
|
},
|
||||||
|
LIST_GROUPS: {
|
||||||
|
organizationId: "The ID of the organization to list groups for."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -141,6 +175,29 @@ export const PROJECTS = {
|
|||||||
},
|
},
|
||||||
ROLLBACK_TO_SNAPSHOT: {
|
ROLLBACK_TO_SNAPSHOT: {
|
||||||
secretSnapshotId: "The ID of the snapshot to rollback to."
|
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;
|
} as const;
|
||||||
|
|
||||||
@@ -215,6 +272,8 @@ export const SECRETS = {
|
|||||||
|
|
||||||
export const RAW_SECRETS = {
|
export const RAW_SECRETS = {
|
||||||
LIST: {
|
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.",
|
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.",
|
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.",
|
environment: "The slug of the environment to list secrets from.",
|
||||||
@@ -397,3 +456,155 @@ export const SECRET_TAGS = {
|
|||||||
projectId: "The ID of the project to delete the tag from."
|
projectId: "The ID of the project to delete the tag from."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const IDENTITY_ADDITIONAL_PRIVILEGE = {
|
||||||
|
CREATE: {
|
||||||
|
projectSlug: "The slug of the project of the identity in.",
|
||||||
|
identityId: "The ID of the identity to delete.",
|
||||||
|
slug: "The slug of the privilege to create.",
|
||||||
|
permissions: `The permission object for the privilege.
|
||||||
|
1. [["read", "secrets", {environment: "dev", secretPath: {$glob: "/"}}]]
|
||||||
|
2. [["read", "secrets", {environment: "dev"}], ["create", "secrets", {environment: "dev"}]]
|
||||||
|
2. [["read", "secrets", {environment: "dev"}]]
|
||||||
|
`,
|
||||||
|
isPackPermission: "Whether the server should pack(compact) the permission object.",
|
||||||
|
isTemporary: "Whether the privilege is temporary.",
|
||||||
|
temporaryMode: "Type of temporary access given. Types: relative",
|
||||||
|
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
|
||||||
|
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
projectSlug: "The slug of the project of the identity in.",
|
||||||
|
identityId: "The ID of the identity to update.",
|
||||||
|
slug: "The slug of the privilege to update.",
|
||||||
|
newSlug: "The new slug of the privilege to update.",
|
||||||
|
permissions: `The permission object for the privilege.
|
||||||
|
1. [["read", "secrets", {environment: "dev", secretPath: {$glob: "/"}}]]
|
||||||
|
2. [["read", "secrets", {environment: "dev"}], ["create", "secrets", {environment: "dev"}]]
|
||||||
|
2. [["read", "secrets", {environment: "dev"}]]
|
||||||
|
`,
|
||||||
|
isPackPermission: "Whether the server should pack(compact) the permission object.",
|
||||||
|
isTemporary: "Whether the privilege is temporary.",
|
||||||
|
temporaryMode: "Type of temporary access given. Types: relative",
|
||||||
|
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
|
||||||
|
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
projectSlug: "The slug of the project of the identity in.",
|
||||||
|
identityId: "The ID of the identity to delete.",
|
||||||
|
slug: "The slug of the privilege to delete."
|
||||||
|
},
|
||||||
|
GET_BY_SLUG: {
|
||||||
|
projectSlug: "The slug of the project of the identity in.",
|
||||||
|
identityId: "The ID of the identity to list.",
|
||||||
|
slug: "The slug of the privilege."
|
||||||
|
},
|
||||||
|
LIST: {
|
||||||
|
projectSlug: "The slug of the project of the identity in.",
|
||||||
|
identityId: "The ID of the identity to list.",
|
||||||
|
unpacked: "Whether the system should send the permissions as unpacked"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PROJECT_USER_ADDITIONAL_PRIVILEGE = {
|
||||||
|
CREATE: {
|
||||||
|
projectMembershipId: "Project membership id of user",
|
||||||
|
slug: "The slug of the privilege to create.",
|
||||||
|
permissions:
|
||||||
|
"The permission object for the privilege. Refer https://casl.js.org/v6/en/guide/define-rules#the-shape-of-raw-rule to understand the shape",
|
||||||
|
isPackPermission: "Whether the server should pack(compact) the permission object.",
|
||||||
|
isTemporary: "Whether the privilege is temporary.",
|
||||||
|
temporaryMode: "Type of temporary access given. Types: relative",
|
||||||
|
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
|
||||||
|
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
privilegeId: "The id of privilege object",
|
||||||
|
slug: "The slug of the privilege to create.",
|
||||||
|
newSlug: "The new slug of the privilege to create.",
|
||||||
|
permissions:
|
||||||
|
"The permission object for the privilege. Refer https://casl.js.org/v6/en/guide/define-rules#the-shape-of-raw-rule to understand the shape",
|
||||||
|
isPackPermission: "Whether the server should pack(compact) the permission object.",
|
||||||
|
isTemporary: "Whether the privilege is temporary.",
|
||||||
|
temporaryMode: "Type of temporary access given. Types: relative",
|
||||||
|
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
|
||||||
|
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
privilegeId: "The id of privilege object"
|
||||||
|
},
|
||||||
|
GET_BY_PRIVILEGEID: {
|
||||||
|
privilegeId: "The id of privilege object"
|
||||||
|
},
|
||||||
|
LIST: {
|
||||||
|
projectMembershipId: "Project membership id of user"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const INTEGRATION_AUTH = {
|
||||||
|
GET: {
|
||||||
|
integrationAuthId: "The id of integration authentication object."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
integration: "The slug of the integration to be unauthorized.",
|
||||||
|
projectId: "The ID of the project to delete the integration auth from."
|
||||||
|
},
|
||||||
|
DELETE_BY_ID: {
|
||||||
|
integrationAuthId: "The id of integration authentication object to delete."
|
||||||
|
},
|
||||||
|
CREATE_ACCESS_TOKEN: {
|
||||||
|
workspaceId: "The ID of the project to create the integration auth for.",
|
||||||
|
integration: "The slug of integration for the auth object.",
|
||||||
|
accessId: "The unique authorized access id of the external integration provider.",
|
||||||
|
accessToken: "The unique authorized access token of the external integration provider.",
|
||||||
|
url: "",
|
||||||
|
namespace: "",
|
||||||
|
refreshToken: "The refresh token for integration authorization."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const INTEGRATION = {
|
||||||
|
CREATE: {
|
||||||
|
integrationAuthId: "The ID of the integration auth object to link with integration.",
|
||||||
|
app: "The name of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.",
|
||||||
|
isActive: "Whether the integration should be active or disabled.",
|
||||||
|
appId:
|
||||||
|
"The ID of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.",
|
||||||
|
secretPath: "The path of the secrets to sync secrets from.",
|
||||||
|
sourceEnvironment: "The environment to sync secret from.",
|
||||||
|
targetEnvironment:
|
||||||
|
"The target environment of the integration provider. Used in cloudflare pages, TeamCity, Gitlab integrations.",
|
||||||
|
targetEnvironmentId:
|
||||||
|
"The target environment id of the integration provider. Used in cloudflare pages, teamcity, gitlab integrations.",
|
||||||
|
targetService:
|
||||||
|
"The service based grouping identifier of the external provider. Used in Terraform cloud, Checkly, Railway and NorthFlank",
|
||||||
|
targetServiceId:
|
||||||
|
"The service based grouping identifier ID of the external provider. Used in Terraform cloud, Checkly, Railway and NorthFlank",
|
||||||
|
owner: "External integration providers service entity owner. Used in Github.",
|
||||||
|
path: "Path to save the synced secrets. Used by Gitlab, AWS Parameter Store, Vault",
|
||||||
|
region: "AWS region to sync secrets to.",
|
||||||
|
scope: "Scope of the provider. Used by Github, Qovery",
|
||||||
|
metadata: {
|
||||||
|
secretPrefix: "The prefix for the saved secret. Used by GCP",
|
||||||
|
secretSuffix: "The suffix for the saved secret. Used by GCP",
|
||||||
|
initialSyncBehavoir: "Type of syncing behavoir with the integration",
|
||||||
|
shouldAutoRedeploy: "Used by Render to trigger auto deploy",
|
||||||
|
secretGCPLabel: "The label for the GCP secrets"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
integrationId: "The ID of the integration object.",
|
||||||
|
app: "The name of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.",
|
||||||
|
appId:
|
||||||
|
"The ID of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.",
|
||||||
|
isActive: "Whether the integration should be active or disabled.",
|
||||||
|
secretPath: "The path of the secrets to sync secrets from.",
|
||||||
|
owner: "External integration providers service entity owner. Used in Github.",
|
||||||
|
targetEnvironment:
|
||||||
|
"The target environment of the integration provider. Used in cloudflare pages, TeamCity, Gitlab integrations.",
|
||||||
|
environment: "The environment to sync secrets from."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
integrationId: "The ID of the integration object."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@@ -114,7 +114,8 @@ const envSchema = z
|
|||||||
.enum(["true", "false"])
|
.enum(["true", "false"])
|
||||||
.transform((val) => val === "true")
|
.transform((val) => val === "true")
|
||||||
.optional(),
|
.optional(),
|
||||||
INFISICAL_CLOUD: zodStrBool.default("false")
|
INFISICAL_CLOUD: zodStrBool.default("false"),
|
||||||
|
MAINTENANCE_MODE: zodStrBool.default("false")
|
||||||
})
|
})
|
||||||
.transform((data) => ({
|
.transform((data) => ({
|
||||||
...data,
|
...data,
|
||||||
|
@@ -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`;
|
||||||
|
@@ -2,5 +2,6 @@
|
|||||||
// Full credits goes to https://github.com/rayapps to those functions
|
// 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
|
// Code taken to keep in in house and to adjust somethings for our needs
|
||||||
export * from "./array";
|
export * from "./array";
|
||||||
|
export * from "./dates";
|
||||||
export * from "./object";
|
export * from "./object";
|
||||||
export * from "./string";
|
export * from "./string";
|
||||||
|
@@ -1,5 +1,17 @@
|
|||||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
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 = {
|
export type TOrgPermission = {
|
||||||
actor: ActorType;
|
actor: ActorType;
|
||||||
actorId: string;
|
actorId: string;
|
||||||
@@ -16,6 +28,15 @@ export type TProjectPermission = {
|
|||||||
actorOrgId: string;
|
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> = {
|
export type RequiredKeys<T> = {
|
||||||
[K in keyof T]-?: undefined extends T[K] ? never : K;
|
[K in keyof T]-?: undefined extends T[K] ? never : K;
|
||||||
}[keyof T];
|
}[keyof T];
|
||||||
|
@@ -61,11 +61,11 @@ export type TQueueJobTypes = {
|
|||||||
};
|
};
|
||||||
[QueueName.SecretWebhook]: {
|
[QueueName.SecretWebhook]: {
|
||||||
name: QueueJobs.SecWebhook;
|
name: QueueJobs.SecWebhook;
|
||||||
payload: { projectId: string; environment: string; secretPath: string };
|
payload: { projectId: string; environment: string; secretPath: string; depth?: number };
|
||||||
};
|
};
|
||||||
[QueueName.IntegrationSync]: {
|
[QueueName.IntegrationSync]: {
|
||||||
name: QueueJobs.IntegrationSync;
|
name: QueueJobs.IntegrationSync;
|
||||||
payload: { projectId: string; environment: string; secretPath: string };
|
payload: { projectId: string; environment: string; secretPath: string; depth?: number };
|
||||||
};
|
};
|
||||||
[QueueName.SecretFullRepoScan]: {
|
[QueueName.SecretFullRepoScan]: {
|
||||||
name: QueueJobs.SecretScan;
|
name: QueueJobs.SecretScan;
|
||||||
|
@@ -24,6 +24,7 @@ import { fastifyErrHandler } from "./plugins/error-handler";
|
|||||||
import { registerExternalNextjs } from "./plugins/external-nextjs";
|
import { registerExternalNextjs } from "./plugins/external-nextjs";
|
||||||
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "./plugins/fastify-zod";
|
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "./plugins/fastify-zod";
|
||||||
import { fastifyIp } from "./plugins/ip";
|
import { fastifyIp } from "./plugins/ip";
|
||||||
|
import { maintenanceMode } from "./plugins/maintenanceMode";
|
||||||
import { fastifySwagger } from "./plugins/swagger";
|
import { fastifySwagger } from "./plugins/swagger";
|
||||||
import { registerRoutes } from "./routes";
|
import { registerRoutes } from "./routes";
|
||||||
|
|
||||||
@@ -72,6 +73,8 @@ export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => {
|
|||||||
}
|
}
|
||||||
await server.register(helmet, { contentSecurityPolicy: false });
|
await server.register(helmet, { contentSecurityPolicy: false });
|
||||||
|
|
||||||
|
await server.register(maintenanceMode);
|
||||||
|
|
||||||
await server.register(registerRoutes, { smtp, queue, db, keyStore });
|
await server.register(registerRoutes, { smtp, queue, db, keyStore });
|
||||||
|
|
||||||
if (appCfg.isProductionMode) {
|
if (appCfg.isProductionMode) {
|
||||||
|
@@ -18,14 +18,43 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const authRateLimit: RateLimitOptions = {
|
// GET endpoints
|
||||||
|
export const readLimit: RateLimitOptions = {
|
||||||
timeWindow: 60 * 1000,
|
timeWindow: 60 * 1000,
|
||||||
max: 600,
|
max: 600,
|
||||||
keyGenerator: (req) => req.realIp
|
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,
|
timeWindow: 60 * 1000,
|
||||||
max: 600,
|
max: 600,
|
||||||
keyGenerator: (req) => req.realIp
|
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
|
||||||
|
};
|
||||||
|
12
backend/src/server/plugins/maintenanceMode.ts
Normal file
12
backend/src/server/plugins/maintenanceMode.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import fp from "fastify-plugin";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
|
||||||
|
export const maintenanceMode = fp(async (fastify) => {
|
||||||
|
fastify.addHook("onRequest", async (req) => {
|
||||||
|
const serverEnvs = getConfig();
|
||||||
|
if (req.url !== "/api/v1/auth/checkAuth" && req.method !== "GET" && serverEnvs.MAINTENANCE_MODE) {
|
||||||
|
throw new Error("Infisical is in maintenance mode. Please try again later.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@@ -4,6 +4,7 @@ import SmeeClient from "smee-client";
|
|||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
|
||||||
export const registerSecretScannerGhApp = async (server: FastifyZodProvider) => {
|
export const registerSecretScannerGhApp = async (server: FastifyZodProvider) => {
|
||||||
const probotApp = (app: Probot) => {
|
const probotApp = (app: Probot) => {
|
||||||
@@ -49,6 +50,9 @@ export const registerSecretScannerGhApp = async (server: FastifyZodProvider) =>
|
|||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/",
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
handler: async (req, res) => {
|
handler: async (req, res) => {
|
||||||
const eventName = req.headers["x-github-event"];
|
const eventName = req.headers["x-github-event"];
|
||||||
const signatureSHA256 = req.headers["x-hub-signature-256"] as string;
|
const signatureSHA256 = req.headers["x-hub-signature-256"] as string;
|
||||||
|
@@ -5,12 +5,25 @@ import { registerV1EERoutes } from "@app/ee/routes/v1";
|
|||||||
import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
||||||
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
|
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
|
||||||
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||||
|
import { dynamicSecretDALFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-dal";
|
||||||
|
import { dynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service";
|
||||||
|
import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/providers";
|
||||||
|
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";
|
import { ldapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
|
||||||
import { ldapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
import { ldapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||||
import { licenseDALFactory } from "@app/ee/services/license/license-dal";
|
import { licenseDALFactory } from "@app/ee/services/license/license-dal";
|
||||||
import { licenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { licenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { permissionDALFactory } from "@app/ee/services/permission/permission-dal";
|
import { permissionDALFactory } from "@app/ee/services/permission/permission-dal";
|
||||||
import { permissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { permissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { projectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||||
|
import { projectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service";
|
||||||
import { samlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal";
|
import { samlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal";
|
||||||
import { samlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
import { samlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
||||||
import { scimDALFactory } from "@app/ee/services/scim/scim-dal";
|
import { scimDALFactory } from "@app/ee/services/scim/scim-dal";
|
||||||
@@ -39,6 +52,7 @@ import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-
|
|||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { TQueueServiceFactory } from "@app/queue";
|
import { TQueueServiceFactory } from "@app/queue";
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal";
|
import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal";
|
||||||
import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
||||||
import { authDALFactory } from "@app/services/auth/auth-dal";
|
import { authDALFactory } from "@app/services/auth/auth-dal";
|
||||||
@@ -47,12 +61,9 @@ import { authPaswordServiceFactory } from "@app/services/auth/auth-password-serv
|
|||||||
import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service";
|
import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service";
|
||||||
import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal";
|
import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal";
|
||||||
import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
import { dynamicSecretDALFactory } from "@app/services/dynamic-secret/dynamic-secret-dal";
|
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
import { dynamicSecretServiceFactory } from "@app/services/dynamic-secret/dynamic-secret-service";
|
import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal";
|
||||||
import { buildDynamicSecretProviders } from "@app/services/dynamic-secret/providers";
|
import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
||||||
import { dynamicSecretLeaseDALFactory } from "@app/services/dynamic-secret-lease/dynamic-secret-lease-dal";
|
|
||||||
import { dynamicSecretLeaseQueueServiceFactory } from "@app/services/dynamic-secret-lease/dynamic-secret-lease-queue";
|
|
||||||
import { dynamicSecretLeaseServiceFactory } from "@app/services/dynamic-secret-lease/dynamic-secret-lease-service";
|
|
||||||
import { identityDALFactory } from "@app/services/identity/identity-dal";
|
import { identityDALFactory } from "@app/services/identity/identity-dal";
|
||||||
import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
|
import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
|
||||||
import { identityServiceFactory } from "@app/services/identity/identity-service";
|
import { identityServiceFactory } from "@app/services/identity/identity-service";
|
||||||
@@ -149,6 +160,7 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
const projectDAL = projectDALFactory(db);
|
const projectDAL = projectDALFactory(db);
|
||||||
const projectMembershipDAL = projectMembershipDALFactory(db);
|
const projectMembershipDAL = projectMembershipDALFactory(db);
|
||||||
|
const projectUserAdditionalPrivilegeDAL = projectUserAdditionalPrivilegeDALFactory(db);
|
||||||
const projectUserMembershipRoleDAL = projectUserMembershipRoleDALFactory(db);
|
const projectUserMembershipRoleDAL = projectUserMembershipRoleDALFactory(db);
|
||||||
const projectRoleDAL = projectRoleDALFactory(db);
|
const projectRoleDAL = projectRoleDALFactory(db);
|
||||||
const projectEnvDAL = projectEnvDALFactory(db);
|
const projectEnvDAL = projectEnvDALFactory(db);
|
||||||
@@ -174,6 +186,7 @@ export const registerRoutes = async (
|
|||||||
const identityOrgMembershipDAL = identityOrgDALFactory(db);
|
const identityOrgMembershipDAL = identityOrgDALFactory(db);
|
||||||
const identityProjectDAL = identityProjectDALFactory(db);
|
const identityProjectDAL = identityProjectDALFactory(db);
|
||||||
const identityProjectMembershipRoleDAL = identityProjectMembershipRoleDALFactory(db);
|
const identityProjectMembershipRoleDAL = identityProjectMembershipRoleDALFactory(db);
|
||||||
|
const identityProjectAdditionalPrivilegeDAL = identityProjectAdditionalPrivilegeDALFactory(db);
|
||||||
|
|
||||||
const identityUaDAL = identityUaDALFactory(db);
|
const identityUaDAL = identityUaDALFactory(db);
|
||||||
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
|
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
|
||||||
@@ -200,6 +213,10 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
const gitAppInstallSessionDAL = gitAppInstallSessionDALFactory(db);
|
const gitAppInstallSessionDAL = gitAppInstallSessionDALFactory(db);
|
||||||
const gitAppOrgDAL = gitAppDALFactory(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 secretScanningDAL = secretScanningDALFactory(db);
|
||||||
const licenseDAL = licenseDALFactory(db);
|
const licenseDAL = licenseDALFactory(db);
|
||||||
const dynamicSecretDAL = dynamicSecretDALFactory(db);
|
const dynamicSecretDAL = dynamicSecretDALFactory(db);
|
||||||
@@ -242,6 +259,29 @@ export const registerRoutes = async (
|
|||||||
samlConfigDAL,
|
samlConfigDAL,
|
||||||
licenseService
|
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({
|
const scimService = scimServiceFactory({
|
||||||
licenseService,
|
licenseService,
|
||||||
scimDAL,
|
scimDAL,
|
||||||
@@ -249,6 +289,7 @@ export const registerRoutes = async (
|
|||||||
orgDAL,
|
orgDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
|
groupDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
smtpService
|
smtpService
|
||||||
});
|
});
|
||||||
@@ -295,6 +336,7 @@ export const registerRoutes = async (
|
|||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
userDAL,
|
userDAL,
|
||||||
|
groupDAL,
|
||||||
orgBotDAL
|
orgBotDAL
|
||||||
});
|
});
|
||||||
const signupService = authSignupServiceFactory({
|
const signupService = authSignupServiceFactory({
|
||||||
@@ -340,11 +382,17 @@ export const registerRoutes = async (
|
|||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
|
const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({
|
||||||
|
permissionService,
|
||||||
|
projectMembershipDAL,
|
||||||
|
projectUserAdditionalPrivilegeDAL
|
||||||
|
});
|
||||||
const projectKeyService = projectKeyServiceFactory({
|
const projectKeyService = projectKeyServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
@@ -387,7 +435,8 @@ export const registerRoutes = async (
|
|||||||
folderDAL,
|
folderDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
identityProjectMembershipRoleDAL
|
identityProjectMembershipRoleDAL,
|
||||||
|
keyStore
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectEnvService = projectEnvServiceFactory({
|
const projectEnvService = projectEnvServiceFactory({
|
||||||
@@ -398,7 +447,12 @@ export const registerRoutes = async (
|
|||||||
folderDAL
|
folderDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectRoleService = projectRoleServiceFactory({ permissionService, projectRoleDAL });
|
const projectRoleService = projectRoleServiceFactory({
|
||||||
|
permissionService,
|
||||||
|
projectRoleDAL,
|
||||||
|
projectUserMembershipRoleDAL,
|
||||||
|
identityProjectMembershipRoleDAL
|
||||||
|
});
|
||||||
|
|
||||||
const snapshotService = secretSnapshotServiceFactory({
|
const snapshotService = secretSnapshotServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
@@ -427,14 +481,6 @@ export const registerRoutes = async (
|
|||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
snapshotService
|
snapshotService
|
||||||
});
|
});
|
||||||
const secretImportService = secretImportServiceFactory({
|
|
||||||
projectEnvDAL,
|
|
||||||
folderDAL,
|
|
||||||
permissionService,
|
|
||||||
secretImportDAL,
|
|
||||||
projectDAL,
|
|
||||||
secretDAL
|
|
||||||
});
|
|
||||||
const integrationAuthService = integrationAuthServiceFactory({
|
const integrationAuthService = integrationAuthServiceFactory({
|
||||||
integrationAuthDAL,
|
integrationAuthDAL,
|
||||||
integrationDAL,
|
integrationDAL,
|
||||||
@@ -462,6 +508,15 @@ export const registerRoutes = async (
|
|||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
secretVersionTagDAL
|
secretVersionTagDAL
|
||||||
});
|
});
|
||||||
|
const secretImportService = secretImportServiceFactory({
|
||||||
|
projectEnvDAL,
|
||||||
|
folderDAL,
|
||||||
|
permissionService,
|
||||||
|
secretImportDAL,
|
||||||
|
projectDAL,
|
||||||
|
secretDAL,
|
||||||
|
secretQueueService
|
||||||
|
});
|
||||||
const secretBlindIndexService = secretBlindIndexServiceFactory({
|
const secretBlindIndexService = secretBlindIndexServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
@@ -479,6 +534,7 @@ export const registerRoutes = async (
|
|||||||
snapshotService,
|
snapshotService,
|
||||||
secretQueueService,
|
secretQueueService,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
|
projectEnvDAL,
|
||||||
projectBotService
|
projectBotService
|
||||||
});
|
});
|
||||||
const sarService = secretApprovalRequestServiceFactory({
|
const sarService = secretApprovalRequestServiceFactory({
|
||||||
@@ -548,6 +604,12 @@ export const registerRoutes = async (
|
|||||||
identityProjectMembershipRoleDAL,
|
identityProjectMembershipRoleDAL,
|
||||||
projectRoleDAL
|
projectRoleDAL
|
||||||
});
|
});
|
||||||
|
const identityProjectAdditionalPrivilegeService = identityProjectAdditionalPrivilegeServiceFactory({
|
||||||
|
projectDAL,
|
||||||
|
identityProjectAdditionalPrivilegeDAL,
|
||||||
|
permissionService,
|
||||||
|
identityProjectDAL
|
||||||
|
});
|
||||||
const identityUaService = identityUaServiceFactory({
|
const identityUaService = identityUaServiceFactory({
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
@@ -600,6 +662,8 @@ export const registerRoutes = async (
|
|||||||
password: passwordService,
|
password: passwordService,
|
||||||
signup: signupService,
|
signup: signupService,
|
||||||
user: userService,
|
user: userService,
|
||||||
|
group: groupService,
|
||||||
|
groupProject: groupProjectService,
|
||||||
permission: permissionService,
|
permission: permissionService,
|
||||||
org: orgService,
|
org: orgService,
|
||||||
orgRole: orgRoleService,
|
orgRole: orgRoleService,
|
||||||
@@ -638,7 +702,9 @@ export const registerRoutes = async (
|
|||||||
trustedIp: trustedIpService,
|
trustedIp: trustedIpService,
|
||||||
scim: scimService,
|
scim: scimService,
|
||||||
secretBlindIndex: secretBlindIndexService,
|
secretBlindIndex: secretBlindIndexService,
|
||||||
telemetry: telemetryService
|
telemetry: telemetryService,
|
||||||
|
projectUserAdditionalPrivilege: projectUserAdditionalPrivilegeService,
|
||||||
|
identityProjectAdditionalPrivilege: identityProjectAdditionalPrivilegeService
|
||||||
});
|
});
|
||||||
|
|
||||||
server.decorate<FastifyZodProvider["store"]>("store", {
|
server.decorate<FastifyZodProvider["store"]>("store", {
|
||||||
@@ -650,8 +716,11 @@ export const registerRoutes = async (
|
|||||||
await server.register(injectAuditLogInfo);
|
await server.register(injectAuditLogInfo);
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/api/status",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/api/status",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|||||||
import { OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
|
import { OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { UnauthorizedError } from "@app/lib/errors";
|
import { UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
|
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -11,24 +12,33 @@ import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
|||||||
|
|
||||||
export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/config",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
config: SuperAdminSchema.omit({ createdAt: true, updatedAt: true })
|
config: SuperAdminSchema.omit({ createdAt: true, updatedAt: true }).merge(
|
||||||
|
z.object({ isMigrationModeOn: z.boolean() })
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
const config = await getServerCfg();
|
const config = await getServerCfg();
|
||||||
return { config };
|
const serverEnvs = getConfig();
|
||||||
|
return { config: { ...config, isMigrationModeOn: serverEnvs.MAINTENANCE_MODE } };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
url: "/config",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
allowSignUp: z.boolean().optional(),
|
allowSignUp: z.boolean().optional(),
|
||||||
@@ -52,8 +62,11 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/signup",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/signup",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
email: z.string().email().trim(),
|
email: z.string().email().trim(),
|
||||||
|
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
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 { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthMode, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -38,8 +38,11 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/checkAuth",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/checkAuth",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -52,8 +55,11 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/token",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/token",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@@ -1,13 +1,17 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectBotsSchema } from "@app/db/schemas";
|
import { ProjectBotsSchema } from "@app/db/schemas";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerProjectBotRouter = async (server: FastifyZodProvider) => {
|
export const registerProjectBotRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:projectId",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:projectId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim()
|
projectId: z.string().trim()
|
||||||
@@ -38,8 +42,11 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:botId/active",
|
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
url: "/:botId/active",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean(),
|
||||||
|
@@ -1,11 +1,15 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { UNIVERSAL_AUTH } from "@app/lib/api-docs";
|
import { UNIVERSAL_AUTH } from "@app/lib/api-docs";
|
||||||
|
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
|
||||||
export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => {
|
export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/token/renew",
|
url: "/token/renew",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
description: "Renew access token",
|
description: "Renew access token",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|||||||
import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas";
|
import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { IDENTITIES } from "@app/lib/api-docs";
|
import { IDENTITIES } from "@app/lib/api-docs";
|
||||||
|
import { creationLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -12,6 +13,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/",
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: creationLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Create identity",
|
description: "Create identity",
|
||||||
@@ -71,6 +75,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/:identityId",
|
url: "/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Update identity",
|
description: "Update identity",
|
||||||
@@ -121,6 +128,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/:identityId",
|
url: "/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Delete identity",
|
description: "Delete identity",
|
||||||
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|||||||
import { IdentityUaClientSecretsSchema, IdentityUniversalAuthsSchema } from "@app/db/schemas";
|
import { IdentityUaClientSecretsSchema, IdentityUniversalAuthsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { UNIVERSAL_AUTH } from "@app/lib/api-docs";
|
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 { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
@@ -22,8 +23,11 @@ export const sanitizedClientSecretSchema = IdentityUaClientSecretsSchema.pick({
|
|||||||
|
|
||||||
export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/universal-auth/login",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/universal-auth/login",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
description: "Login with Universal Auth",
|
description: "Login with Universal Auth",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -66,8 +70,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/universal-auth/identities/:identityId",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/universal-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Attach Universal Auth configuration onto identity",
|
description: "Attach Universal Auth configuration onto identity",
|
||||||
@@ -156,8 +163,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/universal-auth/identities/:identityId",
|
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
url: "/universal-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Update Universal Auth configuration on identity",
|
description: "Update Universal Auth configuration on identity",
|
||||||
@@ -239,8 +249,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/universal-auth/identities/:identityId",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/universal-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Retrieve Universal Auth configuration on identity",
|
description: "Retrieve Universal Auth configuration on identity",
|
||||||
@@ -283,8 +296,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/universal-auth/identities/:identityId/client-secrets",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/universal-auth/identities/:identityId/client-secrets",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Create Universal Auth Client Secret for identity",
|
description: "Create Universal Auth Client Secret for identity",
|
||||||
@@ -335,8 +351,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/universal-auth/identities/:identityId/client-secrets",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/universal-auth/identities/:identityId/client-secrets",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "List Universal Auth Client Secrets for identity",
|
description: "List Universal Auth Client Secrets for identity",
|
||||||
@@ -378,8 +397,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Revoke Universal Auth Client Secrets for identity",
|
description: "Revoke Universal Auth Client Secrets for identity",
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
import { registerAdminRouter } from "./admin-router";
|
import { registerAdminRouter } from "./admin-router";
|
||||||
import { registerAuthRoutes } from "./auth-router";
|
import { registerAuthRoutes } from "./auth-router";
|
||||||
import { registerProjectBotRouter } from "./bot-router";
|
import { registerProjectBotRouter } from "./bot-router";
|
||||||
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
|
||||||
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
|
||||||
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
|
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
|
||||||
import { registerIdentityRouter } from "./identity-router";
|
import { registerIdentityRouter } from "./identity-router";
|
||||||
import { registerIdentityUaRouter } from "./identity-ua";
|
import { registerIdentityUaRouter } from "./identity-ua";
|
||||||
@@ -54,14 +52,6 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
{ prefix: "/workspace" }
|
{ prefix: "/workspace" }
|
||||||
);
|
);
|
||||||
|
|
||||||
await server.register(
|
|
||||||
async (dynamicSecretRouter) => {
|
|
||||||
await dynamicSecretRouter.register(registerDynamicSecretRouter);
|
|
||||||
await dynamicSecretRouter.register(registerDynamicSecretLeaseRouter, { prefix: "/leases" });
|
|
||||||
},
|
|
||||||
{ prefix: "/dynamic-secrets" }
|
|
||||||
);
|
|
||||||
|
|
||||||
await server.register(registerProjectBotRouter, { prefix: "/bot" });
|
await server.register(registerProjectBotRouter, { prefix: "/bot" });
|
||||||
await server.register(registerIntegrationRouter, { prefix: "/integration" });
|
await server.register(registerIntegrationRouter, { prefix: "/integration" });
|
||||||
await server.register(registerIntegrationAuthRouter, { prefix: "/integration-auth" });
|
await server.register(registerIntegrationAuthRouter, { prefix: "/integration-auth" });
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
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 { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -8,10 +10,19 @@ import { integrationAuthPubSchema } from "../sanitizedSchemas";
|
|||||||
|
|
||||||
export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) => {
|
export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/integration-options",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
url: "/integration-options",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
description: "List of integrations available.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
integrationOptions: z
|
integrationOptions: z
|
||||||
@@ -36,12 +47,21 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
url: "/:integrationAuthId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
description: "Get details of an integration authorization by auth object id.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
integrationAuthId: z.string().trim()
|
integrationAuthId: z.string().trim().describe(INTEGRATION_AUTH.GET.integrationAuthId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -62,13 +82,22 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
description: "Remove all integration's auth object from the project.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
integration: z.string().trim(),
|
integration: z.string().trim().describe(INTEGRATION_AUTH.DELETE.integration),
|
||||||
projectId: z.string().trim()
|
projectId: z.string().trim().describe(INTEGRATION_AUTH.DELETE.projectId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -102,12 +131,21 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId",
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
url: "/:integrationAuthId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
description: "Remove an integration auth object by object id.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
integrationAuthId: z.string().trim()
|
integrationAuthId: z.string().trim().describe(INTEGRATION_AUTH.DELETE_BY_ID.integrationAuthId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -140,8 +178,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/oauth-token",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/oauth-token",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -181,18 +222,27 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/access-token",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
url: "/access-token",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
description: "Create the integration authentication object required for syncing secrets.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.workspaceId),
|
||||||
integration: z.string().trim(),
|
integration: z.string().trim().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.integration),
|
||||||
accessId: z.string().trim().optional(),
|
accessId: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.accessId),
|
||||||
accessToken: z.string().trim().optional(),
|
accessToken: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.accessToken),
|
||||||
url: z.string().url().trim().optional(),
|
url: z.string().url().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.url),
|
||||||
namespace: z.string().trim().optional(),
|
namespace: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.namespace),
|
||||||
refreshToken: z.string().trim().optional()
|
refreshToken: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.refreshToken)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -225,8 +275,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/apps",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/apps",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -262,8 +315,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/teams",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/teams",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -293,8 +349,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/vercel/branches",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/vercel/branches",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -323,8 +382,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/checkly/groups",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/checkly/groups",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -353,8 +415,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/github/orgs",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/github/orgs",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -381,8 +446,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/github/envs",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/github/envs",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -415,8 +483,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/qovery/orgs",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/qovery/orgs",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -441,8 +512,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/qovery/projects",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/qovery/projects",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -471,8 +545,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/qovery/environments",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/qovery/environments",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -501,8 +578,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/qovery/apps",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/qovery/apps",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -531,8 +611,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/qovery/containers",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/qovery/containers",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -561,8 +644,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/qovery/jobs",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/qovery/jobs",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -591,8 +677,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/heroku/pipelines",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/heroku/pipelines",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -623,8 +712,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/railway/environments",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/railway/environments",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -653,8 +745,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/railway/services",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/railway/services",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -683,8 +778,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/bitbucket/workspaces",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/bitbucket/workspaces",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -719,8 +817,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/northflank/secret-groups",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/northflank/secret-groups",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -754,8 +855,11 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/teamcity/build-configs",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:integrationAuthId/teamcity/build-configs",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
|
@@ -2,7 +2,9 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { IntegrationsSchema } from "@app/db/schemas";
|
import { IntegrationsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
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 { removeTrailingSlash, shake } from "@app/lib/fn";
|
||||||
|
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -10,36 +12,51 @@ import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telem
|
|||||||
|
|
||||||
export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
|
description: "Create an integration to sync secrets.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
body: z.object({
|
body: z.object({
|
||||||
integrationAuthId: z.string().trim(),
|
integrationAuthId: z.string().trim().describe(INTEGRATION.CREATE.integrationAuthId),
|
||||||
app: z.string().trim().optional(),
|
app: z.string().trim().optional().describe(INTEGRATION.CREATE.app),
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean().describe(INTEGRATION.CREATE.isActive).default(true),
|
||||||
appId: z.string().trim().optional(),
|
appId: z.string().trim().optional().describe(INTEGRATION.CREATE.appId),
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
secretPath: z
|
||||||
sourceEnvironment: z.string().trim(),
|
.string()
|
||||||
targetEnvironment: z.string().trim().optional(),
|
.trim()
|
||||||
targetEnvironmentId: z.string().trim().optional(),
|
.default("/")
|
||||||
targetService: z.string().trim().optional(),
|
.transform(removeTrailingSlash)
|
||||||
targetServiceId: z.string().trim().optional(),
|
.describe(INTEGRATION.CREATE.secretPath),
|
||||||
owner: z.string().trim().optional(),
|
sourceEnvironment: z.string().trim().describe(INTEGRATION.CREATE.sourceEnvironment),
|
||||||
path: z.string().trim().optional(),
|
targetEnvironment: z.string().trim().optional().describe(INTEGRATION.CREATE.targetEnvironment),
|
||||||
region: z.string().trim().optional(),
|
targetEnvironmentId: z.string().trim().optional().describe(INTEGRATION.CREATE.targetEnvironmentId),
|
||||||
scope: z.string().trim().optional(),
|
targetService: z.string().trim().optional().describe(INTEGRATION.CREATE.targetService),
|
||||||
|
targetServiceId: z.string().trim().optional().describe(INTEGRATION.CREATE.targetServiceId),
|
||||||
|
owner: z.string().trim().optional().describe(INTEGRATION.CREATE.owner),
|
||||||
|
path: z.string().trim().optional().describe(INTEGRATION.CREATE.path),
|
||||||
|
region: z.string().trim().optional().describe(INTEGRATION.CREATE.region),
|
||||||
|
scope: z.string().trim().optional().describe(INTEGRATION.CREATE.scope),
|
||||||
metadata: z
|
metadata: z
|
||||||
.object({
|
.object({
|
||||||
secretPrefix: z.string().optional(),
|
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
|
||||||
secretSuffix: z.string().optional(),
|
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
|
||||||
initialSyncBehavior: z.string().optional(),
|
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
|
||||||
shouldAutoRedeploy: z.boolean().optional(),
|
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
|
||||||
secretGCPLabel: z
|
secretGCPLabel: z
|
||||||
.object({
|
.object({
|
||||||
labelName: z.string(),
|
labelName: z.string(),
|
||||||
labelValue: z.string()
|
labelValue: z.string()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
|
.describe(INTEGRATION.CREATE.metadata.secretGCPLabel)
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
}),
|
}),
|
||||||
@@ -49,7 +66,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { integration, integrationAuth } = await server.services.integration.createIntegration({
|
const { integration, integrationAuth } = await server.services.integration.createIntegration({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@@ -99,20 +116,34 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationId",
|
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
url: "/:integrationId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
|
description: "Update an integration by integration id",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
integrationId: z.string().trim()
|
integrationId: z.string().trim().describe(INTEGRATION.UPDATE.integrationId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
app: z.string().trim(),
|
app: z.string().trim().describe(INTEGRATION.UPDATE.app),
|
||||||
appId: z.string().trim(),
|
appId: z.string().trim().describe(INTEGRATION.UPDATE.appId),
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean().describe(INTEGRATION.UPDATE.isActive),
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
secretPath: z
|
||||||
targetEnvironment: z.string().trim(),
|
.string()
|
||||||
owner: z.string().trim(),
|
.trim()
|
||||||
environment: z.string().trim()
|
.default("/")
|
||||||
|
.transform(removeTrailingSlash)
|
||||||
|
.describe(INTEGRATION.UPDATE.secretPath),
|
||||||
|
targetEnvironment: z.string().trim().describe(INTEGRATION.UPDATE.targetEnvironment),
|
||||||
|
owner: z.string().trim().describe(INTEGRATION.UPDATE.owner),
|
||||||
|
environment: z.string().trim().describe(INTEGRATION.UPDATE.environment)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -120,7 +151,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const integration = await server.services.integration.updateIntegration({
|
const integration = await server.services.integration.updateIntegration({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@@ -135,11 +166,20 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationId",
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
url: "/:integrationId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
|
description: "Remove an integration using the integration object ID",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
integrationId: z.string().trim()
|
integrationId: z.string().trim().describe(INTEGRATION.DELETE.integrationId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -147,7 +187,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const integration = await server.services.integration.deleteIntegration({
|
const integration = await server.services.integration.deleteIntegration({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { UsersSchema } from "@app/db/schemas";
|
import { UsersSchema } from "@app/db/schemas";
|
||||||
|
import { inviteUserRateLimit } from "@app/server/config/rateLimiter";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
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) => {
|
export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/signup",
|
url: "/signup",
|
||||||
|
config: {
|
||||||
|
rateLimit: inviteUserRateLimit
|
||||||
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -52,6 +56,9 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/verify",
|
url: "/verify",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: inviteUserRateLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
email: z.string().trim().email(),
|
email: z.string().trim().email(),
|
||||||
|
@@ -1,6 +1,15 @@
|
|||||||
import { z } from "zod";
|
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 { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -8,6 +17,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/",
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -25,6 +37,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:organizationId",
|
url: "/:organizationId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -50,6 +65,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:organizationId/users",
|
url: "/:organizationId/users",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -87,6 +105,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/:organizationId",
|
url: "/:organizationId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -128,6 +149,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:organizationId/incidentContactOrg",
|
url: "/:organizationId/incidentContactOrg",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -151,6 +175,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/:organizationId/incidentContactOrg",
|
url: "/:organizationId/incidentContactOrg",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
body: z.object({ email: z.string().email().trim() }),
|
body: z.object({ email: z.string().email().trim() }),
|
||||||
@@ -176,6 +203,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/:organizationId/incidentContactOrg/:incidentContactId",
|
url: "/:organizationId/incidentContactOrg/:incidentContactId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim(), incidentContactId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim(), incidentContactId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -196,4 +226,41 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
return { incidentContactsOrg };
|
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 };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { BackupPrivateKeySchema, UsersSchema } from "@app/db/schemas";
|
import { BackupPrivateKeySchema, UsersSchema } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
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 { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { validateSignUpAuthorization } from "@app/services/auth/auth-fns";
|
import { validateSignUpAuthorization } from "@app/services/auth/auth-fns";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -12,7 +12,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/srp1",
|
url: "/srp1",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: passwordRateLimit
|
rateLimit: authRateLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -39,7 +39,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/change-password",
|
url: "/change-password",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: passwordRateLimit
|
rateLimit: authRateLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -78,7 +78,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/email/password-reset",
|
url: "/email/password-reset",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: passwordRateLimit
|
rateLimit: authRateLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -103,7 +103,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/email/password-reset-verify",
|
url: "/email/password-reset-verify",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: passwordRateLimit
|
rateLimit: authRateLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -133,7 +133,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/backup-private-key",
|
url: "/backup-private-key",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: passwordRateLimit
|
rateLimit: authRateLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
@@ -168,7 +168,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/backup-private-key",
|
url: "/backup-private-key",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: passwordRateLimit
|
rateLimit: authRateLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
@@ -190,6 +190,9 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/password-reset",
|
url: "/password-reset",
|
||||||
|
config: {
|
||||||
|
rateLimit: authRateLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
protectedKey: z.string().trim(),
|
protectedKey: z.string().trim(),
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user