mirror of
https://github.com/Infisical/infisical.git
synced 2025-08-24 20:43:19 +00:00
Compare commits
200 Commits
ssh-host-a
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
|
ed914d49ee | ||
|
46755f724c | ||
|
e12f4ad253 | ||
|
0faa8f4bb0 | ||
|
365b4b975e | ||
|
fbf634f7da | ||
|
1f3e7da3b7 | ||
|
81396f6b51 | ||
|
63279280fd | ||
|
f2d9593660 | ||
|
219964a242 | ||
|
240f558231 | ||
|
f3b3df1010 | ||
|
1fd6cd4787 | ||
|
a7d715ed08 | ||
|
a758503f40 | ||
|
550cb2b5ec | ||
|
75cb259c51 | ||
|
be2c5a9e57 | ||
|
a077a9d6f2 | ||
|
296493484f | ||
|
92bc9d48af | ||
|
a9c1c197f7 | ||
|
5bd7dd4d65 | ||
|
8e2cfe2c03 | ||
|
0bb107d61d | ||
|
fdbb930940 | ||
|
9e56790886 | ||
|
e08c5f265e | ||
|
e7a55d8a27 | ||
|
35b8adb0f6 | ||
|
d161be1170 | ||
|
aabf933756 | ||
|
5d44d58ff4 | ||
|
69ef7fdf3b | ||
|
ff294dab8d | ||
|
a01a9f3f77 | ||
|
c99440ba81 | ||
|
6d5a6f42e0 | ||
|
d0a642a63a | ||
|
cf84dde0fa | ||
|
0c027fdc43 | ||
|
727a6a7701 | ||
|
98bb5d7aa7 | ||
|
7f1f9e7fd0 | ||
|
5d366687a5 | ||
|
4720914839 | ||
|
98f742a807 | ||
|
66f1967f88 | ||
|
da6cf85c8d | ||
|
e8b6eb0573 | ||
|
03ad5c5db0 | ||
|
e6c4c27a87 | ||
|
2a28d74bde | ||
|
d4ac4f8d8f | ||
|
aedc6e16ad | ||
|
1ec7c67212 | ||
|
ff0ff622a6 | ||
|
511becabd8 | ||
|
f0229c5ecf | ||
|
8d711af23b | ||
|
7bd61d88fc | ||
|
929434d17f | ||
|
ba94b91974 | ||
|
b65f62fda8 | ||
|
c47d76a6c7 | ||
|
9138a9e71d | ||
|
8e4ad8baf8 | ||
|
9f158d5b3f | ||
|
0e1cb4ebb2 | ||
|
e959ed7fab | ||
|
4e4b1b689b | ||
|
8f07f43fbd | ||
|
023f5d1286 | ||
|
72b03d4bdf | ||
|
e870e35002 | ||
|
4544f621af | ||
|
ddb5098eda | ||
|
35749e8d12 | ||
|
ee2e2246da | ||
|
e30d400afa | ||
|
024ed0c0d8 | ||
|
e99e360339 | ||
|
85965184f8 | ||
|
a1bbd50c0b | ||
|
f9c936865a | ||
|
2be10b5f9d | ||
|
3b6e35e13c | ||
|
fcf984965e | ||
|
6bca854475 | ||
|
a69ce50da9 | ||
|
1b798bd5d5 | ||
|
bd3ebe75c9 | ||
|
0f2b8e4266 | ||
|
c4ae8f2987 | ||
|
b50a022d11 | ||
|
8a035c8d82 | ||
|
4fa7ba2ec7 | ||
|
03d7f9f786 | ||
|
1b3e8b0a1c | ||
|
6a26a11cbb | ||
|
d673c8d8e9 | ||
|
b39c7070b5 | ||
|
fa3dd03074 | ||
|
ee40ffd304 | ||
|
d3d76467ac | ||
|
58940f31e3 | ||
|
6d2175cf9f | ||
|
dbb0b28453 | ||
|
225862aed8 | ||
|
8d1bd6aabb | ||
|
740c650441 | ||
|
78ccb5acb7 | ||
|
e9aa8b317b | ||
|
7b42f666f9 | ||
|
8a0cfa34d2 | ||
|
ca9825c1fe | ||
|
1dfc9511c1 | ||
|
694ab35f53 | ||
|
f35cd2d6a6 | ||
|
b259428075 | ||
|
f54a10f626 | ||
|
63a3ce2dba | ||
|
9aabc3ced7 | ||
|
fe9ec6b030 | ||
|
632572f7c3 | ||
|
bef55043f7 | ||
|
0323d152da | ||
|
0a5f6274f5 | ||
|
11ee13676d | ||
|
e7783fe6cc | ||
|
a524690d01 | ||
|
c229d6888c | ||
|
b6566943c6 | ||
|
2e459c161d | ||
|
680f1a2230 | ||
|
68e21ba8ce | ||
|
1e9722474f | ||
|
f345801bd6 | ||
|
f460acf9b4 | ||
|
4160009913 | ||
|
d5065af7e9 | ||
|
68e88ddef8 | ||
|
a2909b8030 | ||
|
3de5fa066b | ||
|
8987938642 | ||
|
3f00359459 | ||
|
a5b5b90ca1 | ||
|
fd0a00023b | ||
|
dd112b3850 | ||
|
b329b5aa4b | ||
|
c01c58fdcb | ||
|
4bba207552 | ||
|
8563eb850b | ||
|
4225bf6e0e | ||
|
fab385fdd9 | ||
|
a204629bef | ||
|
50679ba29d | ||
|
f5fa57d6c5 | ||
|
6088ae09ab | ||
|
0de15bf70c | ||
|
b5d229a7c5 | ||
|
92084ccd47 | ||
|
e0dc2dd6d8 | ||
|
418ac20f91 | ||
|
b377d2a6b1 | ||
|
350272aa57 | ||
|
95489e1b0a | ||
|
56b3e7a76d | ||
|
9ea6eca560 | ||
|
33dea34061 | ||
|
da68073e86 | ||
|
7bd312a287 | ||
|
d61e6752d6 | ||
|
636aee2ea9 | ||
|
d5888f9de7 | ||
|
1590b528bf | ||
|
e30a05e3e8 | ||
|
ce7798c48b | ||
|
75f1ce7b86 | ||
|
6ce1c4e19e | ||
|
f08de1599d | ||
|
a80520e425 | ||
|
4aa3552060 | ||
|
40781949a6 | ||
|
7d4f223174 | ||
|
ef47d0056f | ||
|
ccd7b0062e | ||
|
2ee423174a | ||
|
649f7b560f | ||
|
7219ba3b46 | ||
|
6e65656360 | ||
|
e0491c2056 | ||
|
b8db15563a | ||
|
9982ade219 | ||
|
9032bbe514 | ||
|
c403ffa9f6 | ||
|
1184ea1b11 | ||
|
7d97a76ecc | ||
|
a889f92528 |
@@ -24,3 +24,5 @@ frontend/src/hooks/api/secretRotationsV2/types/index.ts:generic-api-key:65
|
|||||||
frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretRotationListView/SecretRotationItem.tsx:generic-api-key:26
|
frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretRotationListView/SecretRotationItem.tsx:generic-api-key:26
|
||||||
docs/documentation/platform/kms/overview.mdx:generic-api-key:281
|
docs/documentation/platform/kms/overview.mdx:generic-api-key:281
|
||||||
docs/documentation/platform/kms/overview.mdx:generic-api-key:344
|
docs/documentation/platform/kms/overview.mdx:generic-api-key:344
|
||||||
|
docs/cli/commands/user.mdx:generic-api-key:51
|
||||||
|
frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx:generic-api-key:76
|
1149
backend/package-lock.json
generated
1149
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -91,7 +91,6 @@
|
|||||||
"@types/lodash.isequal": "^4.5.8",
|
"@types/lodash.isequal": "^4.5.8",
|
||||||
"@types/node": "^20.17.30",
|
"@types/node": "^20.17.30",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
"@types/passport-github": "^1.1.12",
|
|
||||||
"@types/passport-google-oauth20": "^2.0.14",
|
"@types/passport-google-oauth20": "^2.0.14",
|
||||||
"@types/pg": "^8.10.9",
|
"@types/pg": "^8.10.9",
|
||||||
"@types/picomatch": "^2.3.3",
|
"@types/picomatch": "^2.3.3",
|
||||||
@@ -150,6 +149,7 @@
|
|||||||
"@infisical/quic": "^1.0.8",
|
"@infisical/quic": "^1.0.8",
|
||||||
"@node-saml/passport-saml": "^5.0.1",
|
"@node-saml/passport-saml": "^5.0.1",
|
||||||
"@octokit/auth-app": "^7.1.1",
|
"@octokit/auth-app": "^7.1.1",
|
||||||
|
"@octokit/plugin-paginate-graphql": "^5.2.4",
|
||||||
"@octokit/plugin-retry": "^5.0.5",
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
@@ -175,6 +175,7 @@
|
|||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"axios-retry": "^4.0.0",
|
"axios-retry": "^4.0.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
|
"botbuilder": "^4.23.2",
|
||||||
"bullmq": "^5.4.2",
|
"bullmq": "^5.4.2",
|
||||||
"cassandra-driver": "^4.7.2",
|
"cassandra-driver": "^4.7.2",
|
||||||
"connect-redis": "^7.1.1",
|
"connect-redis": "^7.1.1",
|
||||||
@@ -208,10 +209,10 @@
|
|||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
"oracledb": "^6.4.0",
|
"oracledb": "^6.4.0",
|
||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
"passport-github": "^1.1.0",
|
|
||||||
"passport-gitlab2": "^5.0.0",
|
"passport-gitlab2": "^5.0.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"passport-ldapauth": "^3.0.1",
|
"passport-ldapauth": "^3.0.1",
|
||||||
|
"passport-oauth2": "^1.8.0",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"pg-boss": "^10.1.5",
|
"pg-boss": "^10.1.5",
|
||||||
"pg-query-stream": "^4.5.3",
|
"pg-query-stream": "^4.5.3",
|
||||||
|
9
backend/src/@types/fastify.d.ts
vendored
9
backend/src/@types/fastify.d.ts
vendored
@@ -5,6 +5,7 @@ import { Redis } from "ioredis";
|
|||||||
import { TUsers } from "@app/db/schemas";
|
import { TUsers } from "@app/db/schemas";
|
||||||
import { TAccessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
import { TAccessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
||||||
import { TAccessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
|
import { TAccessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
|
||||||
|
import { TAssumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-service";
|
||||||
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 { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service";
|
import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service";
|
||||||
@@ -14,6 +15,7 @@ import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dy
|
|||||||
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
|
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
|
||||||
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
|
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
|
||||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||||
|
import { TGithubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
|
||||||
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
|
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
|
||||||
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
||||||
import { TIdentityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
|
import { TIdentityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
|
||||||
@@ -98,6 +100,7 @@ import { TUserServiceFactory } from "@app/services/user/user-service";
|
|||||||
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
||||||
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||||
import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||||
|
import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
|
||||||
|
|
||||||
declare module "@fastify/request-context" {
|
declare module "@fastify/request-context" {
|
||||||
interface RequestContextData {
|
interface RequestContextData {
|
||||||
@@ -109,12 +112,14 @@ declare module "@fastify/request-context" {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
identityPermissionMetadata?: Record<string, unknown>; // filled by permission service
|
identityPermissionMetadata?: Record<string, unknown>; // filled by permission service
|
||||||
|
assumedPrivilegeDetails?: { requesterId: string; actorId: string; actorType: ActorType; projectId: string };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "fastify" {
|
declare module "fastify" {
|
||||||
interface Session {
|
interface Session {
|
||||||
callbackPort: string;
|
callbackPort: string;
|
||||||
|
isAdminLogin: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FastifyRequest {
|
interface FastifyRequest {
|
||||||
@@ -138,6 +143,7 @@ declare module "fastify" {
|
|||||||
passportUser: {
|
passportUser: {
|
||||||
isUserCompleted: boolean;
|
isUserCompleted: boolean;
|
||||||
providerAuthToken: string;
|
providerAuthToken: string;
|
||||||
|
externalProviderAccessToken?: string;
|
||||||
};
|
};
|
||||||
kmipUser: {
|
kmipUser: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@@ -241,6 +247,9 @@ declare module "fastify" {
|
|||||||
kmipOperation: TKmipOperationServiceFactory;
|
kmipOperation: TKmipOperationServiceFactory;
|
||||||
gateway: TGatewayServiceFactory;
|
gateway: TGatewayServiceFactory;
|
||||||
secretRotationV2: TSecretRotationV2ServiceFactory;
|
secretRotationV2: TSecretRotationV2ServiceFactory;
|
||||||
|
microsoftTeams: TMicrosoftTeamsServiceFactory;
|
||||||
|
assumePrivileges: TAssumePrivilegeServiceFactory;
|
||||||
|
githubOrgSync: TGithubOrgSyncServiceFactory;
|
||||||
};
|
};
|
||||||
// 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
|
||||||
|
38
backend/src/@types/knex.d.ts
vendored
38
backend/src/@types/knex.d.ts
vendored
@@ -83,6 +83,9 @@ import {
|
|||||||
TGitAppOrg,
|
TGitAppOrg,
|
||||||
TGitAppOrgInsert,
|
TGitAppOrgInsert,
|
||||||
TGitAppOrgUpdate,
|
TGitAppOrgUpdate,
|
||||||
|
TGithubOrgSyncConfigs,
|
||||||
|
TGithubOrgSyncConfigsInsert,
|
||||||
|
TGithubOrgSyncConfigsUpdate,
|
||||||
TGroupProjectMembershipRoles,
|
TGroupProjectMembershipRoles,
|
||||||
TGroupProjectMembershipRolesInsert,
|
TGroupProjectMembershipRolesInsert,
|
||||||
TGroupProjectMembershipRolesUpdate,
|
TGroupProjectMembershipRolesUpdate,
|
||||||
@@ -423,6 +426,21 @@ import {
|
|||||||
TWorkflowIntegrationsInsert,
|
TWorkflowIntegrationsInsert,
|
||||||
TWorkflowIntegrationsUpdate
|
TWorkflowIntegrationsUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
TMicrosoftTeamsIntegrations,
|
||||||
|
TMicrosoftTeamsIntegrationsInsert,
|
||||||
|
TMicrosoftTeamsIntegrationsUpdate
|
||||||
|
} from "@app/db/schemas/microsoft-teams-integrations";
|
||||||
|
import {
|
||||||
|
TProjectMicrosoftTeamsConfigs,
|
||||||
|
TProjectMicrosoftTeamsConfigsInsert,
|
||||||
|
TProjectMicrosoftTeamsConfigsUpdate
|
||||||
|
} from "@app/db/schemas/project-microsoft-teams-configs";
|
||||||
|
import {
|
||||||
|
TSecretReminderRecipients,
|
||||||
|
TSecretReminderRecipientsInsert,
|
||||||
|
TSecretReminderRecipientsUpdate
|
||||||
|
} from "@app/db/schemas/secret-reminder-recipients";
|
||||||
|
|
||||||
declare module "knex" {
|
declare module "knex" {
|
||||||
namespace Knex {
|
namespace Knex {
|
||||||
@@ -994,5 +1012,25 @@ declare module "knex/types/tables" {
|
|||||||
TSecretRotationV2SecretMappingsInsert,
|
TSecretRotationV2SecretMappingsInsert,
|
||||||
TSecretRotationV2SecretMappingsUpdate
|
TSecretRotationV2SecretMappingsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.MicrosoftTeamsIntegrations]: KnexOriginal.CompositeTableType<
|
||||||
|
TMicrosoftTeamsIntegrations,
|
||||||
|
TMicrosoftTeamsIntegrationsInsert,
|
||||||
|
TMicrosoftTeamsIntegrationsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.ProjectMicrosoftTeamsConfigs]: KnexOriginal.CompositeTableType<
|
||||||
|
TProjectMicrosoftTeamsConfigs,
|
||||||
|
TProjectMicrosoftTeamsConfigsInsert,
|
||||||
|
TProjectMicrosoftTeamsConfigsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.SecretReminderRecipients]: KnexOriginal.CompositeTableType<
|
||||||
|
TSecretReminderRecipients,
|
||||||
|
TSecretReminderRecipientsInsert,
|
||||||
|
TSecretReminderRecipientsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.GithubOrgSyncConfig]: KnexOriginal.CompositeTableType<
|
||||||
|
TGithubOrgSyncConfigs,
|
||||||
|
TGithubOrgSyncConfigsInsert,
|
||||||
|
TGithubOrgSyncConfigsUpdate
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,34 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasSecretReminderRecipientsTable = await knex.schema.hasTable(TableName.SecretReminderRecipients);
|
||||||
|
|
||||||
|
if (!hasSecretReminderRecipientsTable) {
|
||||||
|
await knex.schema.createTable(TableName.SecretReminderRecipients, (table) => {
|
||||||
|
table.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
table.timestamps(true, true, true);
|
||||||
|
table.uuid("secretId").notNullable();
|
||||||
|
table.uuid("userId").notNullable();
|
||||||
|
table.string("projectId").notNullable();
|
||||||
|
|
||||||
|
// Based on userId rather than project membership ID so we can easily extend group support in the future if need be.
|
||||||
|
// This does however mean we need to manually clean up once a user is removed from a project.
|
||||||
|
table.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
table.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
|
||||||
|
table.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
|
||||||
|
table.index("secretId");
|
||||||
|
table.unique(["secretId", "userId"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasSecretReminderRecipientsTable = await knex.schema.hasTable(TableName.SecretReminderRecipients);
|
||||||
|
|
||||||
|
if (hasSecretReminderRecipientsTable) {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretReminderRecipients);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,130 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const superAdminHasEncryptedMicrosoftTeamsClientIdColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedMicrosoftTeamsAppId"
|
||||||
|
);
|
||||||
|
const superAdminHasEncryptedMicrosoftTeamsClientSecret = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedMicrosoftTeamsClientSecret"
|
||||||
|
);
|
||||||
|
const superAdminHasEncryptedMicrosoftTeamsBotId = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedMicrosoftTeamsBotId"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!superAdminHasEncryptedMicrosoftTeamsClientIdColumn ||
|
||||||
|
!superAdminHasEncryptedMicrosoftTeamsClientSecret ||
|
||||||
|
!superAdminHasEncryptedMicrosoftTeamsBotId
|
||||||
|
) {
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (table) => {
|
||||||
|
if (!superAdminHasEncryptedMicrosoftTeamsClientIdColumn) {
|
||||||
|
table.binary("encryptedMicrosoftTeamsAppId").nullable();
|
||||||
|
}
|
||||||
|
if (!superAdminHasEncryptedMicrosoftTeamsClientSecret) {
|
||||||
|
table.binary("encryptedMicrosoftTeamsClientSecret").nullable();
|
||||||
|
}
|
||||||
|
if (!superAdminHasEncryptedMicrosoftTeamsBotId) {
|
||||||
|
table.binary("encryptedMicrosoftTeamsBotId").nullable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.WorkflowIntegrations, "status"))) {
|
||||||
|
await knex.schema.alterTable(TableName.WorkflowIntegrations, (table) => {
|
||||||
|
table.enu("status", ["pending", "installed", "failed"]).notNullable().defaultTo("installed"); // defaults to installed so we can have backwards compatibility with existing workflow integrations
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.MicrosoftTeamsIntegrations))) {
|
||||||
|
await knex.schema.createTable(TableName.MicrosoftTeamsIntegrations, (table) => {
|
||||||
|
table.uuid("id", { primaryKey: true }).notNullable();
|
||||||
|
table.foreign("id").references("id").inTable(TableName.WorkflowIntegrations).onDelete("CASCADE"); // the ID itself is the workflow integration ID
|
||||||
|
|
||||||
|
table.string("internalTeamsAppId").nullable();
|
||||||
|
table.string("tenantId").notNullable();
|
||||||
|
table.binary("encryptedAccessToken").nullable();
|
||||||
|
table.binary("encryptedBotAccessToken").nullable();
|
||||||
|
|
||||||
|
table.timestamp("accessTokenExpiresAt").nullable();
|
||||||
|
table.timestamp("botAccessTokenExpiresAt").nullable();
|
||||||
|
|
||||||
|
table.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.MicrosoftTeamsIntegrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.ProjectMicrosoftTeamsConfigs))) {
|
||||||
|
await knex.schema.createTable(TableName.ProjectMicrosoftTeamsConfigs, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
tb.string("projectId").notNullable().unique();
|
||||||
|
tb.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
tb.uuid("microsoftTeamsIntegrationId").notNullable();
|
||||||
|
tb.foreign("microsoftTeamsIntegrationId")
|
||||||
|
.references("id")
|
||||||
|
.inTable(TableName.MicrosoftTeamsIntegrations)
|
||||||
|
.onDelete("CASCADE");
|
||||||
|
tb.boolean("isAccessRequestNotificationEnabled").notNullable().defaultTo(false);
|
||||||
|
tb.boolean("isSecretRequestNotificationEnabled").notNullable().defaultTo(false);
|
||||||
|
|
||||||
|
tb.jsonb("accessRequestChannels").notNullable(); // {teamId: string, channelIds: string[]}
|
||||||
|
tb.jsonb("secretRequestChannels").notNullable(); // {teamId: string, channelIds: string[]}
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.ProjectMicrosoftTeamsConfigs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasEncryptedMicrosoftTeamsClientIdColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedMicrosoftTeamsAppId"
|
||||||
|
);
|
||||||
|
const hasEncryptedMicrosoftTeamsClientSecret = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedMicrosoftTeamsClientSecret"
|
||||||
|
);
|
||||||
|
const hasEncryptedMicrosoftTeamsBotId = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedMicrosoftTeamsBotId"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
hasEncryptedMicrosoftTeamsClientIdColumn ||
|
||||||
|
hasEncryptedMicrosoftTeamsClientSecret ||
|
||||||
|
hasEncryptedMicrosoftTeamsBotId
|
||||||
|
) {
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (table) => {
|
||||||
|
if (hasEncryptedMicrosoftTeamsClientIdColumn) {
|
||||||
|
table.dropColumn("encryptedMicrosoftTeamsAppId");
|
||||||
|
}
|
||||||
|
if (hasEncryptedMicrosoftTeamsClientSecret) {
|
||||||
|
table.dropColumn("encryptedMicrosoftTeamsClientSecret");
|
||||||
|
}
|
||||||
|
if (hasEncryptedMicrosoftTeamsBotId) {
|
||||||
|
table.dropColumn("encryptedMicrosoftTeamsBotId");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (await knex.schema.hasColumn(TableName.WorkflowIntegrations, "status")) {
|
||||||
|
await knex.schema.alterTable(TableName.WorkflowIntegrations, (table) => {
|
||||||
|
table.dropColumn("status");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.ProjectMicrosoftTeamsConfigs)) {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.ProjectMicrosoftTeamsConfigs);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.ProjectMicrosoftTeamsConfigs);
|
||||||
|
}
|
||||||
|
if (await knex.schema.hasTable(TableName.MicrosoftTeamsIntegrations)) {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.MicrosoftTeamsIntegrations);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.MicrosoftTeamsIntegrations);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,26 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasTable = await knex.schema.hasTable(TableName.GithubOrgSyncConfig);
|
||||||
|
if (!hasTable) {
|
||||||
|
await knex.schema.createTable(TableName.GithubOrgSyncConfig, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("githubOrgName").notNullable();
|
||||||
|
t.boolean("isActive").defaultTo(false);
|
||||||
|
t.binary("encryptedGithubOrgAccessToken");
|
||||||
|
t.uuid("orgId").notNullable().unique();
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.GithubOrgSyncConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.GithubOrgSyncConfig);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.GithubOrgSyncConfig);
|
||||||
|
}
|
@@ -0,0 +1,27 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const tokenDuration = appCfg?.JWT_REFRESH_LIFETIME;
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.Organization, "userTokenExpiration"))) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (t) => {
|
||||||
|
t.string("userTokenExpiration");
|
||||||
|
});
|
||||||
|
if (tokenDuration) {
|
||||||
|
await knex(TableName.Organization).update({ userTokenExpiration: tokenDuration });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.Organization, "userTokenExpiration")) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (t) => {
|
||||||
|
t.dropColumn("userTokenExpiration");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -20,7 +20,7 @@ export const CertificatesSchema = z.object({
|
|||||||
notAfter: z.date(),
|
notAfter: z.date(),
|
||||||
revokedAt: z.date().nullable().optional(),
|
revokedAt: z.date().nullable().optional(),
|
||||||
revocationReason: z.number().nullable().optional(),
|
revocationReason: z.number().nullable().optional(),
|
||||||
altNames: z.string().default("").nullable().optional(),
|
altNames: z.string().nullable().optional(),
|
||||||
caCertId: z.string().uuid(),
|
caCertId: z.string().uuid(),
|
||||||
certificateTemplateId: z.string().uuid().nullable().optional(),
|
certificateTemplateId: z.string().uuid().nullable().optional(),
|
||||||
keyUsages: z.string().array().nullable().optional(),
|
keyUsages: z.string().array().nullable().optional(),
|
||||||
|
24
backend/src/db/schemas/github-org-sync-configs.ts
Normal file
24
backend/src/db/schemas/github-org-sync-configs.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const GithubOrgSyncConfigsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
githubOrgName: z.string(),
|
||||||
|
isActive: z.boolean().default(false).nullable().optional(),
|
||||||
|
encryptedGithubOrgAccessToken: zodBuffer.nullable().optional(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGithubOrgSyncConfigs = z.infer<typeof GithubOrgSyncConfigsSchema>;
|
||||||
|
export type TGithubOrgSyncConfigsInsert = Omit<z.input<typeof GithubOrgSyncConfigsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TGithubOrgSyncConfigsUpdate = Partial<Omit<z.input<typeof GithubOrgSyncConfigsSchema>, TImmutableDBKeys>>;
|
@@ -25,6 +25,7 @@ export * from "./external-kms";
|
|||||||
export * from "./gateways";
|
export * from "./gateways";
|
||||||
export * from "./git-app-install-sessions";
|
export * from "./git-app-install-sessions";
|
||||||
export * from "./git-app-org";
|
export * from "./git-app-org";
|
||||||
|
export * from "./github-org-sync-configs";
|
||||||
export * from "./group-project-membership-roles";
|
export * from "./group-project-membership-roles";
|
||||||
export * from "./group-project-memberships";
|
export * from "./group-project-memberships";
|
||||||
export * from "./groups";
|
export * from "./groups";
|
||||||
@@ -57,6 +58,7 @@ export * from "./kms-keys";
|
|||||||
export * from "./kms-root-config";
|
export * from "./kms-root-config";
|
||||||
export * from "./ldap-configs";
|
export * from "./ldap-configs";
|
||||||
export * from "./ldap-group-maps";
|
export * from "./ldap-group-maps";
|
||||||
|
export * from "./microsoft-teams-integrations";
|
||||||
export * from "./models";
|
export * from "./models";
|
||||||
export * from "./oidc-configs";
|
export * from "./oidc-configs";
|
||||||
export * from "./org-bots";
|
export * from "./org-bots";
|
||||||
|
@@ -13,7 +13,7 @@ export const KmipOrgServerCertificatesSchema = z.object({
|
|||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
commonName: z.string(),
|
commonName: z.string(),
|
||||||
altNames: z.string(),
|
altNames: z.string().nullable().optional(),
|
||||||
serialNumber: z.string(),
|
serialNumber: z.string(),
|
||||||
keyAlgorithm: z.string(),
|
keyAlgorithm: z.string(),
|
||||||
issuedAt: z.date(),
|
issuedAt: z.date(),
|
||||||
|
31
backend/src/db/schemas/microsoft-teams-integrations.ts
Normal file
31
backend/src/db/schemas/microsoft-teams-integrations.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 { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const MicrosoftTeamsIntegrationsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
internalTeamsAppId: z.string().nullable().optional(),
|
||||||
|
tenantId: z.string(),
|
||||||
|
encryptedAccessToken: zodBuffer.nullable().optional(),
|
||||||
|
encryptedBotAccessToken: zodBuffer.nullable().optional(),
|
||||||
|
accessTokenExpiresAt: z.date().nullable().optional(),
|
||||||
|
botAccessTokenExpiresAt: z.date().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TMicrosoftTeamsIntegrations = z.infer<typeof MicrosoftTeamsIntegrationsSchema>;
|
||||||
|
export type TMicrosoftTeamsIntegrationsInsert = Omit<
|
||||||
|
z.input<typeof MicrosoftTeamsIntegrationsSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TMicrosoftTeamsIntegrationsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof MicrosoftTeamsIntegrationsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
@@ -146,7 +146,11 @@ export enum TableName {
|
|||||||
KmipOrgServerCertificates = "kmip_org_server_certificates",
|
KmipOrgServerCertificates = "kmip_org_server_certificates",
|
||||||
KmipClientCertificates = "kmip_client_certificates",
|
KmipClientCertificates = "kmip_client_certificates",
|
||||||
SecretRotationV2 = "secret_rotations_v2",
|
SecretRotationV2 = "secret_rotations_v2",
|
||||||
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings"
|
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings",
|
||||||
|
MicrosoftTeamsIntegrations = "microsoft_teams_integrations",
|
||||||
|
ProjectMicrosoftTeamsConfigs = "project_microsoft_teams_configs",
|
||||||
|
SecretReminderRecipients = "secret_reminder_recipients",
|
||||||
|
GithubOrgSyncConfig = "github_org_sync_configs"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
||||||
|
@@ -30,9 +30,9 @@ export const OidcConfigsSchema = z.object({
|
|||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
lastUsed: z.date().nullable().optional(),
|
lastUsed: z.date().nullable().optional(),
|
||||||
|
manageGroupMemberships: z.boolean().default(false),
|
||||||
encryptedOidcClientId: zodBuffer,
|
encryptedOidcClientId: zodBuffer,
|
||||||
encryptedOidcClientSecret: zodBuffer,
|
encryptedOidcClientSecret: zodBuffer,
|
||||||
manageGroupMemberships: z.boolean().default(false),
|
|
||||||
jwtSignatureAlgorithm: z.string().default("RS256")
|
jwtSignatureAlgorithm: z.string().default("RS256")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -23,11 +23,13 @@ export const OrganizationsSchema = z.object({
|
|||||||
defaultMembershipRole: z.string().default("member"),
|
defaultMembershipRole: z.string().default("member"),
|
||||||
enforceMfa: z.boolean().default(false),
|
enforceMfa: z.boolean().default(false),
|
||||||
selectedMfaMethod: z.string().nullable().optional(),
|
selectedMfaMethod: z.string().nullable().optional(),
|
||||||
|
secretShareSendToAnyone: z.boolean().default(true).nullable().optional(),
|
||||||
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(),
|
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(),
|
||||||
shouldUseNewPrivilegeSystem: z.boolean().default(true),
|
shouldUseNewPrivilegeSystem: z.boolean().default(true),
|
||||||
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
|
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
|
||||||
privilegeUpgradeInitiatedAt: z.date().nullable().optional(),
|
privilegeUpgradeInitiatedAt: z.date().nullable().optional(),
|
||||||
bypassOrgAuthEnabled: z.boolean().default(false)
|
bypassOrgAuthEnabled: z.boolean().default(false),
|
||||||
|
userTokenExpiration: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
||||||
|
29
backend/src/db/schemas/project-microsoft-teams-configs.ts
Normal file
29
backend/src/db/schemas/project-microsoft-teams-configs.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const ProjectMicrosoftTeamsConfigsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
projectId: z.string(),
|
||||||
|
microsoftTeamsIntegrationId: z.string().uuid(),
|
||||||
|
isAccessRequestNotificationEnabled: z.boolean().default(false),
|
||||||
|
isSecretRequestNotificationEnabled: z.boolean().default(false),
|
||||||
|
accessRequestChannels: z.unknown(),
|
||||||
|
secretRequestChannels: z.unknown(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TProjectMicrosoftTeamsConfigs = z.infer<typeof ProjectMicrosoftTeamsConfigsSchema>;
|
||||||
|
export type TProjectMicrosoftTeamsConfigsInsert = Omit<
|
||||||
|
z.input<typeof ProjectMicrosoftTeamsConfigsSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TProjectMicrosoftTeamsConfigsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof ProjectMicrosoftTeamsConfigsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
23
backend/src/db/schemas/secret-reminder-recipients.ts
Normal file
23
backend/src/db/schemas/secret-reminder-recipients.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 SecretReminderRecipientsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
secretId: z.string().uuid(),
|
||||||
|
userId: z.string().uuid(),
|
||||||
|
projectId: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretReminderRecipients = z.infer<typeof SecretReminderRecipientsSchema>;
|
||||||
|
export type TSecretReminderRecipientsInsert = Omit<z.input<typeof SecretReminderRecipientsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TSecretReminderRecipientsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof SecretReminderRecipientsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
@@ -26,7 +26,10 @@ export const SuperAdminSchema = z.object({
|
|||||||
encryptedSlackClientSecret: zodBuffer.nullable().optional(),
|
encryptedSlackClientSecret: zodBuffer.nullable().optional(),
|
||||||
authConsentContent: z.string().nullable().optional(),
|
authConsentContent: z.string().nullable().optional(),
|
||||||
pageFrameContent: z.string().nullable().optional(),
|
pageFrameContent: z.string().nullable().optional(),
|
||||||
adminIdentityIds: z.string().array().nullable().optional()
|
adminIdentityIds: z.string().array().nullable().optional(),
|
||||||
|
encryptedMicrosoftTeamsAppId: zodBuffer.nullable().optional(),
|
||||||
|
encryptedMicrosoftTeamsClientSecret: zodBuffer.nullable().optional(),
|
||||||
|
encryptedMicrosoftTeamsBotId: zodBuffer.nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
||||||
|
@@ -14,7 +14,8 @@ export const WorkflowIntegrationsSchema = z.object({
|
|||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
description: z.string().nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
status: z.string().default("installed")
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TWorkflowIntegrations = z.infer<typeof WorkflowIntegrationsSchema>;
|
export type TWorkflowIntegrations = z.infer<typeof WorkflowIntegrationsSchema>;
|
||||||
|
124
backend/src/ee/routes/v1/assume-privilege-router.ts
Normal file
124
backend/src/ee/routes/v1/assume-privilege-router.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { requestContext } from "@fastify/request-context";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerAssumePrivilegeRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:projectId/assume-privileges",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
projectId: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
actorType: z.enum([ActorType.USER, ActorType.IDENTITY]),
|
||||||
|
actorId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req, res) => {
|
||||||
|
if (req.auth.authMode === AuthMode.JWT) {
|
||||||
|
const payload = await server.services.assumePrivileges.assumeProjectPrivileges({
|
||||||
|
targetActorType: req.body.actorType,
|
||||||
|
targetActorId: req.body.actorId,
|
||||||
|
projectId: req.params.projectId,
|
||||||
|
actorPermissionDetails: req.permission,
|
||||||
|
tokenVersionId: req.auth.tokenVersionId
|
||||||
|
});
|
||||||
|
|
||||||
|
const appCfg = getConfig();
|
||||||
|
void res.setCookie("infisical-project-assume-privileges", payload.assumePrivilegesToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED,
|
||||||
|
maxAge: 3600 // 1 hour in seconds
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_START,
|
||||||
|
metadata: {
|
||||||
|
projectId: req.params.projectId,
|
||||||
|
requesterEmail: req.auth.user.username,
|
||||||
|
requesterId: req.auth.user.id,
|
||||||
|
targetActorType: req.body.actorType,
|
||||||
|
targetActorId: req.body.actorId,
|
||||||
|
duration: "1hr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { message: "Successfully assumed role" };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({ message: "Invalid auth mode" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:projectId/assume-privileges",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
projectId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req, res) => {
|
||||||
|
const assumedPrivilegeDetails = requestContext.get("assumedPrivilegeDetails");
|
||||||
|
if (req.auth.authMode === AuthMode.JWT && assumedPrivilegeDetails) {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
void res.setCookie("infisical-project-assume-privileges", "", {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED,
|
||||||
|
expires: new Date(0)
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_END,
|
||||||
|
metadata: {
|
||||||
|
projectId: req.params.projectId,
|
||||||
|
requesterEmail: req.auth.user.username,
|
||||||
|
requesterId: req.auth.user.id,
|
||||||
|
targetActorId: assumedPrivilegeDetails.actorId,
|
||||||
|
targetActorType: assumedPrivilegeDetails.actorType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { message: "Successfully exited assumed role" };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({ message: "Invalid auth mode" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
129
backend/src/ee/routes/v1/github-org-sync-router.ts
Normal file
129
backend/src/ee/routes/v1/github-org-sync-router.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { GithubOrgSyncConfigsSchema } from "@app/db/schemas";
|
||||||
|
import { CharacterType, zodValidateCharacters } from "@app/lib/validator/validate-string";
|
||||||
|
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";
|
||||||
|
|
||||||
|
const SanitizedGithubOrgSyncSchema = GithubOrgSyncConfigsSchema.pick({
|
||||||
|
isActive: true,
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
orgId: true,
|
||||||
|
githubOrgName: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const githubOrgNameValidator = zodValidateCharacters([CharacterType.AlphaNumeric, CharacterType.Hyphen]);
|
||||||
|
export const registerGithubOrgSyncRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
githubOrgName: githubOrgNameValidator(z.string().trim(), "GitHub Org Name"),
|
||||||
|
githubOrgAccessToken: z.string().trim().max(1000).optional(),
|
||||||
|
isActive: z.boolean().default(false)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
githubOrgSyncConfig: SanitizedGithubOrgSyncSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const githubOrgSyncConfig = await server.services.githubOrgSync.createGithubOrgSync({
|
||||||
|
orgPermission: req.permission,
|
||||||
|
githubOrgName: req.body.githubOrgName,
|
||||||
|
githubOrgAccessToken: req.body.githubOrgAccessToken,
|
||||||
|
isActive: req.body.isActive
|
||||||
|
});
|
||||||
|
|
||||||
|
return { githubOrgSyncConfig };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "PATCH",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
githubOrgName: githubOrgNameValidator(z.string().trim(), "GitHub Org Name"),
|
||||||
|
githubOrgAccessToken: z.string().trim().max(1000),
|
||||||
|
isActive: z.boolean()
|
||||||
|
})
|
||||||
|
.partial(),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
githubOrgSyncConfig: SanitizedGithubOrgSyncSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const githubOrgSyncConfig = await server.services.githubOrgSync.updateGithubOrgSync({
|
||||||
|
orgPermission: req.permission,
|
||||||
|
githubOrgName: req.body.githubOrgName,
|
||||||
|
githubOrgAccessToken: req.body.githubOrgAccessToken,
|
||||||
|
isActive: req.body.isActive
|
||||||
|
});
|
||||||
|
|
||||||
|
return { githubOrgSyncConfig };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "DELETE",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
githubOrgSyncConfig: SanitizedGithubOrgSyncSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const githubOrgSyncConfig = await server.services.githubOrgSync.deleteGithubOrgSync({
|
||||||
|
orgPermission: req.permission
|
||||||
|
});
|
||||||
|
|
||||||
|
return { githubOrgSyncConfig };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
githubOrgSyncConfig: SanitizedGithubOrgSyncSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const githubOrgSyncConfig = await server.services.githubOrgSync.getGithubOrgSync({
|
||||||
|
orgPermission: req.permission
|
||||||
|
});
|
||||||
|
|
||||||
|
return { githubOrgSyncConfig };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -2,12 +2,14 @@ import { registerProjectTemplateRouter } from "@app/ee/routes/v1/project-templat
|
|||||||
|
|
||||||
import { registerAccessApprovalPolicyRouter } from "./access-approval-policy-router";
|
import { registerAccessApprovalPolicyRouter } from "./access-approval-policy-router";
|
||||||
import { registerAccessApprovalRequestRouter } from "./access-approval-request-router";
|
import { registerAccessApprovalRequestRouter } from "./access-approval-request-router";
|
||||||
|
import { registerAssumePrivilegeRouter } from "./assume-privilege-router";
|
||||||
import { registerAuditLogStreamRouter } from "./audit-log-stream-router";
|
import { registerAuditLogStreamRouter } from "./audit-log-stream-router";
|
||||||
import { registerCaCrlRouter } from "./certificate-authority-crl-router";
|
import { registerCaCrlRouter } from "./certificate-authority-crl-router";
|
||||||
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
||||||
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
||||||
import { registerExternalKmsRouter } from "./external-kms-router";
|
import { registerExternalKmsRouter } from "./external-kms-router";
|
||||||
import { registerGatewayRouter } from "./gateway-router";
|
import { registerGatewayRouter } from "./gateway-router";
|
||||||
|
import { registerGithubOrgSyncRouter } from "./github-org-sync-router";
|
||||||
import { registerGroupRouter } from "./group-router";
|
import { registerGroupRouter } from "./group-router";
|
||||||
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
|
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
|
||||||
import { registerKmipRouter } from "./kmip-router";
|
import { registerKmipRouter } from "./kmip-router";
|
||||||
@@ -45,6 +47,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
await projectRouter.register(registerProjectRoleRouter);
|
await projectRouter.register(registerProjectRoleRouter);
|
||||||
await projectRouter.register(registerProjectRouter);
|
await projectRouter.register(registerProjectRouter);
|
||||||
await projectRouter.register(registerTrustedIpRouter);
|
await projectRouter.register(registerTrustedIpRouter);
|
||||||
|
await projectRouter.register(registerAssumePrivilegeRouter);
|
||||||
},
|
},
|
||||||
{ prefix: "/workspace" }
|
{ prefix: "/workspace" }
|
||||||
);
|
);
|
||||||
@@ -70,6 +73,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await server.register(registerGatewayRouter, { prefix: "/gateways" });
|
await server.register(registerGatewayRouter, { prefix: "/gateways" });
|
||||||
|
await server.register(registerGithubOrgSyncRouter, { prefix: "/github-org-sync-config" });
|
||||||
|
|
||||||
await server.register(
|
await server.register(
|
||||||
async (pkiRouter) => {
|
async (pkiRouter) => {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { packRules } from "@casl/ability/extra";
|
import { packRules } from "@casl/ability/extra";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
|
||||||
import {
|
import {
|
||||||
backfillPermissionV1SchemaToV2Schema,
|
backfillPermissionV1SchemaToV2Schema,
|
||||||
ProjectPermissionV1Schema
|
ProjectPermissionV1Schema
|
||||||
@@ -245,13 +245,22 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
data: z.object({
|
data: z.object({
|
||||||
membership: ProjectMembershipsSchema.extend({
|
membership: z.object({
|
||||||
|
id: z.string(),
|
||||||
roles: z
|
roles: z
|
||||||
.object({
|
.object({
|
||||||
role: z.string()
|
role: z.string()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
}),
|
}),
|
||||||
|
assumedPrivilegeDetails: z
|
||||||
|
.object({
|
||||||
|
actorId: z.string(),
|
||||||
|
actorType: z.string(),
|
||||||
|
actorName: z.string(),
|
||||||
|
actorEmail: z.string().optional()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
permissions: z.any().array()
|
permissions: z.any().array()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -259,14 +268,20 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { permissions, membership } = await server.services.projectRole.getUserPermission(
|
const { permissions, membership, assumedPrivilegeDetails } = await server.services.projectRole.getUserPermission(
|
||||||
req.permission.id,
|
req.permission.id,
|
||||||
req.params.projectId,
|
req.params.projectId,
|
||||||
req.permission.authMethod,
|
req.permission.authMethod,
|
||||||
req.permission.orgId
|
req.permission.orgId
|
||||||
);
|
);
|
||||||
|
|
||||||
return { data: { permissions, membership } };
|
return {
|
||||||
|
data: {
|
||||||
|
permissions,
|
||||||
|
membership,
|
||||||
|
assumedPrivilegeDetails
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,19 @@
|
|||||||
|
import {
|
||||||
|
AzureClientSecretRotationGeneratedCredentialsSchema,
|
||||||
|
AzureClientSecretRotationSchema,
|
||||||
|
CreateAzureClientSecretRotationSchema,
|
||||||
|
UpdateAzureClientSecretRotationSchema
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
|
||||||
|
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
|
||||||
|
|
||||||
|
export const registerAzureClientSecretRotationRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSecretRotationEndpoints({
|
||||||
|
type: SecretRotation.AzureClientSecret,
|
||||||
|
server,
|
||||||
|
responseSchema: AzureClientSecretRotationSchema,
|
||||||
|
createSchema: CreateAzureClientSecretRotationSchema,
|
||||||
|
updateSchema: UpdateAzureClientSecretRotationSchema,
|
||||||
|
generatedCredentialsSchema: AzureClientSecretRotationGeneratedCredentialsSchema
|
||||||
|
});
|
@@ -2,6 +2,8 @@ import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotat
|
|||||||
|
|
||||||
import { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-rotation-router";
|
import { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-rotation-router";
|
||||||
import { registerAwsIamUserSecretRotationRouter } from "./aws-iam-user-secret-rotation-router";
|
import { registerAwsIamUserSecretRotationRouter } from "./aws-iam-user-secret-rotation-router";
|
||||||
|
import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-rotation-router";
|
||||||
|
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
|
||||||
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
||||||
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
||||||
|
|
||||||
@@ -14,5 +16,7 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
|
|||||||
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
||||||
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
||||||
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
||||||
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter
|
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
|
||||||
|
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,
|
||||||
|
[SecretRotation.LdapPassword]: registerLdapPasswordRotationRouter
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,19 @@
|
|||||||
|
import {
|
||||||
|
CreateLdapPasswordRotationSchema,
|
||||||
|
LdapPasswordRotationGeneratedCredentialsSchema,
|
||||||
|
LdapPasswordRotationSchema,
|
||||||
|
UpdateLdapPasswordRotationSchema
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
|
||||||
|
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
|
||||||
|
|
||||||
|
export const registerLdapPasswordRotationRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSecretRotationEndpoints({
|
||||||
|
type: SecretRotation.LdapPassword,
|
||||||
|
server,
|
||||||
|
responseSchema: LdapPasswordRotationSchema,
|
||||||
|
createSchema: CreateLdapPasswordRotationSchema,
|
||||||
|
updateSchema: UpdateLdapPasswordRotationSchema,
|
||||||
|
generatedCredentialsSchema: LdapPasswordRotationGeneratedCredentialsSchema
|
||||||
|
});
|
@@ -3,6 +3,8 @@ 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 { Auth0ClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
import { Auth0ClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
||||||
import { AwsIamUserSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret";
|
import { AwsIamUserSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret";
|
||||||
|
import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
||||||
|
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||||
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||||
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
||||||
@@ -15,7 +17,9 @@ const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
|||||||
PostgresCredentialsRotationListItemSchema,
|
PostgresCredentialsRotationListItemSchema,
|
||||||
MsSqlCredentialsRotationListItemSchema,
|
MsSqlCredentialsRotationListItemSchema,
|
||||||
Auth0ClientSecretRotationListItemSchema,
|
Auth0ClientSecretRotationListItemSchema,
|
||||||
AwsIamUserSecretRotationListItemSchema
|
AzureClientSecretRotationListItemSchema,
|
||||||
|
AwsIamUserSecretRotationListItemSchema,
|
||||||
|
LdapPasswordRotationListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
||||||
|
@@ -6,13 +6,15 @@ import { getConfig } from "@app/lib/config/env";
|
|||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
import { triggerWorkflowIntegrationNotification } from "@app/lib/workflow-integrations/trigger-notification";
|
||||||
|
import { TriggerFeature } from "@app/lib/workflow-integrations/types";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
|
||||||
|
import { TProjectMicrosoftTeamsConfigDALFactory } from "@app/services/microsoft-teams/project-microsoft-teams-config-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal";
|
import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal";
|
||||||
import { triggerSlackNotification } from "@app/services/slack/slack-fns";
|
|
||||||
import { SlackTriggerFeature } from "@app/services/slack/slack-types";
|
|
||||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
@@ -67,6 +69,8 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||||
|
microsoftTeamsService: Pick<TMicrosoftTeamsServiceFactory, "sendNotification">;
|
||||||
|
projectMicrosoftTeamsConfigDAL: Pick<TProjectMicrosoftTeamsConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>;
|
export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>;
|
||||||
@@ -84,6 +88,8 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
smtpService,
|
smtpService,
|
||||||
userDAL,
|
userDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
|
microsoftTeamsService,
|
||||||
|
projectMicrosoftTeamsConfigDAL,
|
||||||
projectSlackConfigDAL
|
projectSlackConfigDAL
|
||||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||||
const createAccessApprovalRequest = async ({
|
const createAccessApprovalRequest = async ({
|
||||||
@@ -219,24 +225,30 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
|
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
|
||||||
const approvalUrl = `${cfg.SITE_URL}/secret-manager/${project.id}/approval`;
|
const approvalUrl = `${cfg.SITE_URL}/secret-manager/${project.id}/approval`;
|
||||||
|
|
||||||
await triggerSlackNotification({
|
await triggerWorkflowIntegrationNotification({
|
||||||
projectId: project.id,
|
input: {
|
||||||
projectSlackConfigDAL,
|
notification: {
|
||||||
projectDAL,
|
type: TriggerFeature.ACCESS_REQUEST,
|
||||||
kmsService,
|
payload: {
|
||||||
notification: {
|
projectName: project.name,
|
||||||
type: SlackTriggerFeature.ACCESS_REQUEST,
|
requesterFullName,
|
||||||
payload: {
|
isTemporary,
|
||||||
projectName: project.name,
|
requesterEmail: requestedByUser.email as string,
|
||||||
requesterFullName,
|
secretPath,
|
||||||
isTemporary,
|
environment: envSlug,
|
||||||
requesterEmail: requestedByUser.email as string,
|
permissions: accessTypes,
|
||||||
secretPath,
|
approvalUrl,
|
||||||
environment: envSlug,
|
note
|
||||||
permissions: accessTypes,
|
}
|
||||||
approvalUrl,
|
},
|
||||||
note
|
projectId: project.id
|
||||||
}
|
},
|
||||||
|
dependencies: {
|
||||||
|
projectDAL,
|
||||||
|
projectSlackConfigDAL,
|
||||||
|
kmsService,
|
||||||
|
microsoftTeamsService,
|
||||||
|
projectMicrosoftTeamsConfigDAL
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -0,0 +1,101 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import {
|
||||||
|
ProjectPermissionIdentityActions,
|
||||||
|
ProjectPermissionMemberActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "../permission/project-permission";
|
||||||
|
import { TAssumeProjectPrivilegeDTO } from "./assume-privilege-types";
|
||||||
|
|
||||||
|
type TAssumePrivilegeServiceFactoryDep = {
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TAssumePrivilegeServiceFactory = ReturnType<typeof assumePrivilegeServiceFactory>;
|
||||||
|
|
||||||
|
export const assumePrivilegeServiceFactory = ({ projectDAL, permissionService }: TAssumePrivilegeServiceFactoryDep) => {
|
||||||
|
const assumeProjectPrivileges = async ({
|
||||||
|
targetActorType,
|
||||||
|
targetActorId,
|
||||||
|
projectId,
|
||||||
|
actorPermissionDetails,
|
||||||
|
tokenVersionId
|
||||||
|
}: TAssumeProjectPrivilegeDTO) => {
|
||||||
|
const project = await projectDAL.findById(projectId);
|
||||||
|
if (!project) throw new NotFoundError({ message: `Project with ID '${projectId}' not found` });
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor: actorPermissionDetails.type,
|
||||||
|
actorId: actorPermissionDetails.id,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod: actorPermissionDetails.authMethod,
|
||||||
|
actorOrgId: actorPermissionDetails.orgId,
|
||||||
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
|
if (targetActorType === ActorType.USER) {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionMemberActions.AssumePrivileges,
|
||||||
|
ProjectPermissionSub.Member
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionIdentityActions.AssumePrivileges,
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check entity is part of project
|
||||||
|
await permissionService.getProjectPermission({
|
||||||
|
actor: targetActorType,
|
||||||
|
actorId: targetActorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod: actorPermissionDetails.authMethod,
|
||||||
|
actorOrgId: actorPermissionDetails.orgId,
|
||||||
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const assumePrivilegesToken = jwt.sign(
|
||||||
|
{
|
||||||
|
tokenVersionId,
|
||||||
|
actorType: targetActorType,
|
||||||
|
actorId: targetActorId,
|
||||||
|
projectId,
|
||||||
|
requesterId: actorPermissionDetails.id
|
||||||
|
},
|
||||||
|
appCfg.AUTH_SECRET,
|
||||||
|
{ expiresIn: "1hr" }
|
||||||
|
);
|
||||||
|
|
||||||
|
return { actorType: targetActorType, actorId: targetActorId, projectId, assumePrivilegesToken };
|
||||||
|
};
|
||||||
|
|
||||||
|
const verifyAssumePrivilegeToken = (token: string, tokenVersionId: string) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const decodedToken = jwt.verify(token, appCfg.AUTH_SECRET) as {
|
||||||
|
tokenVersionId: string;
|
||||||
|
projectId: string;
|
||||||
|
requesterId: string;
|
||||||
|
actorType: ActorType;
|
||||||
|
actorId: string;
|
||||||
|
};
|
||||||
|
if (decodedToken.tokenVersionId !== tokenVersionId) {
|
||||||
|
throw new ForbiddenRequestError({ message: "Invalid token version" });
|
||||||
|
}
|
||||||
|
return decodedToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
assumeProjectPrivileges,
|
||||||
|
verifyAssumePrivilegeToken
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,10 @@
|
|||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export type TAssumeProjectPrivilegeDTO = {
|
||||||
|
targetActorType: ActorType.USER | ActorType.IDENTITY;
|
||||||
|
targetActorId: string;
|
||||||
|
projectId: string;
|
||||||
|
tokenVersionId: string;
|
||||||
|
actorPermissionDetails: OrgServiceActor;
|
||||||
|
};
|
@@ -29,6 +29,7 @@ import {
|
|||||||
TSecretSyncRaw,
|
TSecretSyncRaw,
|
||||||
TUpdateSecretSyncDTO
|
TUpdateSecretSyncDTO
|
||||||
} from "@app/services/secret-sync/secret-sync-types";
|
} from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types";
|
||||||
|
|
||||||
import { KmipPermission } from "../kmip/kmip-enum";
|
import { KmipPermission } from "../kmip/kmip-enum";
|
||||||
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
|
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
|
||||||
@@ -244,11 +245,14 @@ export enum EventType {
|
|||||||
GET_CERTIFICATE_TEMPLATE_EST_CONFIG = "get-certificate-template-est-config",
|
GET_CERTIFICATE_TEMPLATE_EST_CONFIG = "get-certificate-template-est-config",
|
||||||
ATTEMPT_CREATE_SLACK_INTEGRATION = "attempt-create-slack-integration",
|
ATTEMPT_CREATE_SLACK_INTEGRATION = "attempt-create-slack-integration",
|
||||||
ATTEMPT_REINSTALL_SLACK_INTEGRATION = "attempt-reinstall-slack-integration",
|
ATTEMPT_REINSTALL_SLACK_INTEGRATION = "attempt-reinstall-slack-integration",
|
||||||
|
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
|
||||||
|
UPDATE_PROJECT_SLACK_CONFIG = "update-project-slack-config",
|
||||||
GET_SLACK_INTEGRATION = "get-slack-integration",
|
GET_SLACK_INTEGRATION = "get-slack-integration",
|
||||||
UPDATE_SLACK_INTEGRATION = "update-slack-integration",
|
UPDATE_SLACK_INTEGRATION = "update-slack-integration",
|
||||||
DELETE_SLACK_INTEGRATION = "delete-slack-integration",
|
DELETE_SLACK_INTEGRATION = "delete-slack-integration",
|
||||||
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
|
GET_PROJECT_WORKFLOW_INTEGRATION_CONFIG = "get-project-workflow-integration-config",
|
||||||
UPDATE_PROJECT_SLACK_CONFIG = "update-project-slack-config",
|
UPDATE_PROJECT_WORKFLOW_INTEGRATION_CONFIG = "update-project-workflow-integration-config",
|
||||||
|
|
||||||
GET_PROJECT_SSH_CONFIG = "get-project-ssh-config",
|
GET_PROJECT_SSH_CONFIG = "get-project-ssh-config",
|
||||||
UPDATE_PROJECT_SSH_CONFIG = "update-project-ssh-config",
|
UPDATE_PROJECT_SSH_CONFIG = "update-project-ssh-config",
|
||||||
INTEGRATION_SYNCED = "integration-synced",
|
INTEGRATION_SYNCED = "integration-synced",
|
||||||
@@ -320,7 +324,18 @@ export enum EventType {
|
|||||||
DELETE_SECRET_ROTATION = "delete-secret-rotation",
|
DELETE_SECRET_ROTATION = "delete-secret-rotation",
|
||||||
SECRET_ROTATION_ROTATE_SECRETS = "secret-rotation-rotate-secrets",
|
SECRET_ROTATION_ROTATE_SECRETS = "secret-rotation-rotate-secrets",
|
||||||
|
|
||||||
PROJECT_ACCESS_REQUEST = "project-access-request"
|
PROJECT_ACCESS_REQUEST = "project-access-request",
|
||||||
|
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CREATE = "microsoft-teams-workflow-integration-create",
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_DELETE = "microsoft-teams-workflow-integration-delete",
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_UPDATE = "microsoft-teams-workflow-integration-update",
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CHECK_INSTALLATION_STATUS = "microsoft-teams-workflow-integration-check-installation-status",
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS = "microsoft-teams-workflow-integration-get-teams",
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET = "microsoft-teams-workflow-integration-get",
|
||||||
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST = "microsoft-teams-workflow-integration-list",
|
||||||
|
|
||||||
|
PROJECT_ASSUME_PRIVILEGE_SESSION_START = "project-assume-privileges-session-start",
|
||||||
|
PROJECT_ASSUME_PRIVILEGE_SESSION_END = "project-assume-privileges-session-end"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const filterableSecretEvents: EventType[] = [
|
export const filterableSecretEvents: EventType[] = [
|
||||||
@@ -1978,22 +1993,24 @@ interface GetSlackIntegration {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateProjectSlackConfig {
|
interface UpdateProjectWorkflowIntegrationConfig {
|
||||||
type: EventType.UPDATE_PROJECT_SLACK_CONFIG;
|
type: EventType.UPDATE_PROJECT_WORKFLOW_INTEGRATION_CONFIG;
|
||||||
metadata: {
|
metadata: {
|
||||||
id: string;
|
id: string;
|
||||||
slackIntegrationId: string;
|
integrationId: string;
|
||||||
|
integration: WorkflowIntegration;
|
||||||
isAccessRequestNotificationEnabled: boolean;
|
isAccessRequestNotificationEnabled: boolean;
|
||||||
accessRequestChannels: string;
|
accessRequestChannels?: string | { teamId: string; channelIds: string[] };
|
||||||
isSecretRequestNotificationEnabled: boolean;
|
isSecretRequestNotificationEnabled: boolean;
|
||||||
secretRequestChannels: string;
|
secretRequestChannels?: string | { teamId: string; channelIds: string[] };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetProjectSlackConfig {
|
interface GetProjectWorkflowIntegrationConfig {
|
||||||
type: EventType.GET_PROJECT_SLACK_CONFIG;
|
type: EventType.GET_PROJECT_WORKFLOW_INTEGRATION_CONFIG;
|
||||||
metadata: {
|
metadata: {
|
||||||
id: string;
|
id: string;
|
||||||
|
integration: WorkflowIntegration;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2454,6 +2471,29 @@ interface ProjectAccessRequestEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ProjectAssumePrivilegesEvent {
|
||||||
|
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_START;
|
||||||
|
metadata: {
|
||||||
|
projectId: string;
|
||||||
|
requesterId: string;
|
||||||
|
requesterEmail: string;
|
||||||
|
targetActorType: ActorType;
|
||||||
|
targetActorId: string;
|
||||||
|
duration: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProjectAssumePrivilegesExitEvent {
|
||||||
|
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_END;
|
||||||
|
metadata: {
|
||||||
|
projectId: string;
|
||||||
|
requesterId: string;
|
||||||
|
requesterEmail: string;
|
||||||
|
targetActorType: ActorType;
|
||||||
|
targetActorId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface SetupKmipEvent {
|
interface SetupKmipEvent {
|
||||||
type: EventType.SETUP_KMIP;
|
type: EventType.SETUP_KMIP;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -2536,6 +2576,66 @@ interface RotateSecretRotationEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationCreateEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CREATE;
|
||||||
|
metadata: {
|
||||||
|
tenantId: string;
|
||||||
|
slug: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationDeleteEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_DELETE;
|
||||||
|
metadata: {
|
||||||
|
tenantId: string;
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationCheckInstallationStatusEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CHECK_INSTALLATION_STATUS;
|
||||||
|
metadata: {
|
||||||
|
tenantId: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationGetTeamsEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS;
|
||||||
|
metadata: {
|
||||||
|
tenantId: string;
|
||||||
|
slug: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationGetEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET;
|
||||||
|
metadata: {
|
||||||
|
tenantId: string;
|
||||||
|
slug: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationListEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST;
|
||||||
|
metadata: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MicrosoftTeamsWorkflowIntegrationUpdateEvent {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_UPDATE;
|
||||||
|
metadata: {
|
||||||
|
tenantId: string;
|
||||||
|
slug: string;
|
||||||
|
id: string;
|
||||||
|
newSlug?: string;
|
||||||
|
newDescription?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
@@ -2698,8 +2798,8 @@ export type Event =
|
|||||||
| UpdateSlackIntegration
|
| UpdateSlackIntegration
|
||||||
| DeleteSlackIntegration
|
| DeleteSlackIntegration
|
||||||
| GetSlackIntegration
|
| GetSlackIntegration
|
||||||
| UpdateProjectSlackConfig
|
| UpdateProjectWorkflowIntegrationConfig
|
||||||
| GetProjectSlackConfig
|
| GetProjectWorkflowIntegrationConfig
|
||||||
| GetProjectSshConfig
|
| GetProjectSshConfig
|
||||||
| UpdateProjectSshConfig
|
| UpdateProjectSshConfig
|
||||||
| IntegrationSyncedEvent
|
| IntegrationSyncedEvent
|
||||||
@@ -2759,6 +2859,8 @@ export type Event =
|
|||||||
| KmipOperationLocateEvent
|
| KmipOperationLocateEvent
|
||||||
| KmipOperationRegisterEvent
|
| KmipOperationRegisterEvent
|
||||||
| ProjectAccessRequestEvent
|
| ProjectAccessRequestEvent
|
||||||
|
| ProjectAssumePrivilegesEvent
|
||||||
|
| ProjectAssumePrivilegesExitEvent
|
||||||
| CreateSecretRequestEvent
|
| CreateSecretRequestEvent
|
||||||
| SecretApprovalRequestReview
|
| SecretApprovalRequestReview
|
||||||
| GetSecretRotationsEvent
|
| GetSecretRotationsEvent
|
||||||
@@ -2767,4 +2869,11 @@ export type Event =
|
|||||||
| CreateSecretRotationEvent
|
| CreateSecretRotationEvent
|
||||||
| UpdateSecretRotationEvent
|
| UpdateSecretRotationEvent
|
||||||
| DeleteSecretRotationEvent
|
| DeleteSecretRotationEvent
|
||||||
| RotateSecretRotationEvent;
|
| RotateSecretRotationEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationCreateEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationDeleteEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationCheckInstallationStatusEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationGetTeamsEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationGetEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationListEvent
|
||||||
|
| MicrosoftTeamsWorkflowIntegrationUpdateEvent;
|
||||||
|
@@ -83,18 +83,26 @@ export const externalKmsServiceFactory = ({
|
|||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
|
|
||||||
// if missing kms key this generate a new kms key id and returns new provider input
|
try {
|
||||||
const newProviderInput = await externalKms.generateInputKmsKey();
|
// if missing kms key this generate a new kms key id and returns new provider input
|
||||||
sanitizedProviderInput = JSON.stringify(newProviderInput);
|
const newProviderInput = await externalKms.generateInputKmsKey();
|
||||||
|
sanitizedProviderInput = JSON.stringify(newProviderInput);
|
||||||
|
|
||||||
await externalKms.validateConnection();
|
await externalKms.validateConnection();
|
||||||
|
} finally {
|
||||||
|
await externalKms.cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KmsProviders.Gcp:
|
case KmsProviders.Gcp:
|
||||||
{
|
{
|
||||||
const externalKms = await GcpKmsProviderFactory({ inputs: provider.inputs });
|
const externalKms = await GcpKmsProviderFactory({ inputs: provider.inputs });
|
||||||
await externalKms.validateConnection();
|
try {
|
||||||
sanitizedProviderInput = JSON.stringify(provider.inputs);
|
await externalKms.validateConnection();
|
||||||
|
sanitizedProviderInput = JSON.stringify(provider.inputs);
|
||||||
|
} finally {
|
||||||
|
await externalKms.cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -186,8 +194,12 @@ export const externalKmsServiceFactory = ({
|
|||||||
);
|
);
|
||||||
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
|
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
|
||||||
const externalKms = await AwsKmsProviderFactory({ inputs: updatedProviderInput });
|
const externalKms = await AwsKmsProviderFactory({ inputs: updatedProviderInput });
|
||||||
await externalKms.validateConnection();
|
try {
|
||||||
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
await externalKms.validateConnection();
|
||||||
|
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
||||||
|
} finally {
|
||||||
|
await externalKms.cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KmsProviders.Gcp:
|
case KmsProviders.Gcp:
|
||||||
@@ -197,8 +209,12 @@ export const externalKmsServiceFactory = ({
|
|||||||
);
|
);
|
||||||
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
|
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
|
||||||
const externalKms = await GcpKmsProviderFactory({ inputs: updatedProviderInput });
|
const externalKms = await GcpKmsProviderFactory({ inputs: updatedProviderInput });
|
||||||
await externalKms.validateConnection();
|
try {
|
||||||
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
await externalKms.validateConnection();
|
||||||
|
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
||||||
|
} finally {
|
||||||
|
await externalKms.cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -368,7 +384,11 @@ export const externalKmsServiceFactory = ({
|
|||||||
|
|
||||||
const fetchGcpKeys = async ({ credential, gcpRegion }: Pick<TExternalKmsGcpSchema, "credential" | "gcpRegion">) => {
|
const fetchGcpKeys = async ({ credential, gcpRegion }: Pick<TExternalKmsGcpSchema, "credential" | "gcpRegion">) => {
|
||||||
const externalKms = await GcpKmsProviderFactory({ inputs: { credential, gcpRegion, keyName: "" } });
|
const externalKms = await GcpKmsProviderFactory({ inputs: { credential, gcpRegion, keyName: "" } });
|
||||||
return externalKms.getKeysList();
|
try {
|
||||||
|
return await externalKms.getKeysList();
|
||||||
|
} finally {
|
||||||
|
await externalKms.cleanup();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -102,10 +102,19 @@ export const AwsKmsProviderFactory = async ({ inputs }: AwsKmsProviderArgs): Pro
|
|||||||
return { data: Buffer.from(decryptionCommand.Plaintext) };
|
return { data: Buffer.from(decryptionCommand.Plaintext) };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cleanup = async () => {
|
||||||
|
try {
|
||||||
|
awsClient.destroy();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error("Failed to cleanup AWS KMS client", { cause: error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
generateInputKmsKey,
|
generateInputKmsKey,
|
||||||
validateConnection,
|
validateConnection,
|
||||||
encrypt,
|
encrypt,
|
||||||
decrypt
|
decrypt,
|
||||||
|
cleanup
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -45,6 +45,14 @@ export const GcpKmsProviderFactory = async ({ inputs }: GcpKmsProviderArgs): Pro
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cleanup = async () => {
|
||||||
|
try {
|
||||||
|
await gcpKmsClient.close();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error("Failed to cleanup GCP KMS client", { cause: error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Used when adding the KMS to fetch the list of keys in specified region
|
// Used when adding the KMS to fetch the list of keys in specified region
|
||||||
const getKeysList = async () => {
|
const getKeysList = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -108,6 +116,7 @@ export const GcpKmsProviderFactory = async ({ inputs }: GcpKmsProviderArgs): Pro
|
|||||||
validateConnection,
|
validateConnection,
|
||||||
getKeysList,
|
getKeysList,
|
||||||
encrypt,
|
encrypt,
|
||||||
decrypt
|
decrypt,
|
||||||
|
cleanup
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -98,4 +98,5 @@ export type TExternalKmsProviderFns = {
|
|||||||
validateConnection: () => Promise<boolean>;
|
validateConnection: () => Promise<boolean>;
|
||||||
encrypt: (data: Buffer) => Promise<{ encryptedBlob: Buffer }>;
|
encrypt: (data: Buffer) => Promise<{ encryptedBlob: Buffer }>;
|
||||||
decrypt: (encryptedBlob: Buffer) => Promise<{ data: Buffer }>;
|
decrypt: (encryptedBlob: Buffer) => Promise<{ data: Buffer }>;
|
||||||
|
cleanup: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TGithubOrgSyncDALFactory = ReturnType<typeof githubOrgSyncDALFactory>;
|
||||||
|
|
||||||
|
export const githubOrgSyncDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.GithubOrgSyncConfig);
|
||||||
|
return orm;
|
||||||
|
};
|
@@ -0,0 +1,354 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { Octokit } from "@octokit/core";
|
||||||
|
import { paginateGraphQL } from "@octokit/plugin-paginate-graphql";
|
||||||
|
import { Octokit as OctokitRest } from "@octokit/rest";
|
||||||
|
|
||||||
|
import { OrgMembershipRole } from "@app/db/schemas";
|
||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { groupBy } from "@app/lib/fn";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
|
||||||
|
import { TGroupDALFactory } from "../group/group-dal";
|
||||||
|
import { TUserGroupMembershipDALFactory } from "../group/user-group-membership-dal";
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { TGithubOrgSyncDALFactory } from "./github-org-sync-dal";
|
||||||
|
import { TCreateGithubOrgSyncDTO, TDeleteGithubOrgSyncDTO, TUpdateGithubOrgSyncDTO } from "./github-org-sync-types";
|
||||||
|
|
||||||
|
const OctokitWithPlugin = Octokit.plugin(paginateGraphQL);
|
||||||
|
|
||||||
|
type TGithubOrgSyncServiceFactoryDep = {
|
||||||
|
githubOrgSyncDAL: TGithubOrgSyncDALFactory;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
userGroupMembershipDAL: Pick<
|
||||||
|
TUserGroupMembershipDALFactory,
|
||||||
|
"findGroupMembershipsByUserIdInOrg" | "insertMany" | "delete"
|
||||||
|
>;
|
||||||
|
groupDAL: Pick<TGroupDALFactory, "insertMany" | "transaction" | "find">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGithubOrgSyncServiceFactory = ReturnType<typeof githubOrgSyncServiceFactory>;
|
||||||
|
|
||||||
|
export const githubOrgSyncServiceFactory = ({
|
||||||
|
githubOrgSyncDAL,
|
||||||
|
permissionService,
|
||||||
|
kmsService,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
groupDAL,
|
||||||
|
licenseService
|
||||||
|
}: TGithubOrgSyncServiceFactoryDep) => {
|
||||||
|
const createGithubOrgSync = async ({
|
||||||
|
githubOrgName,
|
||||||
|
orgPermission,
|
||||||
|
githubOrgAccessToken,
|
||||||
|
isActive
|
||||||
|
}: TCreateGithubOrgSyncDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
orgPermission.type,
|
||||||
|
orgPermission.id,
|
||||||
|
orgPermission.orgId,
|
||||||
|
orgPermission.authMethod,
|
||||||
|
orgPermission.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
const plan = await licenseService.getPlan(orgPermission.orgId);
|
||||||
|
if (!plan.githubOrgSync) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to create github organization team sync due to plan restriction. Upgrade plan to create github organization sync."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingConfig = await githubOrgSyncDAL.findOne({ orgId: orgPermission.orgId });
|
||||||
|
if (existingConfig)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Organization ${orgPermission.orgId} already has GitHub Organization sync config.`
|
||||||
|
});
|
||||||
|
|
||||||
|
const octokit = new OctokitRest({
|
||||||
|
auth: githubOrgAccessToken,
|
||||||
|
request: {
|
||||||
|
signal: AbortSignal.timeout(5000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const { data } = await octokit.rest.orgs.get({
|
||||||
|
org: githubOrgName
|
||||||
|
});
|
||||||
|
if (data.login.toLowerCase() !== githubOrgName.toLowerCase())
|
||||||
|
throw new BadRequestError({ message: "Invalid GitHub organisation" });
|
||||||
|
|
||||||
|
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: orgPermission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = await githubOrgSyncDAL.create({
|
||||||
|
orgId: orgPermission.orgId,
|
||||||
|
githubOrgName,
|
||||||
|
isActive,
|
||||||
|
encryptedGithubOrgAccessToken: githubOrgAccessToken
|
||||||
|
? encryptor({ plainText: Buffer.from(githubOrgAccessToken) }).cipherTextBlob
|
||||||
|
: null
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateGithubOrgSync = async ({
|
||||||
|
githubOrgName,
|
||||||
|
orgPermission,
|
||||||
|
githubOrgAccessToken,
|
||||||
|
isActive
|
||||||
|
}: TUpdateGithubOrgSyncDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
orgPermission.type,
|
||||||
|
orgPermission.id,
|
||||||
|
orgPermission.orgId,
|
||||||
|
orgPermission.authMethod,
|
||||||
|
orgPermission.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
const plan = await licenseService.getPlan(orgPermission.orgId);
|
||||||
|
if (!plan.githubOrgSync) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to update github organization team sync due to plan restriction. Upgrade plan to update github organization sync."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingConfig = await githubOrgSyncDAL.findOne({ orgId: orgPermission.orgId });
|
||||||
|
if (!existingConfig)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Organization ${orgPermission.orgId} GitHub organization sync config missing.`
|
||||||
|
});
|
||||||
|
|
||||||
|
const { encryptor, decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: orgPermission.orgId
|
||||||
|
});
|
||||||
|
const newData = {
|
||||||
|
githubOrgName: githubOrgName || existingConfig.githubOrgName,
|
||||||
|
githubOrgAccessToken:
|
||||||
|
githubOrgAccessToken ||
|
||||||
|
(existingConfig.encryptedGithubOrgAccessToken
|
||||||
|
? decryptor({ cipherTextBlob: existingConfig.encryptedGithubOrgAccessToken }).toString()
|
||||||
|
: null)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (githubOrgName || githubOrgAccessToken) {
|
||||||
|
const octokit = new OctokitRest({
|
||||||
|
auth: newData.githubOrgAccessToken,
|
||||||
|
request: {
|
||||||
|
signal: AbortSignal.timeout(5000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const { data } = await octokit.rest.orgs.get({
|
||||||
|
org: newData.githubOrgName
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.login.toLowerCase() !== newData.githubOrgName.toLowerCase())
|
||||||
|
throw new BadRequestError({ message: "Invalid GitHub organisation" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = await githubOrgSyncDAL.updateById(existingConfig.id, {
|
||||||
|
orgId: orgPermission.orgId,
|
||||||
|
githubOrgName: newData.githubOrgName,
|
||||||
|
isActive,
|
||||||
|
encryptedGithubOrgAccessToken: newData.githubOrgAccessToken
|
||||||
|
? encryptor({ plainText: Buffer.from(newData.githubOrgAccessToken) }).cipherTextBlob
|
||||||
|
: null
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteGithubOrgSync = async ({ orgPermission }: TDeleteGithubOrgSyncDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
orgPermission.type,
|
||||||
|
orgPermission.id,
|
||||||
|
orgPermission.orgId,
|
||||||
|
orgPermission.authMethod,
|
||||||
|
orgPermission.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(orgPermission.orgId);
|
||||||
|
if (!plan.githubOrgSync) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to delete github organization team sync due to plan restriction. Upgrade plan to delete github organization sync."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingConfig = await githubOrgSyncDAL.findOne({ orgId: orgPermission.orgId });
|
||||||
|
if (!existingConfig)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Organization ${orgPermission.orgId} GitHub organization sync config missing.`
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = await githubOrgSyncDAL.deleteById(existingConfig.id);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGithubOrgSync = async ({ orgPermission }: TDeleteGithubOrgSyncDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
orgPermission.type,
|
||||||
|
orgPermission.id,
|
||||||
|
orgPermission.orgId,
|
||||||
|
orgPermission.authMethod,
|
||||||
|
orgPermission.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
|
||||||
|
const existingConfig = await githubOrgSyncDAL.findOne({ orgId: orgPermission.orgId });
|
||||||
|
if (!existingConfig)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Organization ${orgPermission.orgId} GitHub organization sync config missing.`
|
||||||
|
});
|
||||||
|
|
||||||
|
return existingConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncUserGroups = async (orgId: string, userId: string, accessToken: string) => {
|
||||||
|
const config = await githubOrgSyncDAL.findOne({ orgId });
|
||||||
|
if (!config || !config?.isActive) return;
|
||||||
|
|
||||||
|
const infisicalUserGroups = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(userId, orgId);
|
||||||
|
const infisicalUserGroupSet = new Set(infisicalUserGroups.map((el) => el.groupName));
|
||||||
|
|
||||||
|
const octoRest = new OctokitRest({
|
||||||
|
auth: accessToken,
|
||||||
|
request: {
|
||||||
|
signal: AbortSignal.timeout(5000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const { data: userOrgMembershipDetails } = await octoRest.rest.orgs
|
||||||
|
.getMembershipForAuthenticatedUser({
|
||||||
|
org: config.githubOrgName
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error(err, "User not part of GitHub synced organization");
|
||||||
|
throw new BadRequestError({ message: "User not part of GitHub synced organization" });
|
||||||
|
});
|
||||||
|
const username = userOrgMembershipDetails?.user?.login;
|
||||||
|
if (!username) throw new BadRequestError({ message: "User not part of GitHub synced organization" });
|
||||||
|
|
||||||
|
const octokit = new OctokitWithPlugin({
|
||||||
|
auth: accessToken,
|
||||||
|
request: {
|
||||||
|
signal: AbortSignal.timeout(5000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = await octokit.graphql
|
||||||
|
.paginate<{
|
||||||
|
organization: { teams: { totalCount: number; edges: { node: { name: string; description: string } }[] } };
|
||||||
|
}>(
|
||||||
|
`
|
||||||
|
query orgTeams($cursor: String,$org: String!, $username: String!){
|
||||||
|
organization(login: $org) {
|
||||||
|
teams(first: 100, userLogins: [$username], after: $cursor) {
|
||||||
|
totalCount
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
name
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
hasNextPage
|
||||||
|
endCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
org: config.githubOrgName,
|
||||||
|
username
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
if ((err as Error)?.message?.includes("Although you appear to have the correct authorization credential")) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Please check your organization have approved Infisical Oauth application. For more info: https://infisical.com/docs/documentation/platform/github-org-sync#troubleshooting"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new BadRequestError({ message: (err as Error)?.message });
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
organization: { teams }
|
||||||
|
} = data;
|
||||||
|
const githubUserTeams = teams?.edges?.map((el) => el.node.name.toLowerCase()) || [];
|
||||||
|
const githubUserTeamSet = new Set(githubUserTeams);
|
||||||
|
const githubUserTeamOnInfisical = await groupDAL.find({ orgId, $in: { name: githubUserTeams } });
|
||||||
|
const githubUserTeamOnInfisicalGroupByName = groupBy(githubUserTeamOnInfisical, (i) => i.name);
|
||||||
|
|
||||||
|
const newTeams = githubUserTeams.filter(
|
||||||
|
(el) => !infisicalUserGroupSet.has(el) && !Object.hasOwn(githubUserTeamOnInfisicalGroupByName, el)
|
||||||
|
);
|
||||||
|
const updateTeams = githubUserTeams.filter(
|
||||||
|
(el) => !infisicalUserGroupSet.has(el) && Object.hasOwn(githubUserTeamOnInfisicalGroupByName, el)
|
||||||
|
);
|
||||||
|
const removeFromTeams = infisicalUserGroups.filter((el) => !githubUserTeamSet.has(el.groupName));
|
||||||
|
|
||||||
|
if (newTeams.length || updateTeams.length || removeFromTeams.length) {
|
||||||
|
await groupDAL.transaction(async (tx) => {
|
||||||
|
if (newTeams.length) {
|
||||||
|
const newGroups = await groupDAL.insertMany(
|
||||||
|
newTeams.map((newGroupName) => ({
|
||||||
|
name: newGroupName,
|
||||||
|
role: OrgMembershipRole.Member,
|
||||||
|
slug: newGroupName,
|
||||||
|
orgId
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
await userGroupMembershipDAL.insertMany(
|
||||||
|
newGroups.map((el) => ({
|
||||||
|
groupId: el.id,
|
||||||
|
userId
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateTeams.length) {
|
||||||
|
await userGroupMembershipDAL.insertMany(
|
||||||
|
updateTeams.map((el) => ({
|
||||||
|
groupId: githubUserTeamOnInfisicalGroupByName[el][0].id,
|
||||||
|
userId
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeFromTeams.length) {
|
||||||
|
await userGroupMembershipDAL.delete(
|
||||||
|
{ userId, $in: { groupId: removeFromTeams.map((el) => el.groupId) } },
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createGithubOrgSync,
|
||||||
|
updateGithubOrgSync,
|
||||||
|
deleteGithubOrgSync,
|
||||||
|
getGithubOrgSync,
|
||||||
|
syncUserGroups
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,23 @@
|
|||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
|
export interface TCreateGithubOrgSyncDTO {
|
||||||
|
orgPermission: OrgServiceActor;
|
||||||
|
githubOrgName: string;
|
||||||
|
githubOrgAccessToken?: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TUpdateGithubOrgSyncDTO {
|
||||||
|
orgPermission: OrgServiceActor;
|
||||||
|
githubOrgName?: string;
|
||||||
|
githubOrgAccessToken?: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TDeleteGithubOrgSyncDTO {
|
||||||
|
orgPermission: OrgServiceActor;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TGetGithubOrgSyncDTO {
|
||||||
|
orgPermission: OrgServiceActor;
|
||||||
|
}
|
@@ -22,6 +22,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
pitRecovery: false,
|
pitRecovery: false,
|
||||||
ipAllowlisting: false,
|
ipAllowlisting: false,
|
||||||
rbac: false,
|
rbac: false,
|
||||||
|
githubOrgSync: false,
|
||||||
customRateLimits: false,
|
customRateLimits: false,
|
||||||
customAlerts: false,
|
customAlerts: false,
|
||||||
secretAccessInsights: false,
|
secretAccessInsights: false,
|
||||||
|
@@ -45,6 +45,7 @@ export type TFeatureSet = {
|
|||||||
auditLogsRetentionDays: 0;
|
auditLogsRetentionDays: 0;
|
||||||
auditLogStreams: false;
|
auditLogStreams: false;
|
||||||
auditLogStreamLimit: 3;
|
auditLogStreamLimit: 3;
|
||||||
|
githubOrgSync: false;
|
||||||
samlSSO: false;
|
samlSSO: false;
|
||||||
hsm: false;
|
hsm: false;
|
||||||
oidcSSO: false;
|
oidcSSO: false;
|
||||||
|
@@ -685,10 +685,16 @@ export const oidcConfigServiceFactory = ({
|
|||||||
id_token_signed_response_alg: oidcCfg.jwtSignatureAlgorithm
|
id_token_signed_response_alg: oidcCfg.jwtSignatureAlgorithm
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if the OIDC provider supports PKCE
|
||||||
|
const codeChallengeMethods = client.issuer.metadata.code_challenge_methods_supported;
|
||||||
|
const supportsPKCE = Array.isArray(codeChallengeMethods) && codeChallengeMethods.includes("S256");
|
||||||
|
|
||||||
const strategy = new OpenIdStrategy(
|
const strategy = new OpenIdStrategy(
|
||||||
{
|
{
|
||||||
client,
|
client,
|
||||||
passReqToCallback: true
|
passReqToCallback: true,
|
||||||
|
usePKCE: supportsPKCE,
|
||||||
|
params: supportsPKCE ? { code_challenge_method: "S256" } : undefined
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(_req: any, tokenSet: TokenSet, cb: any) => {
|
(_req: any, tokenSet: TokenSet, cb: any) => {
|
||||||
|
@@ -8,7 +8,8 @@ export enum OIDCConfigurationType {
|
|||||||
export enum OIDCJWTSignatureAlgorithm {
|
export enum OIDCJWTSignatureAlgorithm {
|
||||||
RS256 = "RS256",
|
RS256 = "RS256",
|
||||||
HS256 = "HS256",
|
HS256 = "HS256",
|
||||||
RS512 = "RS512"
|
RS512 = "RS512",
|
||||||
|
EDDSA = "EdDSA"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TOidcLoginDTO = {
|
export type TOidcLoginDTO = {
|
||||||
|
@@ -74,6 +74,7 @@ export enum OrgPermissionSubjects {
|
|||||||
IncidentAccount = "incident-contact",
|
IncidentAccount = "incident-contact",
|
||||||
Sso = "sso",
|
Sso = "sso",
|
||||||
Scim = "scim",
|
Scim = "scim",
|
||||||
|
GithubOrgSync = "github-org-sync",
|
||||||
Ldap = "ldap",
|
Ldap = "ldap",
|
||||||
Groups = "groups",
|
Groups = "groups",
|
||||||
Billing = "billing",
|
Billing = "billing",
|
||||||
@@ -101,6 +102,7 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.GithubOrgSync]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
||||||
| [OrgPermissionGroupActions, OrgPermissionSubjects.Groups]
|
| [OrgPermissionGroupActions, OrgPermissionSubjects.Groups]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||||
@@ -165,6 +167,10 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
|
|||||||
subject: z.literal(OrgPermissionSubjects.Scim).describe("The entity this permission pertains to."),
|
subject: z.literal(OrgPermissionSubjects.Scim).describe("The entity this permission pertains to."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
}),
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.GithubOrgSync).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(OrgPermissionSubjects.Ldap).describe("The entity this permission pertains to."),
|
subject: z.literal(OrgPermissionSubjects.Ldap).describe("The entity this permission pertains to."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
@@ -273,6 +279,11 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
|
||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
|
||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
||||||
|
@@ -551,13 +551,26 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getProjectPermission = async <T extends ActorType>({
|
const getProjectPermission = async <T extends ActorType>({
|
||||||
actor,
|
actor: inputActor,
|
||||||
actorId,
|
actorId: inputActorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType
|
actionProjectType
|
||||||
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
|
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
|
||||||
|
let actor = inputActor;
|
||||||
|
let actorId = inputActorId;
|
||||||
|
const assumedPrivilegeDetailsCtx = requestContext.get("assumedPrivilegeDetails");
|
||||||
|
if (
|
||||||
|
assumedPrivilegeDetailsCtx &&
|
||||||
|
actor === ActorType.USER &&
|
||||||
|
actorId === assumedPrivilegeDetailsCtx.requesterId &&
|
||||||
|
projectId === assumedPrivilegeDetailsCtx.projectId
|
||||||
|
) {
|
||||||
|
actor = assumedPrivilegeDetailsCtx.actorType;
|
||||||
|
actorId = assumedPrivilegeDetailsCtx.actorId;
|
||||||
|
}
|
||||||
|
|
||||||
switch (actor) {
|
switch (actor) {
|
||||||
case ActorType.USER:
|
case ActorType.USER:
|
||||||
return getUserProjectPermission({
|
return getUserProjectPermission({
|
||||||
|
@@ -50,7 +50,8 @@ export enum ProjectPermissionIdentityActions {
|
|||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
GrantPrivileges = "grant-privileges"
|
GrantPrivileges = "grant-privileges",
|
||||||
|
AssumePrivileges = "assume-privileges"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionMemberActions {
|
export enum ProjectPermissionMemberActions {
|
||||||
@@ -58,7 +59,8 @@ export enum ProjectPermissionMemberActions {
|
|||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
GrantPrivileges = "grant-privileges"
|
GrantPrivileges = "grant-privileges",
|
||||||
|
AssumePrivileges = "assume-privileges"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionGroupActions {
|
export enum ProjectPermissionGroupActions {
|
||||||
@@ -714,7 +716,8 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionMemberActions.Edit,
|
ProjectPermissionMemberActions.Edit,
|
||||||
ProjectPermissionMemberActions.Delete,
|
ProjectPermissionMemberActions.Delete,
|
||||||
ProjectPermissionMemberActions.Read,
|
ProjectPermissionMemberActions.Read,
|
||||||
ProjectPermissionMemberActions.GrantPrivileges
|
ProjectPermissionMemberActions.GrantPrivileges,
|
||||||
|
ProjectPermissionMemberActions.AssumePrivileges
|
||||||
],
|
],
|
||||||
ProjectPermissionSub.Member
|
ProjectPermissionSub.Member
|
||||||
);
|
);
|
||||||
@@ -736,7 +739,8 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionIdentityActions.Edit,
|
ProjectPermissionIdentityActions.Edit,
|
||||||
ProjectPermissionIdentityActions.Delete,
|
ProjectPermissionIdentityActions.Delete,
|
||||||
ProjectPermissionIdentityActions.Read,
|
ProjectPermissionIdentityActions.Read,
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges
|
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||||
|
ProjectPermissionIdentityActions.AssumePrivileges
|
||||||
],
|
],
|
||||||
ProjectPermissionSub.Identity
|
ProjectPermissionSub.Identity
|
||||||
);
|
);
|
||||||
|
@@ -17,9 +17,13 @@ import { groupBy, pick, unique } from "@app/lib/fn";
|
|||||||
import { setKnexStringValue } from "@app/lib/knex";
|
import { setKnexStringValue } from "@app/lib/knex";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { EnforcementLevel } from "@app/lib/types";
|
import { EnforcementLevel } from "@app/lib/types";
|
||||||
|
import { triggerWorkflowIntegrationNotification } from "@app/lib/workflow-integrations/trigger-notification";
|
||||||
|
import { TriggerFeature } from "@app/lib/workflow-integrations/types";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
|
||||||
|
import { TProjectMicrosoftTeamsConfigDALFactory } from "@app/services/microsoft-teams/project-microsoft-teams-config-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
@@ -52,8 +56,6 @@ import {
|
|||||||
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
||||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||||
import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal";
|
import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal";
|
||||||
import { triggerSlackNotification } from "@app/services/slack/slack-fns";
|
|
||||||
import { SlackTriggerFeature } from "@app/services/slack/slack-types";
|
|
||||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
@@ -126,6 +128,8 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
secretApprovalPolicyDAL: Pick<TSecretApprovalPolicyDALFactory, "findById">;
|
secretApprovalPolicyDAL: Pick<TSecretApprovalPolicyDALFactory, "findById">;
|
||||||
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
projectMicrosoftTeamsConfigDAL: Pick<TProjectMicrosoftTeamsConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||||
|
microsoftTeamsService: Pick<TMicrosoftTeamsServiceFactory, "sendNotification">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
||||||
@@ -155,7 +159,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretVersionTagV2BridgeDAL,
|
secretVersionTagV2BridgeDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
projectSlackConfigDAL,
|
projectSlackConfigDAL,
|
||||||
resourceMetadataDAL
|
resourceMetadataDAL,
|
||||||
|
projectMicrosoftTeamsConfigDAL,
|
||||||
|
microsoftTeamsService
|
||||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
@@ -1171,21 +1177,28 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||||
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
||||||
await triggerSlackNotification({
|
|
||||||
projectId,
|
await triggerWorkflowIntegrationNotification({
|
||||||
projectDAL,
|
input: {
|
||||||
kmsService,
|
projectId,
|
||||||
projectSlackConfigDAL,
|
notification: {
|
||||||
notification: {
|
type: TriggerFeature.SECRET_APPROVAL,
|
||||||
type: SlackTriggerFeature.SECRET_APPROVAL,
|
payload: {
|
||||||
payload: {
|
userEmail: user.email as string,
|
||||||
userEmail: user.email as string,
|
environment: env.name,
|
||||||
environment: env.name,
|
secretPath,
|
||||||
secretPath,
|
projectId,
|
||||||
projectId,
|
requestId: secretApprovalRequest.id,
|
||||||
requestId: secretApprovalRequest.id,
|
secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretName) ?? []))]
|
||||||
secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretName) ?? []))]
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
projectDAL,
|
||||||
|
projectSlackConfigDAL,
|
||||||
|
kmsService,
|
||||||
|
projectMicrosoftTeamsConfigDAL,
|
||||||
|
microsoftTeamsService
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1503,21 +1516,28 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
||||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||||
await triggerSlackNotification({
|
|
||||||
projectId,
|
await triggerWorkflowIntegrationNotification({
|
||||||
projectDAL,
|
input: {
|
||||||
kmsService,
|
projectId,
|
||||||
projectSlackConfigDAL,
|
notification: {
|
||||||
notification: {
|
type: TriggerFeature.SECRET_APPROVAL,
|
||||||
type: SlackTriggerFeature.SECRET_APPROVAL,
|
payload: {
|
||||||
payload: {
|
userEmail: user.email as string,
|
||||||
userEmail: user.email as string,
|
environment: env.name,
|
||||||
environment: env.name,
|
secretPath,
|
||||||
secretPath,
|
projectId,
|
||||||
projectId,
|
requestId: secretApprovalRequest.id,
|
||||||
requestId: secretApprovalRequest.id,
|
secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretKey) ?? []))]
|
||||||
secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretKey) ?? []))]
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
projectDAL,
|
||||||
|
kmsService,
|
||||||
|
projectSlackConfigDAL,
|
||||||
|
microsoftTeamsService,
|
||||||
|
projectMicrosoftTeamsConfigDAL
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -33,6 +33,7 @@ export type TApprovalCreateSecretV2Bridge = {
|
|||||||
secretComment?: string;
|
secretComment?: string;
|
||||||
reminderNote?: string | null;
|
reminderNote?: string | null;
|
||||||
reminderRepeatDays?: number | null;
|
reminderRepeatDays?: number | null;
|
||||||
|
secretReminderRecipients?: string[] | null;
|
||||||
skipMultilineEncoding?: boolean;
|
skipMultilineEncoding?: boolean;
|
||||||
metadata?: Record<string, string>;
|
metadata?: Record<string, string>;
|
||||||
secretMetadata?: ResourceMetadataDTO;
|
secretMetadata?: ResourceMetadataDTO;
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
export const AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
|
||||||
|
name: "Azure Client Secret",
|
||||||
|
type: SecretRotation.AzureClientSecret,
|
||||||
|
connection: AppConnection.AzureClientSecrets,
|
||||||
|
template: {
|
||||||
|
secretsMapping: {
|
||||||
|
clientId: "AZURE_CLIENT_ID",
|
||||||
|
clientSecret: "AZURE_CLIENT_SECRET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@@ -0,0 +1,202 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AzureAddPasswordResponse,
|
||||||
|
TAzureClientSecretRotationGeneratedCredentials,
|
||||||
|
TAzureClientSecretRotationWithConnection
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/azure-client-secret/azure-client-secret-rotation-types";
|
||||||
|
import {
|
||||||
|
TRotationFactory,
|
||||||
|
TRotationFactoryGetSecretsPayload,
|
||||||
|
TRotationFactoryIssueCredentials,
|
||||||
|
TRotationFactoryRevokeCredentials,
|
||||||
|
TRotationFactoryRotateCredentials
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-client-secrets";
|
||||||
|
|
||||||
|
const GRAPH_API_BASE = "https://graph.microsoft.com/v1.0";
|
||||||
|
|
||||||
|
type AzureErrorResponse = { error: { message: string } };
|
||||||
|
|
||||||
|
const sleep = async () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const azureClientSecretRotationFactory: TRotationFactory<
|
||||||
|
TAzureClientSecretRotationWithConnection,
|
||||||
|
TAzureClientSecretRotationGeneratedCredentials
|
||||||
|
> = (secretRotation, appConnectionDAL, kmsService) => {
|
||||||
|
const {
|
||||||
|
connection,
|
||||||
|
parameters: { objectId, clientId: clientIdParam },
|
||||||
|
secretsMapping
|
||||||
|
} = secretRotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new client secret for the Azure app.
|
||||||
|
*/
|
||||||
|
const $rotateClientSecret = async () => {
|
||||||
|
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
|
||||||
|
const endpoint = `${GRAPH_API_BASE}/applications/${objectId}/addPassword`;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const formattedDate = `${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(
|
||||||
|
2,
|
||||||
|
"0"
|
||||||
|
)}-${now.getFullYear()}`;
|
||||||
|
|
||||||
|
const endDateTime = new Date();
|
||||||
|
endDateTime.setFullYear(now.getFullYear() + 5);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await request.post<AzureAddPasswordResponse>(
|
||||||
|
endpoint,
|
||||||
|
{
|
||||||
|
passwordCredential: {
|
||||||
|
displayName: `Infisical Rotated Secret (${formattedDate})`,
|
||||||
|
endDateTime: endDateTime.toISOString()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!data?.secretText || !data?.keyId) {
|
||||||
|
throw new Error("Invalid response from Azure: missing secretText or keyId.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
clientSecret: data.secretText,
|
||||||
|
keyId: data.keyId,
|
||||||
|
clientId: clientIdParam
|
||||||
|
};
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
let message;
|
||||||
|
if (
|
||||||
|
error.response?.data &&
|
||||||
|
typeof error.response.data === "object" &&
|
||||||
|
"error" in error.response.data &&
|
||||||
|
typeof (error.response.data as AzureErrorResponse).error.message === "string"
|
||||||
|
) {
|
||||||
|
message = (error.response.data as AzureErrorResponse).error.message;
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to add client secret to Azure app ${objectId}: ${
|
||||||
|
message || error.message || "Unknown error"
|
||||||
|
}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Unable to validate connection: verify credentials"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revokes a client secret from the Azure app using its keyId.
|
||||||
|
*/
|
||||||
|
const revokeCredential = async (keyId: string) => {
|
||||||
|
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
|
||||||
|
const endpoint = `${GRAPH_API_BASE}/applications/${objectId}/removePassword`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await request.post(
|
||||||
|
endpoint,
|
||||||
|
{ keyId },
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
let message;
|
||||||
|
if (
|
||||||
|
error.response?.data &&
|
||||||
|
typeof error.response.data === "object" &&
|
||||||
|
"error" in error.response.data &&
|
||||||
|
typeof (error.response.data as AzureErrorResponse).error.message === "string"
|
||||||
|
) {
|
||||||
|
message = (error.response.data as AzureErrorResponse).error.message;
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to remove client secret with keyId ${keyId} from app ${objectId}: ${
|
||||||
|
message || error.message || "Unknown error"
|
||||||
|
}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Unable to validate connection: verify credentials"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issues a new set of credentials.
|
||||||
|
*/
|
||||||
|
const issueCredentials: TRotationFactoryIssueCredentials<TAzureClientSecretRotationGeneratedCredentials> = async (
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
|
const credentials = await $rotateClientSecret();
|
||||||
|
return callback(credentials);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revokes a list of credentials.
|
||||||
|
*/
|
||||||
|
const revokeCredentials: TRotationFactoryRevokeCredentials<TAzureClientSecretRotationGeneratedCredentials> = async (
|
||||||
|
credentials,
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
|
if (!credentials?.length) return callback();
|
||||||
|
|
||||||
|
for (const { keyId } of credentials) {
|
||||||
|
await revokeCredential(keyId);
|
||||||
|
await sleep();
|
||||||
|
}
|
||||||
|
return callback();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates credentials by issuing new ones and revoking the old.
|
||||||
|
*/
|
||||||
|
const rotateCredentials: TRotationFactoryRotateCredentials<TAzureClientSecretRotationGeneratedCredentials> = async (
|
||||||
|
oldCredentials,
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
|
const newCredentials = await $rotateClientSecret();
|
||||||
|
if (oldCredentials?.keyId) {
|
||||||
|
await revokeCredential(oldCredentials.keyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(newCredentials);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the generated credentials into the secret payload format.
|
||||||
|
*/
|
||||||
|
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TAzureClientSecretRotationGeneratedCredentials> = ({
|
||||||
|
clientSecret
|
||||||
|
}) => [
|
||||||
|
{ key: secretsMapping.clientSecret, value: clientSecret },
|
||||||
|
{ key: secretsMapping.clientId, value: clientIdParam }
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
issueCredentials,
|
||||||
|
revokeCredentials,
|
||||||
|
rotateCredentials,
|
||||||
|
getSecretsPayload
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,74 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
import {
|
||||||
|
BaseCreateSecretRotationSchema,
|
||||||
|
BaseSecretRotationSchema,
|
||||||
|
BaseUpdateSecretRotationSchema
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas";
|
||||||
|
import { SecretRotations } from "@app/lib/api-docs";
|
||||||
|
import { SecretNameSchema } from "@app/server/lib/schemas";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
export const AzureClientSecretRotationGeneratedCredentialsSchema = z
|
||||||
|
.object({
|
||||||
|
clientId: z.string(),
|
||||||
|
clientSecret: z.string(),
|
||||||
|
keyId: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.max(2);
|
||||||
|
|
||||||
|
const AzureClientSecretRotationParametersSchema = z.object({
|
||||||
|
objectId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Object ID Required")
|
||||||
|
.describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.objectId),
|
||||||
|
appName: z.string().trim().describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.appName).optional(),
|
||||||
|
clientId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Client ID Required")
|
||||||
|
.describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.clientId)
|
||||||
|
});
|
||||||
|
|
||||||
|
const AzureClientSecretRotationSecretsMappingSchema = z.object({
|
||||||
|
clientId: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AZURE_CLIENT_SECRET.clientId),
|
||||||
|
clientSecret: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AZURE_CLIENT_SECRET.clientSecret)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureClientSecretRotationTemplateSchema = z.object({
|
||||||
|
secretsMapping: z.object({
|
||||||
|
clientId: z.string(),
|
||||||
|
clientSecret: z.string()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureClientSecretRotationSchema = BaseSecretRotationSchema(SecretRotation.AzureClientSecret).extend({
|
||||||
|
type: z.literal(SecretRotation.AzureClientSecret),
|
||||||
|
parameters: AzureClientSecretRotationParametersSchema,
|
||||||
|
secretsMapping: AzureClientSecretRotationSecretsMappingSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateAzureClientSecretRotationSchema = BaseCreateSecretRotationSchema(
|
||||||
|
SecretRotation.AzureClientSecret
|
||||||
|
).extend({
|
||||||
|
parameters: AzureClientSecretRotationParametersSchema,
|
||||||
|
secretsMapping: AzureClientSecretRotationSecretsMappingSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateAzureClientSecretRotationSchema = BaseUpdateSecretRotationSchema(
|
||||||
|
SecretRotation.AzureClientSecret
|
||||||
|
).extend({
|
||||||
|
parameters: AzureClientSecretRotationParametersSchema.optional(),
|
||||||
|
secretsMapping: AzureClientSecretRotationSecretsMappingSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureClientSecretRotationListItemSchema = z.object({
|
||||||
|
name: z.literal("Azure Client Secret"),
|
||||||
|
connection: z.literal(AppConnection.AzureClientSecrets),
|
||||||
|
type: z.literal(SecretRotation.AzureClientSecret),
|
||||||
|
template: AzureClientSecretRotationTemplateSchema
|
||||||
|
});
|
@@ -0,0 +1,41 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TAzureClientSecretsConnection } from "@app/services/app-connection/azure-client-secrets";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AzureClientSecretRotationGeneratedCredentialsSchema,
|
||||||
|
AzureClientSecretRotationListItemSchema,
|
||||||
|
AzureClientSecretRotationSchema,
|
||||||
|
CreateAzureClientSecretRotationSchema
|
||||||
|
} from "./azure-client-secret-rotation-schemas";
|
||||||
|
|
||||||
|
export type TAzureClientSecretRotation = z.infer<typeof AzureClientSecretRotationSchema>;
|
||||||
|
|
||||||
|
export type TAzureClientSecretRotationInput = z.infer<typeof CreateAzureClientSecretRotationSchema>;
|
||||||
|
|
||||||
|
export type TAzureClientSecretRotationListItem = z.infer<typeof AzureClientSecretRotationListItemSchema>;
|
||||||
|
|
||||||
|
export type TAzureClientSecretRotationWithConnection = TAzureClientSecretRotation & {
|
||||||
|
connection: TAzureClientSecretsConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TAzureClientSecretRotationGeneratedCredentials = z.infer<
|
||||||
|
typeof AzureClientSecretRotationGeneratedCredentialsSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface TAzureClientSecretRotationParameters {
|
||||||
|
appId: string;
|
||||||
|
keyId?: string;
|
||||||
|
displayName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TAzureClientSecretRotationSecretsMapping {
|
||||||
|
appId: string;
|
||||||
|
clientSecret: string;
|
||||||
|
keyId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AzureAddPasswordResponse {
|
||||||
|
secretText: string;
|
||||||
|
keyId: string;
|
||||||
|
}
|
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./azure-client-secret-rotation-constants";
|
||||||
|
export * from "./azure-client-secret-rotation-schemas";
|
||||||
|
export * from "./azure-client-secret-rotation-types";
|
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./ldap-password-rotation-constants";
|
||||||
|
export * from "./ldap-password-rotation-schemas";
|
||||||
|
export * from "./ldap-password-rotation-types";
|
@@ -0,0 +1,15 @@
|
|||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
export const LDAP_PASSWORD_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
|
||||||
|
name: "LDAP Password",
|
||||||
|
type: SecretRotation.LdapPassword,
|
||||||
|
connection: AppConnection.LDAP,
|
||||||
|
template: {
|
||||||
|
secretsMapping: {
|
||||||
|
dn: "LDAP_DN",
|
||||||
|
password: "LDAP_PASSWORD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@@ -0,0 +1,181 @@
|
|||||||
|
import ldap from "ldapjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
TRotationFactory,
|
||||||
|
TRotationFactoryGetSecretsPayload,
|
||||||
|
TRotationFactoryIssueCredentials,
|
||||||
|
TRotationFactoryRevokeCredentials,
|
||||||
|
TRotationFactoryRotateCredentials
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { encryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns";
|
||||||
|
import { getLdapConnectionClient, LdapProvider, TLdapConnection } from "@app/services/app-connection/ldap";
|
||||||
|
|
||||||
|
import { generatePassword } from "../shared/utils";
|
||||||
|
import {
|
||||||
|
TLdapPasswordRotationGeneratedCredentials,
|
||||||
|
TLdapPasswordRotationWithConnection
|
||||||
|
} from "./ldap-password-rotation-types";
|
||||||
|
|
||||||
|
const getEncodedPassword = (password: string) => Buffer.from(`"${password}"`, "utf16le");
|
||||||
|
|
||||||
|
export const ldapPasswordRotationFactory: TRotationFactory<
|
||||||
|
TLdapPasswordRotationWithConnection,
|
||||||
|
TLdapPasswordRotationGeneratedCredentials
|
||||||
|
> = (secretRotation, appConnectionDAL, kmsService) => {
|
||||||
|
const {
|
||||||
|
connection,
|
||||||
|
parameters: { dn, passwordRequirements },
|
||||||
|
secretsMapping
|
||||||
|
} = secretRotation;
|
||||||
|
|
||||||
|
const $verifyCredentials = async (credentials: Pick<TLdapConnection["credentials"], "dn" | "password">) => {
|
||||||
|
try {
|
||||||
|
const client = await getLdapConnectionClient({ ...connection.credentials, ...credentials });
|
||||||
|
|
||||||
|
client.unbind();
|
||||||
|
client.destroy();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to verify credentials - ${(error as Error).message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const $rotatePassword = async () => {
|
||||||
|
const { credentials, orgId } = connection;
|
||||||
|
|
||||||
|
if (!credentials.url.startsWith("ldaps")) throw new Error("Password Rotation requires an LDAPS connection");
|
||||||
|
|
||||||
|
const client = await getLdapConnectionClient(credentials);
|
||||||
|
const isPersonalRotation = credentials.dn === dn;
|
||||||
|
|
||||||
|
const password = generatePassword(passwordRequirements);
|
||||||
|
|
||||||
|
let changes: ldap.Change[] | ldap.Change;
|
||||||
|
|
||||||
|
switch (credentials.provider) {
|
||||||
|
case LdapProvider.ActiveDirectory:
|
||||||
|
{
|
||||||
|
const encodedPassword = getEncodedPassword(password);
|
||||||
|
|
||||||
|
// service account vs personal password rotation require different changes
|
||||||
|
if (isPersonalRotation) {
|
||||||
|
const currentEncodedPassword = getEncodedPassword(credentials.password);
|
||||||
|
|
||||||
|
changes = [
|
||||||
|
new ldap.Change({
|
||||||
|
operation: "delete",
|
||||||
|
modification: {
|
||||||
|
type: "unicodePwd",
|
||||||
|
values: [currentEncodedPassword]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new ldap.Change({
|
||||||
|
operation: "add",
|
||||||
|
modification: {
|
||||||
|
type: "unicodePwd",
|
||||||
|
values: [encodedPassword]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
changes = new ldap.Change({
|
||||||
|
operation: "replace",
|
||||||
|
modification: {
|
||||||
|
type: "unicodePwd",
|
||||||
|
values: [encodedPassword]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unhandled provider: ${credentials.provider as LdapProvider}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
client.modify(dn, changes, (err) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error(err, "LDAP Password Rotation Failed");
|
||||||
|
reject(new Error(`Provider Modify Error: ${err.message}`));
|
||||||
|
} else {
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
client.unbind();
|
||||||
|
client.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
await $verifyCredentials({ dn, password });
|
||||||
|
|
||||||
|
if (isPersonalRotation) {
|
||||||
|
const updatedCredentials: TLdapConnection["credentials"] = {
|
||||||
|
...credentials,
|
||||||
|
password
|
||||||
|
};
|
||||||
|
|
||||||
|
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||||
|
credentials: updatedCredentials,
|
||||||
|
orgId,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
await appConnectionDAL.updateById(connection.id, { encryptedCredentials });
|
||||||
|
}
|
||||||
|
|
||||||
|
return { dn, password };
|
||||||
|
};
|
||||||
|
|
||||||
|
const issueCredentials: TRotationFactoryIssueCredentials<TLdapPasswordRotationGeneratedCredentials> = async (
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
|
const credentials = await $rotatePassword();
|
||||||
|
|
||||||
|
return callback(credentials);
|
||||||
|
};
|
||||||
|
|
||||||
|
const revokeCredentials: TRotationFactoryRevokeCredentials<TLdapPasswordRotationGeneratedCredentials> = async (
|
||||||
|
_,
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
|
// we just rotate to a new password, essentially revoking old credentials
|
||||||
|
await $rotatePassword();
|
||||||
|
|
||||||
|
return callback();
|
||||||
|
};
|
||||||
|
|
||||||
|
const rotateCredentials: TRotationFactoryRotateCredentials<TLdapPasswordRotationGeneratedCredentials> = async (
|
||||||
|
_,
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
|
const credentials = await $rotatePassword();
|
||||||
|
|
||||||
|
return callback(credentials);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TLdapPasswordRotationGeneratedCredentials> = (
|
||||||
|
generatedCredentials
|
||||||
|
) => {
|
||||||
|
const secrets = [
|
||||||
|
{
|
||||||
|
key: secretsMapping.dn,
|
||||||
|
value: generatedCredentials.dn
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: secretsMapping.password,
|
||||||
|
value: generatedCredentials.password
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
issueCredentials,
|
||||||
|
revokeCredentials,
|
||||||
|
rotateCredentials,
|
||||||
|
getSecretsPayload
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,68 @@
|
|||||||
|
import RE2 from "re2";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
import {
|
||||||
|
BaseCreateSecretRotationSchema,
|
||||||
|
BaseSecretRotationSchema,
|
||||||
|
BaseUpdateSecretRotationSchema
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas";
|
||||||
|
import { PasswordRequirementsSchema } from "@app/ee/services/secret-rotation-v2/shared/general";
|
||||||
|
import { SecretRotations } from "@app/lib/api-docs";
|
||||||
|
import { DistinguishedNameRegex } from "@app/lib/regex";
|
||||||
|
import { SecretNameSchema } from "@app/server/lib/schemas";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
export const LdapPasswordRotationGeneratedCredentialsSchema = z
|
||||||
|
.object({
|
||||||
|
dn: z.string(),
|
||||||
|
password: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.max(2);
|
||||||
|
|
||||||
|
const LdapPasswordRotationParametersSchema = z.object({
|
||||||
|
dn: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.regex(new RE2(DistinguishedNameRegex), "Invalid DN format, ie; CN=user,OU=users,DC=example,DC=com")
|
||||||
|
.min(1, "Distinguished Name (DN) Required")
|
||||||
|
.describe(SecretRotations.PARAMETERS.LDAP_PASSWORD.dn),
|
||||||
|
passwordRequirements: PasswordRequirementsSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
const LdapPasswordRotationSecretsMappingSchema = z.object({
|
||||||
|
dn: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.LDAP_PASSWORD.dn),
|
||||||
|
password: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.LDAP_PASSWORD.password)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LdapPasswordRotationTemplateSchema = z.object({
|
||||||
|
secretsMapping: z.object({
|
||||||
|
dn: z.string(),
|
||||||
|
password: z.string()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LdapPasswordRotationSchema = BaseSecretRotationSchema(SecretRotation.LdapPassword).extend({
|
||||||
|
type: z.literal(SecretRotation.LdapPassword),
|
||||||
|
parameters: LdapPasswordRotationParametersSchema,
|
||||||
|
secretsMapping: LdapPasswordRotationSecretsMappingSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateLdapPasswordRotationSchema = BaseCreateSecretRotationSchema(SecretRotation.LdapPassword).extend({
|
||||||
|
parameters: LdapPasswordRotationParametersSchema,
|
||||||
|
secretsMapping: LdapPasswordRotationSecretsMappingSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateLdapPasswordRotationSchema = BaseUpdateSecretRotationSchema(SecretRotation.LdapPassword).extend({
|
||||||
|
parameters: LdapPasswordRotationParametersSchema.optional(),
|
||||||
|
secretsMapping: LdapPasswordRotationSecretsMappingSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LdapPasswordRotationListItemSchema = z.object({
|
||||||
|
name: z.literal("LDAP Password"),
|
||||||
|
connection: z.literal(AppConnection.LDAP),
|
||||||
|
type: z.literal(SecretRotation.LdapPassword),
|
||||||
|
template: LdapPasswordRotationTemplateSchema
|
||||||
|
});
|
@@ -0,0 +1,22 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TLdapConnection } from "@app/services/app-connection/ldap";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreateLdapPasswordRotationSchema,
|
||||||
|
LdapPasswordRotationGeneratedCredentialsSchema,
|
||||||
|
LdapPasswordRotationListItemSchema,
|
||||||
|
LdapPasswordRotationSchema
|
||||||
|
} from "./ldap-password-rotation-schemas";
|
||||||
|
|
||||||
|
export type TLdapPasswordRotation = z.infer<typeof LdapPasswordRotationSchema>;
|
||||||
|
|
||||||
|
export type TLdapPasswordRotationInput = z.infer<typeof CreateLdapPasswordRotationSchema>;
|
||||||
|
|
||||||
|
export type TLdapPasswordRotationListItem = z.infer<typeof LdapPasswordRotationListItemSchema>;
|
||||||
|
|
||||||
|
export type TLdapPasswordRotationWithConnection = TLdapPasswordRotation & {
|
||||||
|
connection: TLdapConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TLdapPasswordRotationGeneratedCredentials = z.infer<typeof LdapPasswordRotationGeneratedCredentialsSchema>;
|
@@ -2,7 +2,9 @@ export enum SecretRotation {
|
|||||||
PostgresCredentials = "postgres-credentials",
|
PostgresCredentials = "postgres-credentials",
|
||||||
MsSqlCredentials = "mssql-credentials",
|
MsSqlCredentials = "mssql-credentials",
|
||||||
Auth0ClientSecret = "auth0-client-secret",
|
Auth0ClientSecret = "auth0-client-secret",
|
||||||
AwsIamUserSecret = "aws-iam-user-secret"
|
AzureClientSecret = "azure-client-secret",
|
||||||
|
AwsIamUserSecret = "aws-iam-user-secret",
|
||||||
|
LdapPassword = "ldap-password"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretRotationStatus {
|
export enum SecretRotationStatus {
|
||||||
|
@@ -5,6 +5,8 @@ import { KmsDataKey } from "@app/services/kms/kms-types";
|
|||||||
|
|
||||||
import { AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./auth0-client-secret";
|
import { AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./auth0-client-secret";
|
||||||
import { AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION } from "./aws-iam-user-secret";
|
import { AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION } from "./aws-iam-user-secret";
|
||||||
|
import { AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./azure-client-secret";
|
||||||
|
import { LDAP_PASSWORD_ROTATION_LIST_OPTION } from "./ldap-password";
|
||||||
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
|
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
|
||||||
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
||||||
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
|
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
|
||||||
@@ -20,7 +22,9 @@ const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2List
|
|||||||
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
|
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
[SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION
|
[SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||||
|
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION,
|
||||||
|
[SecretRotation.LdapPassword]: LDAP_PASSWORD_ROTATION_LIST_OPTION
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listSecretRotationOptions = () => {
|
export const listSecretRotationOptions = () => {
|
||||||
|
@@ -5,12 +5,16 @@ export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
|||||||
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
||||||
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
|
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
|
||||||
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
||||||
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret"
|
[SecretRotation.AzureClientSecret]: "Azure Client Secret",
|
||||||
|
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret",
|
||||||
|
[SecretRotation.LdapPassword]: "LDAP Password"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
||||||
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
||||||
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
||||||
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
||||||
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS
|
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
|
||||||
|
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS,
|
||||||
|
[SecretRotation.LdapPassword]: AppConnection.LDAP
|
||||||
};
|
};
|
||||||
|
@@ -20,7 +20,7 @@ export const BaseSecretRotationSchema = (type: SecretRotation) =>
|
|||||||
// unique to provider
|
// unique to provider
|
||||||
type: true,
|
type: true,
|
||||||
parameters: true,
|
parameters: true,
|
||||||
secretMappings: true
|
secretsMapping: true
|
||||||
}).extend({
|
}).extend({
|
||||||
connection: z.object({
|
connection: z.object({
|
||||||
app: z.literal(SECRET_ROTATION_CONNECTION_MAP[type]),
|
app: z.literal(SECRET_ROTATION_CONNECTION_MAP[type]),
|
||||||
|
@@ -14,6 +14,8 @@ import {
|
|||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { auth0ClientSecretRotationFactory } from "@app/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-fns";
|
import { auth0ClientSecretRotationFactory } from "@app/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-fns";
|
||||||
|
import { azureClientSecretRotationFactory } from "@app/ee/services/secret-rotation-v2/azure-client-secret/azure-client-secret-rotation-fns";
|
||||||
|
import { ldapPasswordRotationFactory } from "@app/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-fns";
|
||||||
import { SecretRotation, SecretRotationStatus } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
import { SecretRotation, SecretRotationStatus } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
import {
|
import {
|
||||||
calculateNextRotationAt,
|
calculateNextRotationAt,
|
||||||
@@ -101,7 +103,7 @@ export type TSecretRotationV2ServiceFactoryDep = {
|
|||||||
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
|
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
|
||||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||||
queueService: Pick<TQueueServiceFactory, "queuePg">;
|
queueService: Pick<TQueueServiceFactory, "queuePg">;
|
||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
|
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
|
||||||
@@ -116,7 +118,9 @@ const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplem
|
|||||||
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation
|
[SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
|
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
|
[SecretRotation.LdapPassword]: ldapPasswordRotationFactory as TRotationFactoryImplementation
|
||||||
};
|
};
|
||||||
|
|
||||||
export const secretRotationV2ServiceFactory = ({
|
export const secretRotationV2ServiceFactory = ({
|
||||||
@@ -445,12 +449,25 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
{
|
{
|
||||||
parameters: payload.parameters,
|
parameters: payload.parameters,
|
||||||
secretsMapping,
|
secretsMapping,
|
||||||
connection
|
connection,
|
||||||
|
rotationInterval: payload.rotationInterval
|
||||||
} as TSecretRotationV2WithConnection,
|
} as TSecretRotationV2WithConnection,
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
kmsService
|
kmsService
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// even though we have a db constraint we want to check before any rotation of credentials is attempted
|
||||||
|
// to prevent creation failure after external credentials have been modified
|
||||||
|
const conflictingRotation = await secretRotationV2DAL.findOne({
|
||||||
|
name: payload.name,
|
||||||
|
folderId: folder.id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (conflictingRotation)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `A Secret Rotation with the name "${payload.name}" already exists at the secret path "${secretPath}"`
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
|
|
||||||
|
@@ -19,6 +19,20 @@ import {
|
|||||||
TAwsIamUserSecretRotationListItem,
|
TAwsIamUserSecretRotationListItem,
|
||||||
TAwsIamUserSecretRotationWithConnection
|
TAwsIamUserSecretRotationWithConnection
|
||||||
} from "./aws-iam-user-secret";
|
} from "./aws-iam-user-secret";
|
||||||
|
import {
|
||||||
|
TAzureClientSecretRotation,
|
||||||
|
TAzureClientSecretRotationGeneratedCredentials,
|
||||||
|
TAzureClientSecretRotationInput,
|
||||||
|
TAzureClientSecretRotationListItem,
|
||||||
|
TAzureClientSecretRotationWithConnection
|
||||||
|
} from "./azure-client-secret";
|
||||||
|
import {
|
||||||
|
TLdapPasswordRotation,
|
||||||
|
TLdapPasswordRotationGeneratedCredentials,
|
||||||
|
TLdapPasswordRotationInput,
|
||||||
|
TLdapPasswordRotationListItem,
|
||||||
|
TLdapPasswordRotationWithConnection
|
||||||
|
} from "./ldap-password";
|
||||||
import {
|
import {
|
||||||
TMsSqlCredentialsRotation,
|
TMsSqlCredentialsRotation,
|
||||||
TMsSqlCredentialsRotationInput,
|
TMsSqlCredentialsRotationInput,
|
||||||
@@ -38,29 +52,39 @@ export type TSecretRotationV2 =
|
|||||||
| TPostgresCredentialsRotation
|
| TPostgresCredentialsRotation
|
||||||
| TMsSqlCredentialsRotation
|
| TMsSqlCredentialsRotation
|
||||||
| TAuth0ClientSecretRotation
|
| TAuth0ClientSecretRotation
|
||||||
|
| TAzureClientSecretRotation
|
||||||
|
| TLdapPasswordRotation
|
||||||
| TAwsIamUserSecretRotation;
|
| TAwsIamUserSecretRotation;
|
||||||
|
|
||||||
export type TSecretRotationV2WithConnection =
|
export type TSecretRotationV2WithConnection =
|
||||||
| TPostgresCredentialsRotationWithConnection
|
| TPostgresCredentialsRotationWithConnection
|
||||||
| TMsSqlCredentialsRotationWithConnection
|
| TMsSqlCredentialsRotationWithConnection
|
||||||
| TAuth0ClientSecretRotationWithConnection
|
| TAuth0ClientSecretRotationWithConnection
|
||||||
|
| TAzureClientSecretRotationWithConnection
|
||||||
|
| TLdapPasswordRotationWithConnection
|
||||||
| TAwsIamUserSecretRotationWithConnection;
|
| TAwsIamUserSecretRotationWithConnection;
|
||||||
|
|
||||||
export type TSecretRotationV2GeneratedCredentials =
|
export type TSecretRotationV2GeneratedCredentials =
|
||||||
| TSqlCredentialsRotationGeneratedCredentials
|
| TSqlCredentialsRotationGeneratedCredentials
|
||||||
| TAuth0ClientSecretRotationGeneratedCredentials
|
| TAuth0ClientSecretRotationGeneratedCredentials
|
||||||
|
| TAzureClientSecretRotationGeneratedCredentials
|
||||||
|
| TLdapPasswordRotationGeneratedCredentials
|
||||||
| TAwsIamUserSecretRotationGeneratedCredentials;
|
| TAwsIamUserSecretRotationGeneratedCredentials;
|
||||||
|
|
||||||
export type TSecretRotationV2Input =
|
export type TSecretRotationV2Input =
|
||||||
| TPostgresCredentialsRotationInput
|
| TPostgresCredentialsRotationInput
|
||||||
| TMsSqlCredentialsRotationInput
|
| TMsSqlCredentialsRotationInput
|
||||||
| TAuth0ClientSecretRotationInput
|
| TAuth0ClientSecretRotationInput
|
||||||
|
| TAzureClientSecretRotationInput
|
||||||
|
| TLdapPasswordRotationInput
|
||||||
| TAwsIamUserSecretRotationInput;
|
| TAwsIamUserSecretRotationInput;
|
||||||
|
|
||||||
export type TSecretRotationV2ListItem =
|
export type TSecretRotationV2ListItem =
|
||||||
| TPostgresCredentialsRotationListItem
|
| TPostgresCredentialsRotationListItem
|
||||||
| TMsSqlCredentialsRotationListItem
|
| TMsSqlCredentialsRotationListItem
|
||||||
| TAuth0ClientSecretRotationListItem
|
| TAuth0ClientSecretRotationListItem
|
||||||
|
| TAzureClientSecretRotationListItem
|
||||||
|
| TLdapPasswordRotationListItem
|
||||||
| TAwsIamUserSecretRotationListItem;
|
| TAwsIamUserSecretRotationListItem;
|
||||||
|
|
||||||
export type TSecretRotationV2Raw = NonNullable<Awaited<ReturnType<TSecretRotationV2DALFactory["findById"]>>>;
|
export type TSecretRotationV2Raw = NonNullable<Awaited<ReturnType<TSecretRotationV2DALFactory["findById"]>>>;
|
||||||
@@ -185,7 +209,7 @@ export type TRotationFactory<
|
|||||||
C extends TSecretRotationV2GeneratedCredentials
|
C extends TSecretRotationV2GeneratedCredentials
|
||||||
> = (
|
> = (
|
||||||
secretRotation: T,
|
secretRotation: T,
|
||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
) => {
|
) => {
|
||||||
issueCredentials: TRotationFactoryIssueCredentials<C>;
|
issueCredentials: TRotationFactoryIssueCredentials<C>;
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
||||||
|
import { AzureClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
||||||
|
import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||||
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||||
|
|
||||||
@@ -10,5 +12,7 @@ export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
|||||||
PostgresCredentialsRotationSchema,
|
PostgresCredentialsRotationSchema,
|
||||||
MsSqlCredentialsRotationSchema,
|
MsSqlCredentialsRotationSchema,
|
||||||
Auth0ClientSecretRotationSchema,
|
Auth0ClientSecretRotationSchema,
|
||||||
|
AzureClientSecretRotationSchema,
|
||||||
|
LdapPasswordRotationSchema,
|
||||||
AwsIamUserSecretRotationSchema
|
AwsIamUserSecretRotationSchema
|
||||||
]);
|
]);
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
export * from "./password-requirements-schema";
|
@@ -0,0 +1,44 @@
|
|||||||
|
import RE2 from "re2";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SecretRotations } from "@app/lib/api-docs";
|
||||||
|
|
||||||
|
export const PasswordRequirementsSchema = z
|
||||||
|
.object({
|
||||||
|
length: z
|
||||||
|
.number()
|
||||||
|
.min(1, "Password length must be a positive number")
|
||||||
|
.max(250, "Password length must be less than 250")
|
||||||
|
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.length),
|
||||||
|
required: z.object({
|
||||||
|
digits: z
|
||||||
|
.number()
|
||||||
|
.min(0, "Digit count must be non-negative")
|
||||||
|
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.required.digits),
|
||||||
|
lowercase: z
|
||||||
|
.number()
|
||||||
|
.min(0, "Lowercase count must be non-negative")
|
||||||
|
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.required.lowercase),
|
||||||
|
uppercase: z
|
||||||
|
.number()
|
||||||
|
.min(0, "Uppercase count must be non-negative")
|
||||||
|
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.required.uppercase),
|
||||||
|
symbols: z
|
||||||
|
.number()
|
||||||
|
.min(0, "Symbol count must be non-negative")
|
||||||
|
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.required.symbols)
|
||||||
|
}),
|
||||||
|
allowedSymbols: z
|
||||||
|
.string()
|
||||||
|
.regex(new RE2("[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?~]"), "Invalid symbols")
|
||||||
|
.optional()
|
||||||
|
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.allowedSymbols)
|
||||||
|
})
|
||||||
|
.refine((data) => {
|
||||||
|
return Object.values(data.required).some((count) => count > 0);
|
||||||
|
}, "At least one character type must be required")
|
||||||
|
.refine((data) => {
|
||||||
|
const total = Object.values(data.required).reduce((sum, count) => sum + count, 0);
|
||||||
|
return total <= data.length;
|
||||||
|
}, "Sum of required characters cannot exceed the total length")
|
||||||
|
.describe(SecretRotations.PARAMETERS.GENERAL.PASSWORD_REQUIREMENTS.base);
|
@@ -1,6 +1,17 @@
|
|||||||
import { randomInt } from "crypto";
|
import { randomInt } from "crypto";
|
||||||
|
|
||||||
const DEFAULT_PASSWORD_REQUIREMENTS = {
|
type TPasswordRequirements = {
|
||||||
|
length: number;
|
||||||
|
required: {
|
||||||
|
lowercase: number;
|
||||||
|
uppercase: number;
|
||||||
|
digits: number;
|
||||||
|
symbols: number;
|
||||||
|
};
|
||||||
|
allowedSymbols?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_PASSWORD_REQUIREMENTS: TPasswordRequirements = {
|
||||||
length: 48,
|
length: 48,
|
||||||
required: {
|
required: {
|
||||||
lowercase: 1,
|
lowercase: 1,
|
||||||
@@ -11,9 +22,9 @@ const DEFAULT_PASSWORD_REQUIREMENTS = {
|
|||||||
allowedSymbols: "-_.~!*"
|
allowedSymbols: "-_.~!*"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generatePassword = () => {
|
export const generatePassword = (passwordRequirements?: TPasswordRequirements) => {
|
||||||
try {
|
try {
|
||||||
const { length, required, allowedSymbols } = DEFAULT_PASSWORD_REQUIREMENTS;
|
const { length, required, allowedSymbols } = passwordRequirements ?? DEFAULT_PASSWORD_REQUIREMENTS;
|
||||||
|
|
||||||
const chars = {
|
const chars = {
|
||||||
lowercase: "abcdefghijklmnopqrstuvwxyz",
|
lowercase: "abcdefghijklmnopqrstuvwxyz",
|
||||||
|
@@ -807,6 +807,8 @@ export const RAW_SECRETS = {
|
|||||||
tagIds: "The ID of the tags to be attached to the updated secret.",
|
tagIds: "The ID of the tags to be attached to the updated secret.",
|
||||||
secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days.",
|
secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days.",
|
||||||
secretReminderNote: "Note to be attached in notification email.",
|
secretReminderNote: "Note to be attached in notification email.",
|
||||||
|
secretReminderRecipients:
|
||||||
|
"An array of user IDs that will receive the reminder email. If not specified, all project members will receive the reminder email.",
|
||||||
newSecretName: "The new name for the secret."
|
newSecretName: "The new name for the secret."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
@@ -1860,9 +1862,30 @@ export const AppConnections = {
|
|||||||
instanceUrl: "The Windmill instance URL to connect with (defaults to https://app.windmill.dev).",
|
instanceUrl: "The Windmill instance URL to connect with (defaults to https://app.windmill.dev).",
|
||||||
accessToken: "The access token to use to connect with Windmill."
|
accessToken: "The access token to use to connect with Windmill."
|
||||||
},
|
},
|
||||||
|
HC_VAULT: {
|
||||||
|
instanceUrl: "The Hashicrop Vault instance URL to connect with.",
|
||||||
|
namespace: "The Hashicrop Vault namespace to connect with.",
|
||||||
|
accessToken: "The access token used to connect with Hashicorp Vault.",
|
||||||
|
roleId: "The Role ID used to connect with Hashicorp Vault.",
|
||||||
|
secretId: "The Secret ID used to connect with Hashicorp Vault."
|
||||||
|
},
|
||||||
|
LDAP: {
|
||||||
|
provider: "The type of LDAP provider. Determines provider-specific behaviors.",
|
||||||
|
url: "The LDAP/LDAPS URL to connect to (e.g., 'ldap://domain-or-ip:389' or 'ldaps://domain-or-ip:636').",
|
||||||
|
dn: "The Distinguished Name (DN) of the principal to bind with (e.g., 'CN=John,CN=Users,DC=example,DC=com').",
|
||||||
|
password: "The password to bind with for authentication.",
|
||||||
|
sslRejectUnauthorized:
|
||||||
|
"Whether or not to reject unauthorized SSL certificates (true/false) when using ldaps://. Set to false only in test environments.",
|
||||||
|
sslCertificate:
|
||||||
|
"The SSL certificate (PEM format) to use for secure connection when using ldaps:// with a self-signed certificate."
|
||||||
|
},
|
||||||
TEAMCITY: {
|
TEAMCITY: {
|
||||||
instanceUrl: "The TeamCity instance URL to connect with.",
|
instanceUrl: "The TeamCity instance URL to connect with.",
|
||||||
accessToken: "The access token to use to connect with TeamCity."
|
accessToken: "The access token to use to connect with TeamCity."
|
||||||
|
},
|
||||||
|
AZURE_CLIENT_SECRETS: {
|
||||||
|
code: "The OAuth code to use to connect with Azure Client Secrets.",
|
||||||
|
tenantId: "The Tenant ID to use to connect with Azure Client Secrets."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -2003,6 +2026,10 @@ export const SecretSyncs = {
|
|||||||
workspace: "The Windmill workspace to sync secrets to.",
|
workspace: "The Windmill workspace to sync secrets to.",
|
||||||
path: "The Windmill workspace path to sync secrets to."
|
path: "The Windmill workspace path to sync secrets to."
|
||||||
},
|
},
|
||||||
|
HC_VAULT: {
|
||||||
|
mount: "The Hashicorp Vault Secrets Engine Mount to sync secrets to.",
|
||||||
|
path: "The Hashicorp Vault path to sync secrets to."
|
||||||
|
},
|
||||||
TEAMCITY: {
|
TEAMCITY: {
|
||||||
project: "The TeamCity project to sync secrets to.",
|
project: "The TeamCity project to sync secrets to.",
|
||||||
buildConfig: "The TeamCity build configuration to sync secrets to."
|
buildConfig: "The TeamCity build configuration to sync secrets to."
|
||||||
@@ -2071,6 +2098,27 @@ export const SecretRotations = {
|
|||||||
AUTH0_CLIENT_SECRET: {
|
AUTH0_CLIENT_SECRET: {
|
||||||
clientId: "The client ID of the Auth0 Application to rotate the client secret for."
|
clientId: "The client ID of the Auth0 Application to rotate the client secret for."
|
||||||
},
|
},
|
||||||
|
AZURE_CLIENT_SECRET: {
|
||||||
|
objectId: "The ID of the Azure Application to rotate the client secret for.",
|
||||||
|
appName: "The name of the Azure Application to rotate the client secret for.",
|
||||||
|
clientId: "The client ID of the Azure Application to rotate the client secret for."
|
||||||
|
},
|
||||||
|
LDAP_PASSWORD: {
|
||||||
|
dn: "The Distinguished Name (DN) of the principal to rotate the password for."
|
||||||
|
},
|
||||||
|
GENERAL: {
|
||||||
|
PASSWORD_REQUIREMENTS: {
|
||||||
|
base: "The password requirements to use when generating the new password.",
|
||||||
|
length: "The length of the password to generate.",
|
||||||
|
required: {
|
||||||
|
digits: "The amount of digits to require in the generated password.",
|
||||||
|
lowercase: "The amount of lowercase characters to require in the generated password.",
|
||||||
|
uppercase: "The amount of uppercase characters to require in the generated password.",
|
||||||
|
symbols: "The amount of symbols to require in the generated password."
|
||||||
|
},
|
||||||
|
allowedSymbols: 'The allowed symbols to use in the generated password (defaults to "-_.~!*").'
|
||||||
|
}
|
||||||
|
},
|
||||||
AWS_IAM_USER_SECRET: {
|
AWS_IAM_USER_SECRET: {
|
||||||
userName: "The name of the client to rotate credentials for.",
|
userName: "The name of the client to rotate credentials for.",
|
||||||
region: "The AWS region the client is present in."
|
region: "The AWS region the client is present in."
|
||||||
@@ -2085,6 +2133,14 @@ export const SecretRotations = {
|
|||||||
clientId: "The name of the secret that the client ID will be mapped to.",
|
clientId: "The name of the secret that the client ID will be mapped to.",
|
||||||
clientSecret: "The name of the secret that the rotated client secret will be mapped to."
|
clientSecret: "The name of the secret that the rotated client secret will be mapped to."
|
||||||
},
|
},
|
||||||
|
AZURE_CLIENT_SECRET: {
|
||||||
|
clientId: "The name of the secret that the client ID will be mapped to.",
|
||||||
|
clientSecret: "The name of the secret that the rotated client secret will be mapped to."
|
||||||
|
},
|
||||||
|
LDAP_PASSWORD: {
|
||||||
|
dn: "The name of the secret that the Distinguished Name (DN) of the principal will be mapped to.",
|
||||||
|
password: "The name of the secret that the rotated password will be mapped to."
|
||||||
|
},
|
||||||
AWS_IAM_USER_SECRET: {
|
AWS_IAM_USER_SECRET: {
|
||||||
accessKeyId: "The name of the secret that the access key ID will be mapped to.",
|
accessKeyId: "The name of the secret that the access key ID will be mapped to.",
|
||||||
secretAccessKey: "The name of the secret that the rotated secret access key will be mapped to."
|
secretAccessKey: "The name of the secret that the rotated secret access key will be mapped to."
|
||||||
|
1
backend/src/lib/config/const.ts
Normal file
1
backend/src/lib/config/const.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const INFISICAL_PROVIDER_GITHUB_ACCESS_TOKEN = "x-infisical-github-auth-access-token";
|
@@ -2,7 +2,7 @@ export const daysToMillisecond = (days: number) => days * 24 * 60 * 60 * 1000;
|
|||||||
|
|
||||||
export const secondsToMillis = (seconds: number) => seconds * 1000;
|
export const secondsToMillis = (seconds: number) => seconds * 1000;
|
||||||
|
|
||||||
export const applyJitter = (delayMs: number, jitterMs: number) => {
|
export const applyJitter = (delay: number, jitter: number) => {
|
||||||
const jitter = Math.floor(Math.random() * (2 * jitterMs)) - jitterMs;
|
const jitterTime = Math.floor(Math.random() * (2 * jitter)) - jitter;
|
||||||
return delayMs + jitter;
|
return delay + jitterTime;
|
||||||
};
|
};
|
||||||
|
@@ -6,4 +6,5 @@ export * from "./array";
|
|||||||
export * from "./dates";
|
export * from "./dates";
|
||||||
export * from "./object";
|
export * from "./object";
|
||||||
export * from "./string";
|
export * from "./string";
|
||||||
|
export * from "./time";
|
||||||
export * from "./undefined";
|
export * from "./undefined";
|
||||||
|
21
backend/src/lib/fn/time.ts
Normal file
21
backend/src/lib/fn/time.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import ms, { StringValue } from "ms";
|
||||||
|
|
||||||
|
const convertToMilliseconds = (exp: string | number): number => {
|
||||||
|
if (typeof exp === "number") {
|
||||||
|
return exp * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = ms(exp as StringValue);
|
||||||
|
if (typeof result !== "number") {
|
||||||
|
throw new Error(`Invalid expiration format: ${exp}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMinExpiresIn = (exp1: string | number, exp2: string | number): string | number => {
|
||||||
|
const ms1 = convertToMilliseconds(exp1);
|
||||||
|
const ms2 = convertToMilliseconds(exp2);
|
||||||
|
|
||||||
|
return ms1 <= ms2 ? exp1 : exp2;
|
||||||
|
};
|
@@ -2,6 +2,8 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
import { Tables } from "knex/types/tables";
|
import { Tables } from "knex/types/tables";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
import { DatabaseError } from "../errors";
|
import { DatabaseError } from "../errors";
|
||||||
import { buildDynamicKnexQuery, TKnexDynamicOperator } from "./dynamic";
|
import { buildDynamicKnexQuery, TKnexDynamicOperator } from "./dynamic";
|
||||||
|
|
||||||
@@ -25,28 +27,41 @@ export type TFindFilter<R extends object = object> = Partial<R> & {
|
|||||||
$search?: Partial<{ [k in keyof R]: R[k] }>;
|
$search?: Partial<{ [k in keyof R]: R[k] }>;
|
||||||
$complex?: TKnexDynamicOperator<R>;
|
$complex?: TKnexDynamicOperator<R>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildFindFilter =
|
export const buildFindFilter =
|
||||||
<R extends object = object>({ $in, $notNull, $search, $complex, ...filter }: TFindFilter<R>) =>
|
<R extends object = object>(
|
||||||
|
{ $in, $notNull, $search, $complex, ...filter }: TFindFilter<R>,
|
||||||
|
tableName?: TableName,
|
||||||
|
excludeKeys?: Array<keyof R>
|
||||||
|
) =>
|
||||||
(bd: Knex.QueryBuilder<R, R>) => {
|
(bd: Knex.QueryBuilder<R, R>) => {
|
||||||
void bd.where(filter);
|
const processedFilter = tableName
|
||||||
|
? Object.fromEntries(
|
||||||
|
Object.entries(filter)
|
||||||
|
.filter(([key]) => !excludeKeys || !excludeKeys.includes(key as keyof R))
|
||||||
|
.map(([key, value]) => [`${tableName}.${key}`, value])
|
||||||
|
)
|
||||||
|
: filter;
|
||||||
|
|
||||||
|
void bd.where(processedFilter);
|
||||||
if ($in) {
|
if ($in) {
|
||||||
Object.entries($in).forEach(([key, val]) => {
|
Object.entries($in).forEach(([key, val]) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
void bd.whereIn(key as never, val as never);
|
void bd.whereIn(`${tableName ? `${tableName}.` : ""}${key}`, val as never);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($notNull?.length) {
|
if ($notNull?.length) {
|
||||||
$notNull.forEach((key) => {
|
$notNull.forEach((key) => {
|
||||||
void bd.whereNotNull(key as never);
|
void bd.whereNotNull(`${tableName ? `${tableName}.` : ""}${key as string}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($search) {
|
if ($search) {
|
||||||
Object.entries($search).forEach(([key, val]) => {
|
Object.entries($search).forEach(([key, val]) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
void bd.whereILike(key as never, val as never);
|
void bd.whereILike(`${tableName ? `${tableName}.` : ""}${key}`, val as never);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
3
backend/src/lib/regex/index.ts
Normal file
3
backend/src/lib/regex/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const DistinguishedNameRegex =
|
||||||
|
// DN format, ie; CN=user,OU=users,DC=example,DC=com
|
||||||
|
/^(?:(?:[a-zA-Z0-9]+=[^,+="<>#;\\\\]+)(?:(?:\\+[a-zA-Z0-9]+=[^,+="<>#;\\\\]+)*)(?:,(?:[a-zA-Z0-9]+=[^,+="<>#;\\\\]+)(?:(?:\\+[a-zA-Z0-9]+=[^,+="<>#;\\\\]+)*))*)?$/;
|
@@ -16,3 +16,17 @@ export const fetchGithubEmails = async (accessToken: string) => {
|
|||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TGithubUser = {
|
||||||
|
name?: string;
|
||||||
|
login: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchGithubUser = async (accessToken: string) => {
|
||||||
|
const { data } = await request.get<TGithubUser>(`${INTEGRATION_GITHUB_API_URL}/user`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
@@ -15,13 +15,13 @@ export const blockLocalAndPrivateIpAddresses = async (url: string) => {
|
|||||||
|
|
||||||
const validUrl = new URL(url);
|
const validUrl = new URL(url);
|
||||||
const inputHostIps: string[] = [];
|
const inputHostIps: string[] = [];
|
||||||
if (isIPv4(validUrl.host)) {
|
if (isIPv4(validUrl.hostname)) {
|
||||||
inputHostIps.push(validUrl.host);
|
inputHostIps.push(validUrl.hostname);
|
||||||
} else {
|
} else {
|
||||||
if (validUrl.host === "localhost" || validUrl.host === "host.docker.internal") {
|
if (validUrl.hostname === "localhost" || validUrl.hostname === "host.docker.internal") {
|
||||||
throw new BadRequestError({ message: "Local IPs not allowed as URL" });
|
throw new BadRequestError({ message: "Local IPs not allowed as URL" });
|
||||||
}
|
}
|
||||||
const resolvedIps = await dns.resolve4(validUrl.host);
|
const resolvedIps = await dns.resolve4(validUrl.hostname);
|
||||||
inputHostIps.push(...resolvedIps);
|
inputHostIps.push(...resolvedIps);
|
||||||
}
|
}
|
||||||
const isInternalIp = inputHostIps.some((el) => isPrivateIp(el));
|
const isInternalIp = inputHostIps.some((el) => isPrivateIp(el));
|
||||||
|
@@ -0,0 +1,98 @@
|
|||||||
|
import { validateMicrosoftTeamsChannelsSchema } from "@app/services/microsoft-teams/microsoft-teams-fns";
|
||||||
|
import { sendSlackNotification } from "@app/services/slack/slack-fns";
|
||||||
|
|
||||||
|
import { logger } from "../logger";
|
||||||
|
import { TriggerFeature, TTriggerWorkflowNotificationDTO } from "./types";
|
||||||
|
|
||||||
|
export const triggerWorkflowIntegrationNotification = async (dto: TTriggerWorkflowNotificationDTO) => {
|
||||||
|
try {
|
||||||
|
const { projectId, notification } = dto.input;
|
||||||
|
const { projectDAL, projectSlackConfigDAL, kmsService, projectMicrosoftTeamsConfigDAL, microsoftTeamsService } =
|
||||||
|
dto.dependencies;
|
||||||
|
|
||||||
|
const project = await projectDAL.findById(projectId);
|
||||||
|
|
||||||
|
if (!project) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const microsoftTeamsConfig = await projectMicrosoftTeamsConfigDAL.getIntegrationDetailsByProject(projectId);
|
||||||
|
const slackConfig = await projectSlackConfigDAL.getIntegrationDetailsByProject(projectId);
|
||||||
|
|
||||||
|
if (slackConfig) {
|
||||||
|
if (notification.type === TriggerFeature.ACCESS_REQUEST) {
|
||||||
|
const targetChannelIds = slackConfig.accessRequestChannels?.split(", ") || [];
|
||||||
|
if (targetChannelIds.length && slackConfig.isAccessRequestNotificationEnabled) {
|
||||||
|
await sendSlackNotification({
|
||||||
|
orgId: project.orgId,
|
||||||
|
notification,
|
||||||
|
kmsService,
|
||||||
|
targetChannelIds,
|
||||||
|
slackIntegration: slackConfig
|
||||||
|
}).catch((error) => {
|
||||||
|
logger.error(error, "Error sending Slack notification");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (notification.type === TriggerFeature.SECRET_APPROVAL) {
|
||||||
|
const targetChannelIds = slackConfig.secretRequestChannels?.split(", ") || [];
|
||||||
|
if (targetChannelIds.length && slackConfig.isSecretRequestNotificationEnabled) {
|
||||||
|
await sendSlackNotification({
|
||||||
|
orgId: project.orgId,
|
||||||
|
notification,
|
||||||
|
kmsService,
|
||||||
|
targetChannelIds,
|
||||||
|
slackIntegration: slackConfig
|
||||||
|
}).catch((error) => {
|
||||||
|
logger.error(error, "Error sending Slack notification");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (microsoftTeamsConfig) {
|
||||||
|
if (notification.type === TriggerFeature.ACCESS_REQUEST) {
|
||||||
|
if (microsoftTeamsConfig.isAccessRequestNotificationEnabled && microsoftTeamsConfig.accessRequestChannels) {
|
||||||
|
const { success, data } = validateMicrosoftTeamsChannelsSchema.safeParse(
|
||||||
|
microsoftTeamsConfig.accessRequestChannels
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success && data) {
|
||||||
|
await microsoftTeamsService
|
||||||
|
.sendNotification({
|
||||||
|
notification,
|
||||||
|
target: data,
|
||||||
|
tenantId: microsoftTeamsConfig.tenantId,
|
||||||
|
microsoftTeamsIntegrationId: microsoftTeamsConfig.id,
|
||||||
|
orgId: project.orgId
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error(error, "Error sending Microsoft Teams notification");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (notification.type === TriggerFeature.SECRET_APPROVAL) {
|
||||||
|
if (microsoftTeamsConfig.isSecretRequestNotificationEnabled && microsoftTeamsConfig.secretRequestChannels) {
|
||||||
|
const { success, data } = validateMicrosoftTeamsChannelsSchema.safeParse(
|
||||||
|
microsoftTeamsConfig.secretRequestChannels
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success && data) {
|
||||||
|
await microsoftTeamsService
|
||||||
|
.sendNotification({
|
||||||
|
notification,
|
||||||
|
target: data,
|
||||||
|
tenantId: microsoftTeamsConfig.tenantId,
|
||||||
|
microsoftTeamsIntegrationId: microsoftTeamsConfig.id,
|
||||||
|
orgId: project.orgId
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error(error, "Error sending Microsoft Teams notification");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Error triggering workflow integration notification");
|
||||||
|
}
|
||||||
|
};
|
51
backend/src/lib/workflow-integrations/types.ts
Normal file
51
backend/src/lib/workflow-integrations/types.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
|
||||||
|
import { TProjectMicrosoftTeamsConfigDALFactory } from "@app/services/microsoft-teams/project-microsoft-teams-config-dal";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal";
|
||||||
|
|
||||||
|
export enum TriggerFeature {
|
||||||
|
SECRET_APPROVAL = "secret-approval",
|
||||||
|
ACCESS_REQUEST = "access-request"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TNotification =
|
||||||
|
| {
|
||||||
|
type: TriggerFeature.SECRET_APPROVAL;
|
||||||
|
payload: {
|
||||||
|
userEmail: string;
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
requestId: string;
|
||||||
|
projectId: string;
|
||||||
|
secretKeys: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: TriggerFeature.ACCESS_REQUEST;
|
||||||
|
payload: {
|
||||||
|
requesterFullName: string;
|
||||||
|
requesterEmail: string;
|
||||||
|
isTemporary: boolean;
|
||||||
|
secretPath: string;
|
||||||
|
environment: string;
|
||||||
|
projectName: string;
|
||||||
|
permissions: string[];
|
||||||
|
approvalUrl: string;
|
||||||
|
note?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTriggerWorkflowNotificationDTO = {
|
||||||
|
input: {
|
||||||
|
projectId: string;
|
||||||
|
notification: TNotification;
|
||||||
|
};
|
||||||
|
dependencies: {
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
|
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||||
|
projectMicrosoftTeamsConfigDAL: Pick<TProjectMicrosoftTeamsConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
microsoftTeamsService: Pick<TMicrosoftTeamsServiceFactory, "sendNotification">;
|
||||||
|
};
|
||||||
|
};
|
24
backend/src/server/plugins/auth/inject-assume-privilege.ts
Normal file
24
backend/src/server/plugins/auth/inject-assume-privilege.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { requestContext } from "@fastify/request-context";
|
||||||
|
import fp from "fastify-plugin";
|
||||||
|
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const injectAssumePrivilege = fp(async (server: FastifyZodProvider) => {
|
||||||
|
server.addHook("onRequest", async (req, res) => {
|
||||||
|
const assumeRoleCookie = req.cookies["infisical-project-assume-privileges"];
|
||||||
|
try {
|
||||||
|
if (req?.auth?.authMode === AuthMode.JWT && assumeRoleCookie) {
|
||||||
|
const decodedToken = server.services.assumePrivileges.verifyAssumePrivilegeToken(
|
||||||
|
assumeRoleCookie,
|
||||||
|
req.auth.tokenVersionId
|
||||||
|
);
|
||||||
|
if (decodedToken) {
|
||||||
|
requestContext.set("assumedPrivilegeDetails", decodedToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
req.log.error({ error }, "Failed to verify assume privilege token");
|
||||||
|
void res.clearCookie("infisical-project-assume-privileges");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@@ -111,6 +111,11 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Authentication is handled on a route-level here.
|
||||||
|
if (req.url.includes("/api/v1/workflow-integrations/microsoft-teams/message-endpoint")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { authMode, token, actor } = await extractAuth(req, appCfg.AUTH_SECRET);
|
const { authMode, token, actor } = await extractAuth(req, appCfg.AUTH_SECRET);
|
||||||
|
|
||||||
if (!authMode) return;
|
if (!authMode) return;
|
||||||
|
@@ -12,6 +12,7 @@ import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-appr
|
|||||||
import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal";
|
import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal";
|
||||||
import { accessApprovalRequestReviewerDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-reviewer-dal";
|
import { accessApprovalRequestReviewerDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-reviewer-dal";
|
||||||
import { accessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
|
import { accessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
|
||||||
|
import { assumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-service";
|
||||||
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";
|
||||||
@@ -32,6 +33,8 @@ import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
|
|||||||
import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||||
import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal";
|
import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal";
|
||||||
import { projectGatewayDALFactory } from "@app/ee/services/gateway/project-gateway-dal";
|
import { projectGatewayDALFactory } from "@app/ee/services/gateway/project-gateway-dal";
|
||||||
|
import { githubOrgSyncDALFactory } from "@app/ee/services/github-org-sync/github-org-sync-dal";
|
||||||
|
import { githubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
|
||||||
import { groupDALFactory } from "@app/ee/services/group/group-dal";
|
import { groupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { groupServiceFactory } from "@app/ee/services/group/group-service";
|
import { groupServiceFactory } from "@app/ee/services/group/group-service";
|
||||||
import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
@@ -171,6 +174,9 @@ import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal";
|
|||||||
import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal";
|
import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal";
|
||||||
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
|
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
|
||||||
import { kmsServiceFactory } from "@app/services/kms/kms-service";
|
import { kmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { microsoftTeamsIntegrationDALFactory } from "@app/services/microsoft-teams/microsoft-teams-integration-dal";
|
||||||
|
import { microsoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
|
||||||
|
import { projectMicrosoftTeamsConfigDALFactory } from "@app/services/microsoft-teams/project-microsoft-teams-config-dal";
|
||||||
import { incidentContactDALFactory } from "@app/services/org/incident-contacts-dal";
|
import { incidentContactDALFactory } from "@app/services/org/incident-contacts-dal";
|
||||||
import { orgBotDALFactory } from "@app/services/org/org-bot-dal";
|
import { orgBotDALFactory } from "@app/services/org/org-bot-dal";
|
||||||
import { orgDALFactory } from "@app/services/org/org-dal";
|
import { orgDALFactory } from "@app/services/org/org-dal";
|
||||||
@@ -214,6 +220,7 @@ import { secretFolderServiceFactory } from "@app/services/secret-folder/secret-f
|
|||||||
import { secretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal";
|
import { secretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal";
|
||||||
import { secretImportDALFactory } from "@app/services/secret-import/secret-import-dal";
|
import { secretImportDALFactory } from "@app/services/secret-import/secret-import-dal";
|
||||||
import { secretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
import { secretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
||||||
|
import { secretReminderRecipientsDALFactory } from "@app/services/secret-reminder-recipients/secret-reminder-recipients-dal";
|
||||||
import { secretSharingDALFactory } from "@app/services/secret-sharing/secret-sharing-dal";
|
import { secretSharingDALFactory } from "@app/services/secret-sharing/secret-sharing-dal";
|
||||||
import { secretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
import { secretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
||||||
import { secretSyncDALFactory } from "@app/services/secret-sync/secret-sync-dal";
|
import { secretSyncDALFactory } from "@app/services/secret-sync/secret-sync-dal";
|
||||||
@@ -248,6 +255,7 @@ import { workflowIntegrationDALFactory } from "@app/services/workflow-integratio
|
|||||||
import { workflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
import { workflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||||
|
|
||||||
import { injectAuditLogInfo } from "../plugins/audit-log";
|
import { injectAuditLogInfo } from "../plugins/audit-log";
|
||||||
|
import { injectAssumePrivilege } from "../plugins/auth/inject-assume-privilege";
|
||||||
import { injectIdentity } from "../plugins/auth/inject-identity";
|
import { injectIdentity } from "../plugins/auth/inject-identity";
|
||||||
import { injectPermission } from "../plugins/auth/inject-permission";
|
import { injectPermission } from "../plugins/auth/inject-permission";
|
||||||
import { injectRateLimits } from "../plugins/inject-rate-limits";
|
import { injectRateLimits } from "../plugins/inject-rate-limits";
|
||||||
@@ -417,8 +425,12 @@ export const registerRoutes = async (
|
|||||||
const orgGatewayConfigDAL = orgGatewayConfigDALFactory(db);
|
const orgGatewayConfigDAL = orgGatewayConfigDALFactory(db);
|
||||||
const gatewayDAL = gatewayDALFactory(db);
|
const gatewayDAL = gatewayDALFactory(db);
|
||||||
const projectGatewayDAL = projectGatewayDALFactory(db);
|
const projectGatewayDAL = projectGatewayDALFactory(db);
|
||||||
|
const secretReminderRecipientsDAL = secretReminderRecipientsDALFactory(db);
|
||||||
|
const githubOrgSyncDAL = githubOrgSyncDALFactory(db);
|
||||||
|
|
||||||
const secretRotationV2DAL = secretRotationV2DALFactory(db, folderDAL);
|
const secretRotationV2DAL = secretRotationV2DALFactory(db, folderDAL);
|
||||||
|
const microsoftTeamsIntegrationDAL = microsoftTeamsIntegrationDALFactory(db);
|
||||||
|
const projectMicrosoftTeamsConfigDAL = projectMicrosoftTeamsConfigDALFactory(db);
|
||||||
|
|
||||||
const permissionService = permissionServiceFactory({
|
const permissionService = permissionServiceFactory({
|
||||||
permissionDAL,
|
permissionDAL,
|
||||||
@@ -427,6 +439,11 @@ export const registerRoutes = async (
|
|||||||
serviceTokenDAL,
|
serviceTokenDAL,
|
||||||
projectDAL
|
projectDAL
|
||||||
});
|
});
|
||||||
|
const assumePrivilegeService = assumePrivilegeServiceFactory({
|
||||||
|
projectDAL,
|
||||||
|
permissionService
|
||||||
|
});
|
||||||
|
|
||||||
const licenseService = licenseServiceFactory({
|
const licenseService = licenseServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
@@ -549,6 +566,15 @@ export const registerRoutes = async (
|
|||||||
externalGroupOrgRoleMappingDAL
|
externalGroupOrgRoleMappingDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const githubOrgSyncConfigService = githubOrgSyncServiceFactory({
|
||||||
|
licenseService,
|
||||||
|
githubOrgSyncDAL,
|
||||||
|
kmsService,
|
||||||
|
permissionService,
|
||||||
|
groupDAL,
|
||||||
|
userGroupMembershipDAL
|
||||||
|
});
|
||||||
|
|
||||||
const ldapService = ldapConfigServiceFactory({
|
const ldapService = ldapConfigServiceFactory({
|
||||||
ldapConfigDAL,
|
ldapConfigDAL,
|
||||||
ldapGroupMapDAL,
|
ldapGroupMapDAL,
|
||||||
@@ -602,6 +628,7 @@ export const registerRoutes = async (
|
|||||||
tokenService,
|
tokenService,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
totpService,
|
totpService,
|
||||||
|
orgMembershipDAL,
|
||||||
auditLogService
|
auditLogService
|
||||||
});
|
});
|
||||||
const passwordService = authPaswordServiceFactory({
|
const passwordService = authPaswordServiceFactory({
|
||||||
@@ -666,6 +693,15 @@ export const registerRoutes = async (
|
|||||||
orgDAL,
|
orgDAL,
|
||||||
externalGroupOrgRoleMappingDAL
|
externalGroupOrgRoleMappingDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const microsoftTeamsService = microsoftTeamsServiceFactory({
|
||||||
|
microsoftTeamsIntegrationDAL,
|
||||||
|
permissionService,
|
||||||
|
workflowIntegrationDAL,
|
||||||
|
kmsService,
|
||||||
|
serverCfgDAL: superAdminDAL
|
||||||
|
});
|
||||||
|
|
||||||
const superAdminService = superAdminServiceFactory({
|
const superAdminService = superAdminServiceFactory({
|
||||||
userDAL,
|
userDAL,
|
||||||
identityDAL,
|
identityDAL,
|
||||||
@@ -679,7 +715,8 @@ export const registerRoutes = async (
|
|||||||
orgService,
|
orgService,
|
||||||
keyStore,
|
keyStore,
|
||||||
licenseService,
|
licenseService,
|
||||||
kmsService
|
kmsService,
|
||||||
|
microsoftTeamsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const orgAdminService = orgAdminServiceFactory({
|
const orgAdminService = orgAdminServiceFactory({
|
||||||
@@ -728,6 +765,7 @@ export const registerRoutes = async (
|
|||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
groupProjectDAL,
|
groupProjectDAL,
|
||||||
|
secretReminderRecipientsDAL,
|
||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({
|
const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({
|
||||||
@@ -961,6 +999,7 @@ export const registerRoutes = async (
|
|||||||
secretApprovalRequestDAL,
|
secretApprovalRequestDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
|
secretReminderRecipientsDAL,
|
||||||
orgService,
|
orgService,
|
||||||
resourceMetadataDAL,
|
resourceMetadataDAL,
|
||||||
secretSyncQueue
|
secretSyncQueue
|
||||||
@@ -1003,6 +1042,8 @@ export const registerRoutes = async (
|
|||||||
certificateTemplateDAL,
|
certificateTemplateDAL,
|
||||||
projectSlackConfigDAL,
|
projectSlackConfigDAL,
|
||||||
slackIntegrationDAL,
|
slackIntegrationDAL,
|
||||||
|
projectMicrosoftTeamsConfigDAL,
|
||||||
|
microsoftTeamsIntegrationDAL,
|
||||||
projectTemplateService,
|
projectTemplateService,
|
||||||
groupProjectDAL,
|
groupProjectDAL,
|
||||||
smtpService
|
smtpService
|
||||||
@@ -1022,7 +1063,9 @@ export const registerRoutes = async (
|
|||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
identityProjectMembershipRoleDAL,
|
identityProjectMembershipRoleDAL,
|
||||||
projectDAL
|
projectDAL,
|
||||||
|
identityDAL,
|
||||||
|
userDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const snapshotService = secretSnapshotServiceFactory({
|
const snapshotService = secretSnapshotServiceFactory({
|
||||||
@@ -1125,7 +1168,9 @@ export const registerRoutes = async (
|
|||||||
userDAL,
|
userDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
projectSlackConfigDAL,
|
projectSlackConfigDAL,
|
||||||
resourceMetadataDAL
|
resourceMetadataDAL,
|
||||||
|
projectMicrosoftTeamsConfigDAL,
|
||||||
|
microsoftTeamsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretService = secretServiceFactory({
|
const secretService = secretServiceFactory({
|
||||||
@@ -1187,7 +1232,9 @@ export const registerRoutes = async (
|
|||||||
accessApprovalPolicyApproverDAL,
|
accessApprovalPolicyApproverDAL,
|
||||||
projectSlackConfigDAL,
|
projectSlackConfigDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
groupDAL
|
groupDAL,
|
||||||
|
microsoftTeamsService,
|
||||||
|
projectMicrosoftTeamsConfigDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretReplicationService = secretReplicationServiceFactory({
|
const secretReplicationService = secretReplicationServiceFactory({
|
||||||
@@ -1516,6 +1563,7 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
const secretSyncService = secretSyncServiceFactory({
|
const secretSyncService = secretSyncServiceFactory({
|
||||||
secretSyncDAL,
|
secretSyncDAL,
|
||||||
|
secretImportDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
appConnectionService,
|
appConnectionService,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
@@ -1584,6 +1632,7 @@ export const registerRoutes = async (
|
|||||||
await dailyResourceCleanUp.startCleanUp();
|
await dailyResourceCleanUp.startCleanUp();
|
||||||
await dailyExpiringPkiItemAlert.startSendingAlerts();
|
await dailyExpiringPkiItemAlert.startSendingAlerts();
|
||||||
await kmsService.startService();
|
await kmsService.startService();
|
||||||
|
await microsoftTeamsService.start();
|
||||||
|
|
||||||
// inject all services
|
// inject all services
|
||||||
server.decorate<FastifyZodProvider["services"]>("services", {
|
server.decorate<FastifyZodProvider["services"]>("services", {
|
||||||
@@ -1675,7 +1724,10 @@ export const registerRoutes = async (
|
|||||||
kmip: kmipService,
|
kmip: kmipService,
|
||||||
kmipOperation: kmipOperationService,
|
kmipOperation: kmipOperationService,
|
||||||
gateway: gatewayService,
|
gateway: gatewayService,
|
||||||
secretRotationV2: secretRotationV2Service
|
secretRotationV2: secretRotationV2Service,
|
||||||
|
microsoftTeams: microsoftTeamsService,
|
||||||
|
assumePrivileges: assumePrivilegeService,
|
||||||
|
githubOrgSync: githubOrgSyncConfigService
|
||||||
});
|
});
|
||||||
|
|
||||||
const cronJobs: CronJob[] = [];
|
const cronJobs: CronJob[] = [];
|
||||||
@@ -1696,6 +1748,7 @@ export const registerRoutes = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
await server.register(injectIdentity, { userDAL, serviceTokenDAL });
|
await server.register(injectIdentity, { userDAL, serviceTokenDAL });
|
||||||
|
await server.register(injectAssumePrivilege);
|
||||||
await server.register(injectPermission);
|
await server.register(injectPermission);
|
||||||
await server.register(injectRateLimits);
|
await server.register(injectRateLimits);
|
||||||
await server.register(injectAuditLogInfo);
|
await server.register(injectAuditLogInfo);
|
||||||
@@ -1735,30 +1788,6 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
logger.info(`Raw event loop stats: ${JSON.stringify(histogram, null, 2)}`);
|
logger.info(`Raw event loop stats: ${JSON.stringify(histogram, null, 2)}`);
|
||||||
|
|
||||||
// try {
|
|
||||||
// await db.raw("SELECT NOW()");
|
|
||||||
// } catch (err) {
|
|
||||||
// logger.error("Health check: database connection failed", err);
|
|
||||||
// return reply.code(503).send({
|
|
||||||
// date: new Date(),
|
|
||||||
// message: "Service unavailable"
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (cfg.isRedisConfigured) {
|
|
||||||
// const redis = new Redis(cfg.REDIS_URL);
|
|
||||||
// try {
|
|
||||||
// await redis.ping();
|
|
||||||
// redis.disconnect();
|
|
||||||
// } catch (err) {
|
|
||||||
// logger.error("Health check: redis connection failed", err);
|
|
||||||
// return reply.code(503).send({
|
|
||||||
// date: new Date(),
|
|
||||||
// message: "Service unavailable"
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: "Ok",
|
message: "Ok",
|
||||||
|
@@ -27,7 +27,10 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
encryptedSlackClientId: true,
|
encryptedSlackClientId: true,
|
||||||
encryptedSlackClientSecret: true
|
encryptedSlackClientSecret: true,
|
||||||
|
encryptedMicrosoftTeamsAppId: true,
|
||||||
|
encryptedMicrosoftTeamsClientSecret: true,
|
||||||
|
encryptedMicrosoftTeamsBotId: true
|
||||||
}).extend({
|
}).extend({
|
||||||
isMigrationModeOn: z.boolean(),
|
isMigrationModeOn: z.boolean(),
|
||||||
defaultAuthOrgSlug: z.string().nullable(),
|
defaultAuthOrgSlug: z.string().nullable(),
|
||||||
@@ -74,6 +77,9 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
slackClientId: z.string().optional(),
|
slackClientId: z.string().optional(),
|
||||||
slackClientSecret: z.string().optional(),
|
slackClientSecret: z.string().optional(),
|
||||||
|
microsoftTeamsAppId: z.string().optional(),
|
||||||
|
microsoftTeamsClientSecret: z.string().optional(),
|
||||||
|
microsoftTeamsBotId: z.string().optional(),
|
||||||
authConsentContent: z
|
authConsentContent: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -197,15 +203,22 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/integrations/slack/config",
|
url: "/integrations",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
clientId: z.string(),
|
slack: z.object({
|
||||||
clientSecret: z.string()
|
clientId: z.string(),
|
||||||
|
clientSecret: z.string()
|
||||||
|
}),
|
||||||
|
microsoftTeams: z.object({
|
||||||
|
appId: z.string(),
|
||||||
|
clientSecret: z.string(),
|
||||||
|
botId: z.string()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -215,9 +228,9 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
const adminSlackConfig = await server.services.superAdmin.getAdminSlackConfig();
|
const adminIntegrationsConfig = await server.services.superAdmin.getAdminIntegrationsConfig();
|
||||||
|
|
||||||
return adminSlackConfig;
|
return adminIntegrationsConfig;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -10,6 +10,10 @@ import {
|
|||||||
AzureAppConfigurationConnectionListItemSchema,
|
AzureAppConfigurationConnectionListItemSchema,
|
||||||
SanitizedAzureAppConfigurationConnectionSchema
|
SanitizedAzureAppConfigurationConnectionSchema
|
||||||
} from "@app/services/app-connection/azure-app-configuration";
|
} from "@app/services/app-connection/azure-app-configuration";
|
||||||
|
import {
|
||||||
|
AzureClientSecretsConnectionListItemSchema,
|
||||||
|
SanitizedAzureClientSecretsConnectionSchema
|
||||||
|
} from "@app/services/app-connection/azure-client-secrets";
|
||||||
import {
|
import {
|
||||||
AzureKeyVaultConnectionListItemSchema,
|
AzureKeyVaultConnectionListItemSchema,
|
||||||
SanitizedAzureKeyVaultConnectionSchema
|
SanitizedAzureKeyVaultConnectionSchema
|
||||||
@@ -24,10 +28,15 @@ import {
|
|||||||
} from "@app/services/app-connection/databricks";
|
} from "@app/services/app-connection/databricks";
|
||||||
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
|
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
|
||||||
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
|
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
|
||||||
|
import {
|
||||||
|
HCVaultConnectionListItemSchema,
|
||||||
|
SanitizedHCVaultConnectionSchema
|
||||||
|
} from "@app/services/app-connection/hc-vault";
|
||||||
import {
|
import {
|
||||||
HumanitecConnectionListItemSchema,
|
HumanitecConnectionListItemSchema,
|
||||||
SanitizedHumanitecConnectionSchema
|
SanitizedHumanitecConnectionSchema
|
||||||
} from "@app/services/app-connection/humanitec";
|
} from "@app/services/app-connection/humanitec";
|
||||||
|
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
|
||||||
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
|
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
|
||||||
import {
|
import {
|
||||||
PostgresConnectionListItemSchema,
|
PostgresConnectionListItemSchema,
|
||||||
@@ -62,8 +71,11 @@ const SanitizedAppConnectionSchema = z.union([
|
|||||||
...SanitizedPostgresConnectionSchema.options,
|
...SanitizedPostgresConnectionSchema.options,
|
||||||
...SanitizedMsSqlConnectionSchema.options,
|
...SanitizedMsSqlConnectionSchema.options,
|
||||||
...SanitizedCamundaConnectionSchema.options,
|
...SanitizedCamundaConnectionSchema.options,
|
||||||
...SanitizedWindmillConnectionSchema.options,
|
|
||||||
...SanitizedAuth0ConnectionSchema.options,
|
...SanitizedAuth0ConnectionSchema.options,
|
||||||
|
...SanitizedHCVaultConnectionSchema.options,
|
||||||
|
...SanitizedAzureClientSecretsConnectionSchema.options,
|
||||||
|
...SanitizedWindmillConnectionSchema.options,
|
||||||
|
...SanitizedLdapConnectionSchema.options,
|
||||||
...SanitizedTeamCityConnectionSchema.options
|
...SanitizedTeamCityConnectionSchema.options
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -80,8 +92,11 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|||||||
PostgresConnectionListItemSchema,
|
PostgresConnectionListItemSchema,
|
||||||
MsSqlConnectionListItemSchema,
|
MsSqlConnectionListItemSchema,
|
||||||
CamundaConnectionListItemSchema,
|
CamundaConnectionListItemSchema,
|
||||||
WindmillConnectionListItemSchema,
|
|
||||||
Auth0ConnectionListItemSchema,
|
Auth0ConnectionListItemSchema,
|
||||||
|
HCVaultConnectionListItemSchema,
|
||||||
|
AzureClientSecretsConnectionListItemSchema,
|
||||||
|
WindmillConnectionListItemSchema,
|
||||||
|
LdapConnectionListItemSchema,
|
||||||
TeamCityConnectionListItemSchema
|
TeamCityConnectionListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -0,0 +1,49 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateAzureClientSecretsConnectionSchema,
|
||||||
|
SanitizedAzureClientSecretsConnectionSchema,
|
||||||
|
UpdateAzureClientSecretsConnectionSchema
|
||||||
|
} from "@app/services/app-connection/azure-client-secrets";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerAzureClientSecretsConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.AzureClientSecrets,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedAzureClientSecretsConnectionSchema,
|
||||||
|
createSchema: CreateAzureClientSecretsConnectionSchema,
|
||||||
|
updateSchema: UpdateAzureClientSecretsConnectionSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/clients`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
clients: z.object({ name: z.string(), id: z.string(), appId: z.string() }).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const clients = await server.services.appConnection.azureClientSecrets.listApps(connectionId, req.permission);
|
||||||
|
|
||||||
|
return { clients };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -0,0 +1,47 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateHCVaultConnectionSchema,
|
||||||
|
SanitizedHCVaultConnectionSchema,
|
||||||
|
UpdateHCVaultConnectionSchema
|
||||||
|
} from "@app/services/app-connection/hc-vault";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerHCVaultConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.HCVault,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedHCVaultConnectionSchema,
|
||||||
|
createSchema: CreateHCVaultConnectionSchema,
|
||||||
|
updateSchema: UpdateHCVaultConnectionSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
// The following endpoints are for internal Infisical App use only and not part of the public API
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/mounts`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.string().array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const mounts = await server.services.appConnection.hcvault.listMounts(connectionId, req.permission);
|
||||||
|
return mounts;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -1,14 +1,17 @@
|
|||||||
import { registerAuth0ConnectionRouter } from "@app/server/routes/v1/app-connection-routers/auth0-connection-router";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
import { registerAuth0ConnectionRouter } from "./auth0-connection-router";
|
||||||
import { registerAwsConnectionRouter } from "./aws-connection-router";
|
import { registerAwsConnectionRouter } from "./aws-connection-router";
|
||||||
import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router";
|
import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router";
|
||||||
|
import { registerAzureClientSecretsConnectionRouter } from "./azure-client-secrets-connection-router";
|
||||||
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
|
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
|
||||||
import { registerCamundaConnectionRouter } from "./camunda-connection-router";
|
import { registerCamundaConnectionRouter } from "./camunda-connection-router";
|
||||||
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
||||||
import { registerGcpConnectionRouter } from "./gcp-connection-router";
|
import { registerGcpConnectionRouter } from "./gcp-connection-router";
|
||||||
import { registerGitHubConnectionRouter } from "./github-connection-router";
|
import { registerGitHubConnectionRouter } from "./github-connection-router";
|
||||||
|
import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router";
|
||||||
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
||||||
|
import { registerLdapConnectionRouter } from "./ldap-connection-router";
|
||||||
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
|
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
|
||||||
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
|
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
|
||||||
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
|
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
|
||||||
@@ -25,6 +28,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.GCP]: registerGcpConnectionRouter,
|
[AppConnection.GCP]: registerGcpConnectionRouter,
|
||||||
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
|
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
|
||||||
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter,
|
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter,
|
||||||
|
[AppConnection.AzureClientSecrets]: registerAzureClientSecretsConnectionRouter,
|
||||||
[AppConnection.Databricks]: registerDatabricksConnectionRouter,
|
[AppConnection.Databricks]: registerDatabricksConnectionRouter,
|
||||||
[AppConnection.Humanitec]: registerHumanitecConnectionRouter,
|
[AppConnection.Humanitec]: registerHumanitecConnectionRouter,
|
||||||
[AppConnection.TerraformCloud]: registerTerraformCloudConnectionRouter,
|
[AppConnection.TerraformCloud]: registerTerraformCloudConnectionRouter,
|
||||||
@@ -34,5 +38,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.Camunda]: registerCamundaConnectionRouter,
|
[AppConnection.Camunda]: registerCamundaConnectionRouter,
|
||||||
[AppConnection.Windmill]: registerWindmillConnectionRouter,
|
[AppConnection.Windmill]: registerWindmillConnectionRouter,
|
||||||
[AppConnection.Auth0]: registerAuth0ConnectionRouter,
|
[AppConnection.Auth0]: registerAuth0ConnectionRouter,
|
||||||
|
[AppConnection.HCVault]: registerHCVaultConnectionRouter,
|
||||||
|
[AppConnection.LDAP]: registerLdapConnectionRouter,
|
||||||
[AppConnection.TeamCity]: registerTeamCityConnectionRouter
|
[AppConnection.TeamCity]: registerTeamCityConnectionRouter
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateLdapConnectionSchema,
|
||||||
|
SanitizedLdapConnectionSchema,
|
||||||
|
UpdateLdapConnectionSchema
|
||||||
|
} from "@app/services/app-connection/ldap";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerLdapConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.LDAP,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedLdapConnectionSchema,
|
||||||
|
createSchema: CreateLdapConnectionSchema,
|
||||||
|
updateSchema: UpdateLdapConnectionSchema
|
||||||
|
});
|
||||||
|
};
|
@@ -2,6 +2,7 @@ import jwt from "jsonwebtoken";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { getMinExpiresIn } from "@app/lib/fn";
|
||||||
import { authRateLimit, writeLimit } 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, AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthMode, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
@@ -33,6 +34,14 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
|
|||||||
secure: appCfg.HTTPS_ENABLED
|
secure: appCfg.HTTPS_ENABLED
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void res.cookie("infisical-project-assume-privileges", "", {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED,
|
||||||
|
maxAge: 0
|
||||||
|
});
|
||||||
|
|
||||||
return { message: "Successfully logged out" };
|
return { message: "Successfully logged out" };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -71,6 +80,18 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { decodedToken, tokenVersion } = await server.services.authToken.validateRefreshToken(req.cookies.jid);
|
const { decodedToken, tokenVersion } = await server.services.authToken.validateRefreshToken(req.cookies.jid);
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
let expiresIn: string | number = appCfg.JWT_AUTH_LIFETIME;
|
||||||
|
if (decodedToken.organizationId) {
|
||||||
|
const org = await server.services.org.findOrganizationById(
|
||||||
|
decodedToken.userId,
|
||||||
|
decodedToken.organizationId,
|
||||||
|
decodedToken.authMethod,
|
||||||
|
decodedToken.organizationId
|
||||||
|
);
|
||||||
|
if (org && org.userTokenExpiration) {
|
||||||
|
expiresIn = getMinExpiresIn(appCfg.JWT_AUTH_LIFETIME, org.userTokenExpiration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
{
|
{
|
||||||
@@ -84,7 +105,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
|
|||||||
mfaMethod: decodedToken.mfaMethod
|
mfaMethod: decodedToken.mfaMethod
|
||||||
},
|
},
|
||||||
appCfg.AUTH_SECRET,
|
appCfg.AUTH_SECRET,
|
||||||
{ expiresIn: appCfg.JWT_AUTH_LIFETIME }
|
{ expiresIn }
|
||||||
);
|
);
|
||||||
|
|
||||||
return { token, organizationId: decodedToken.organizationId };
|
return { token, organizationId: decodedToken.organizationId };
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretFoldersSchema, SecretImportsSchema } from "@app/db/schemas";
|
import { SecretFoldersSchema, SecretImportsSchema, UsersSchema } 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 { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
||||||
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
||||||
@@ -154,7 +154,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
secrets: z
|
secrets: z
|
||||||
.object({
|
.object({
|
||||||
secretId: z.string(),
|
secretId: z.string(),
|
||||||
referencedSecretKey: z.string()
|
referencedSecretKey: z.string(),
|
||||||
|
referencedSecretEnv: z.string()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional()
|
.optional()
|
||||||
@@ -166,6 +167,16 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
usedBySecretSyncs: z
|
||||||
|
.object({
|
||||||
|
name: z.string(),
|
||||||
|
destination: z.string(),
|
||||||
|
environment: z.string(),
|
||||||
|
id: z.string(),
|
||||||
|
path: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional(),
|
||||||
totalFolderCount: z.number().optional(),
|
totalFolderCount: z.number().optional(),
|
||||||
totalDynamicSecretCount: z.number().optional(),
|
totalDynamicSecretCount: z.number().optional(),
|
||||||
totalSecretCount: z.number().optional(),
|
totalSecretCount: z.number().optional(),
|
||||||
@@ -500,6 +511,24 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const usedBySecretSyncs: { name: string; destination: string; environment: string; id: string; path: string }[] =
|
||||||
|
[];
|
||||||
|
for await (const environment of environments) {
|
||||||
|
const secretSyncs = await server.services.secretSync.listSecretSyncsBySecretPath(
|
||||||
|
{ projectId, secretPath, environment },
|
||||||
|
req.permission
|
||||||
|
);
|
||||||
|
secretSyncs.forEach((sync) => {
|
||||||
|
usedBySecretSyncs.push({
|
||||||
|
name: sync.name,
|
||||||
|
destination: sync.destination,
|
||||||
|
environment,
|
||||||
|
id: sync.id,
|
||||||
|
path: sync.folder?.path || "/"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
folders,
|
folders,
|
||||||
dynamicSecrets,
|
dynamicSecrets,
|
||||||
@@ -512,6 +541,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
totalSecretCount,
|
totalSecretCount,
|
||||||
totalSecretRotationCount,
|
totalSecretRotationCount,
|
||||||
importedByEnvs,
|
importedByEnvs,
|
||||||
|
usedBySecretSyncs,
|
||||||
totalCount:
|
totalCount:
|
||||||
(totalFolderCount ?? 0) +
|
(totalFolderCount ?? 0) +
|
||||||
(totalDynamicSecretCount ?? 0) +
|
(totalDynamicSecretCount ?? 0) +
|
||||||
@@ -594,6 +624,12 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional(),
|
.optional(),
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
|
secretReminderRecipients: z
|
||||||
|
.object({
|
||||||
|
user: UsersSchema.pick({ id: true, email: true, username: true }),
|
||||||
|
id: z.string()
|
||||||
|
})
|
||||||
|
.array(),
|
||||||
secretValueHidden: z.boolean(),
|
secretValueHidden: z.boolean(),
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
@@ -605,6 +641,16 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
totalFolderCount: z.number().optional(),
|
totalFolderCount: z.number().optional(),
|
||||||
totalDynamicSecretCount: z.number().optional(),
|
totalDynamicSecretCount: z.number().optional(),
|
||||||
totalSecretCount: z.number().optional(),
|
totalSecretCount: z.number().optional(),
|
||||||
|
usedBySecretSyncs: z
|
||||||
|
.object({
|
||||||
|
name: z.string(),
|
||||||
|
destination: z.string(),
|
||||||
|
environment: z.string(),
|
||||||
|
id: z.string(),
|
||||||
|
path: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional(),
|
||||||
importedBy: z
|
importedBy: z
|
||||||
.object({
|
.object({
|
||||||
environment: z.object({
|
environment: z.object({
|
||||||
@@ -618,7 +664,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
secrets: z
|
secrets: z
|
||||||
.object({
|
.object({
|
||||||
secretId: z.string(),
|
secretId: z.string(),
|
||||||
referencedSecretKey: z.string()
|
referencedSecretKey: z.string(),
|
||||||
|
referencedSecretEnv: z.string()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional()
|
.optional()
|
||||||
@@ -898,6 +945,18 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
secrets
|
secrets
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const secretSyncs = await server.services.secretSync.listSecretSyncsBySecretPath(
|
||||||
|
{ projectId, secretPath, environment },
|
||||||
|
req.permission
|
||||||
|
);
|
||||||
|
const usedBySecretSyncs = secretSyncs.map((sync) => ({
|
||||||
|
name: sync.name,
|
||||||
|
destination: sync.destination,
|
||||||
|
environment: sync.environment?.name || environment,
|
||||||
|
id: sync.id,
|
||||||
|
path: sync.folder?.path || "/"
|
||||||
|
}));
|
||||||
|
|
||||||
if (secrets?.length || secretRotations?.length) {
|
if (secrets?.length || secretRotations?.length) {
|
||||||
const secretCount =
|
const secretCount =
|
||||||
(secrets?.length ?? 0) +
|
(secrets?.length ?? 0) +
|
||||||
@@ -944,6 +1003,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
totalSecretCount,
|
totalSecretCount,
|
||||||
totalSecretRotationCount,
|
totalSecretRotationCount,
|
||||||
importedBy,
|
importedBy,
|
||||||
|
usedBySecretSyncs,
|
||||||
totalCount:
|
totalCount:
|
||||||
(totalImportCount ?? 0) +
|
(totalImportCount ?? 0) +
|
||||||
(totalFolderCount ?? 0) +
|
(totalFolderCount ?? 0) +
|
||||||
|
@@ -47,6 +47,7 @@ import { registerUserEngagementRouter } from "./user-engagement-router";
|
|||||||
import { registerUserRouter } from "./user-router";
|
import { registerUserRouter } from "./user-router";
|
||||||
import { registerWebhookRouter } from "./webhook-router";
|
import { registerWebhookRouter } from "./webhook-router";
|
||||||
import { registerWorkflowIntegrationRouter } from "./workflow-integration-router";
|
import { registerWorkflowIntegrationRouter } from "./workflow-integration-router";
|
||||||
|
import { registerMicrosoftTeamsRouter } from "./microsoft-teams-router";
|
||||||
|
|
||||||
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
||||||
await server.register(registerSsoRouter, { prefix: "/sso" });
|
await server.register(registerSsoRouter, { prefix: "/sso" });
|
||||||
@@ -79,6 +80,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
async (workflowIntegrationRouter) => {
|
async (workflowIntegrationRouter) => {
|
||||||
await workflowIntegrationRouter.register(registerWorkflowIntegrationRouter);
|
await workflowIntegrationRouter.register(registerWorkflowIntegrationRouter);
|
||||||
await workflowIntegrationRouter.register(registerSlackRouter, { prefix: "/slack" });
|
await workflowIntegrationRouter.register(registerSlackRouter, { prefix: "/slack" });
|
||||||
|
await workflowIntegrationRouter.register(registerMicrosoftTeamsRouter, { prefix: "/microsoft-teams" });
|
||||||
},
|
},
|
||||||
{ prefix: "/workflow-integrations" }
|
{ prefix: "/workflow-integrations" }
|
||||||
);
|
);
|
||||||
|
381
backend/src/server/routes/v1/microsoft-teams-router.ts
Normal file
381
backend/src/server/routes/v1/microsoft-teams-router.ts
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { MicrosoftTeamsIntegrationsSchema, WorkflowIntegrationsSchema } from "@app/db/schemas";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { WorkflowIntegrationStatus } from "@app/services/workflow-integration/workflow-integration-types";
|
||||||
|
|
||||||
|
const sanitizedMicrosoftTeamsIntegrationSchema = WorkflowIntegrationsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
description: true,
|
||||||
|
slug: true,
|
||||||
|
integration: true
|
||||||
|
}).merge(
|
||||||
|
MicrosoftTeamsIntegrationsSchema.pick({
|
||||||
|
tenantId: true
|
||||||
|
}).extend({
|
||||||
|
status: z.nativeEnum(WorkflowIntegrationStatus)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const registerMicrosoftTeamsRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/client-id",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
clientId: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const clientId = await server.services.microsoftTeams.getClientId({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
clientId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
redirectUri: z.string(),
|
||||||
|
tenantId: z.string().uuid(),
|
||||||
|
slug: z.string(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
code: z.string().trim()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
await server.services.microsoftTeams.completeMicrosoftTeamsIntegration({
|
||||||
|
tenantId: req.body.tenantId,
|
||||||
|
slug: req.body.slug,
|
||||||
|
description: req.body.description,
|
||||||
|
redirectUri: req.body.redirectUri,
|
||||||
|
code: req.body.code,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CREATE,
|
||||||
|
metadata: {
|
||||||
|
tenantId: req.body.tenantId,
|
||||||
|
slug: req.body.slug,
|
||||||
|
description: req.body.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
response: {
|
||||||
|
200: sanitizedMicrosoftTeamsIntegrationSchema.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const microsoftTeamsIntegrations = await server.services.microsoftTeams.getMicrosoftTeamsIntegrationsByOrg({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST,
|
||||||
|
metadata: {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return microsoftTeamsIntegrations;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:id/installation-status",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const microsoftTeamsIntegration = await server.services.microsoftTeams.checkInstallationStatus({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
workflowIntegrationId: req.params.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CHECK_INSTALLATION_STATUS,
|
||||||
|
metadata: {
|
||||||
|
tenantId: microsoftTeamsIntegration.tenantId,
|
||||||
|
slug: microsoftTeamsIntegration.slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedMicrosoftTeamsIntegrationSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const deletedMicrosoftTeamsIntegration = await server.services.microsoftTeams.deleteMicrosoftTeamsIntegration({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_DELETE,
|
||||||
|
metadata: {
|
||||||
|
tenantId: deletedMicrosoftTeamsIntegration.tenantId,
|
||||||
|
slug: deletedMicrosoftTeamsIntegration.slug,
|
||||||
|
id: deletedMicrosoftTeamsIntegration.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return deletedMicrosoftTeamsIntegration;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedMicrosoftTeamsIntegrationSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const microsoftTeamsIntegration = await server.services.microsoftTeams.getMicrosoftTeamsIntegrationById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET,
|
||||||
|
metadata: {
|
||||||
|
slug: microsoftTeamsIntegration.slug,
|
||||||
|
id: microsoftTeamsIntegration.id,
|
||||||
|
tenantId: microsoftTeamsIntegration.tenantId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return microsoftTeamsIntegration;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
slug: slugSchema({ max: 64 }).optional(),
|
||||||
|
description: z.string().optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedMicrosoftTeamsIntegrationSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const microsoftTeamsIntegration = await server.services.microsoftTeams.updateMicrosoftTeamsIntegration({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.id,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_UPDATE,
|
||||||
|
metadata: {
|
||||||
|
slug: microsoftTeamsIntegration.slug,
|
||||||
|
id: microsoftTeamsIntegration.id,
|
||||||
|
tenantId: microsoftTeamsIntegration.tenantId,
|
||||||
|
newSlug: req.body.slug,
|
||||||
|
newDescription: req.body.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return microsoftTeamsIntegration;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:workflowIntegrationId/teams",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
workflowIntegrationId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z
|
||||||
|
.object({
|
||||||
|
teamId: z.string(),
|
||||||
|
teamName: z.string(),
|
||||||
|
channels: z
|
||||||
|
.object({
|
||||||
|
channelName: z.string(),
|
||||||
|
channelId: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const microsoftTeamsIntegration = await server.services.microsoftTeams.getTeams({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
workflowIntegrationId: req.params.workflowIntegrationId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS,
|
||||||
|
metadata: {
|
||||||
|
tenantId: microsoftTeamsIntegration.tenantId,
|
||||||
|
slug: microsoftTeamsIntegration.slug,
|
||||||
|
id: microsoftTeamsIntegration.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return microsoftTeamsIntegration.teams;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/message-endpoint",
|
||||||
|
schema: {
|
||||||
|
body: z.any(),
|
||||||
|
response: {
|
||||||
|
200: z.any()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req, res) => {
|
||||||
|
await server.services.microsoftTeams.handleMessageEndpoint(req, res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -1,3 +1,4 @@
|
|||||||
|
import RE2 from "re2";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -263,7 +264,18 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
enforceMfa: z.boolean().optional(),
|
enforceMfa: z.boolean().optional(),
|
||||||
selectedMfaMethod: z.nativeEnum(MfaMethod).optional(),
|
selectedMfaMethod: z.nativeEnum(MfaMethod).optional(),
|
||||||
allowSecretSharingOutsideOrganization: z.boolean().optional(),
|
allowSecretSharingOutsideOrganization: z.boolean().optional(),
|
||||||
bypassOrgAuthEnabled: z.boolean().optional()
|
bypassOrgAuthEnabled: z.boolean().optional(),
|
||||||
|
userTokenExpiration: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => new RE2(/^\d+[mhdw]$/).test(val), "Must be a number followed by m, h, d, or w")
|
||||||
|
.refine(
|
||||||
|
(val) => {
|
||||||
|
const numericPart = val.slice(0, -1);
|
||||||
|
return parseInt(numericPart, 10) >= 1;
|
||||||
|
},
|
||||||
|
{ message: "Duration value must be at least 1" }
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@@ -14,6 +14,7 @@ import {
|
|||||||
UserEncryptionKeysSchema,
|
UserEncryptionKeysSchema,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import { ProjectMicrosoftTeamsConfigsSchema } from "@app/db/schemas/project-microsoft-teams-configs";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ApiDocsTags, PROJECTS } from "@app/lib/api-docs";
|
import { ApiDocsTags, PROJECTS } from "@app/lib/api-docs";
|
||||||
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
|
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
|
||||||
@@ -21,8 +22,10 @@ import { re2Validator } from "@app/lib/zod";
|
|||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { validateMicrosoftTeamsChannelsSchema } from "@app/services/microsoft-teams/microsoft-teams-fns";
|
||||||
import { ProjectFilterType, SearchProjectSortBy } from "@app/services/project/project-types";
|
import { ProjectFilterType, SearchProjectSortBy } from "@app/services/project/project-types";
|
||||||
import { validateSlackChannelsField } from "@app/services/slack/slack-auth-validators";
|
import { validateSlackChannelsField } from "@app/services/slack/slack-auth-validators";
|
||||||
|
import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types";
|
||||||
|
|
||||||
import { integrationAuthPubSchema, SanitizedProjectSchema } from "../sanitizedSchemas";
|
import { integrationAuthPubSchema, SanitizedProjectSchema } from "../sanitizedSchemas";
|
||||||
import { sanitizedServiceTokenSchema } from "../v2/service-token-router";
|
import { sanitizedServiceTokenSchema } from "../v2/service-token-router";
|
||||||
@@ -740,55 +743,112 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:workspaceId/slack-config",
|
url: "/:workspaceId/workflow-integration-config/:integration",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim(),
|
||||||
|
integration: z.nativeEnum(WorkflowIntegration)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: ProjectSlackConfigsSchema.pick({
|
200: z.discriminatedUnion("integration", [
|
||||||
id: true,
|
ProjectSlackConfigsSchema.pick({
|
||||||
slackIntegrationId: true,
|
id: true,
|
||||||
isAccessRequestNotificationEnabled: true,
|
isAccessRequestNotificationEnabled: true,
|
||||||
accessRequestChannels: true,
|
accessRequestChannels: true,
|
||||||
isSecretRequestNotificationEnabled: true,
|
isSecretRequestNotificationEnabled: true,
|
||||||
secretRequestChannels: true
|
secretRequestChannels: true
|
||||||
|
}).merge(
|
||||||
|
z.object({
|
||||||
|
integration: z.literal(WorkflowIntegration.SLACK),
|
||||||
|
integrationId: z.string()
|
||||||
|
})
|
||||||
|
),
|
||||||
|
ProjectMicrosoftTeamsConfigsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
isAccessRequestNotificationEnabled: true,
|
||||||
|
accessRequestChannels: true,
|
||||||
|
isSecretRequestNotificationEnabled: true,
|
||||||
|
secretRequestChannels: true
|
||||||
|
}).merge(
|
||||||
|
z.object({
|
||||||
|
integration: z.literal(WorkflowIntegration.MICROSOFT_TEAMS),
|
||||||
|
integrationId: z.string()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const config = await server.services.project.getProjectWorkflowIntegrationConfig({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
projectId: req.params.workspaceId,
|
||||||
|
integration: req.params.integration
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: req.params.workspaceId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_PROJECT_WORKFLOW_INTEGRATION_CONFIG,
|
||||||
|
metadata: {
|
||||||
|
id: config.id,
|
||||||
|
integration: config.integration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:projectId/workflow-integration/:integration/:integrationId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
projectId: z.string().trim(),
|
||||||
|
integration: z.nativeEnum(WorkflowIntegration),
|
||||||
|
integrationId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
integrationConfig: z.object({
|
||||||
|
id: z.string()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const slackConfig = await server.services.project.getProjectSlackConfig({
|
const deletedIntegration = await server.services.project.deleteProjectWorkflowIntegration({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.params.workspaceId
|
projectId: req.params.projectId,
|
||||||
|
integration: req.params.integration,
|
||||||
|
integrationId: req.params.integrationId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (slackConfig) {
|
return {
|
||||||
await server.services.auditLog.createAuditLog({
|
integrationConfig: deletedIntegration
|
||||||
...req.auditLogInfo,
|
};
|
||||||
projectId: req.params.workspaceId,
|
|
||||||
event: {
|
|
||||||
type: EventType.GET_PROJECT_SLACK_CONFIG,
|
|
||||||
metadata: {
|
|
||||||
id: slackConfig.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return slackConfig;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
url: "/:workspaceId/slack-config",
|
url: "/:workspaceId/workflow-integration",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
@@ -796,27 +856,57 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
|
||||||
slackIntegrationId: z.string(),
|
body: z.discriminatedUnion("integration", [
|
||||||
isAccessRequestNotificationEnabled: z.boolean(),
|
z.object({
|
||||||
accessRequestChannels: validateSlackChannelsField,
|
integration: z.literal(WorkflowIntegration.SLACK),
|
||||||
isSecretRequestNotificationEnabled: z.boolean(),
|
integrationId: z.string(),
|
||||||
secretRequestChannels: validateSlackChannelsField
|
accessRequestChannels: validateSlackChannelsField,
|
||||||
}),
|
secretRequestChannels: validateSlackChannelsField,
|
||||||
response: {
|
isAccessRequestNotificationEnabled: z.boolean(),
|
||||||
200: ProjectSlackConfigsSchema.pick({
|
isSecretRequestNotificationEnabled: z.boolean()
|
||||||
id: true,
|
}),
|
||||||
slackIntegrationId: true,
|
z.object({
|
||||||
isAccessRequestNotificationEnabled: true,
|
integration: z.literal(WorkflowIntegration.MICROSOFT_TEAMS),
|
||||||
accessRequestChannels: true,
|
integrationId: z.string(),
|
||||||
isSecretRequestNotificationEnabled: true,
|
accessRequestChannels: validateMicrosoftTeamsChannelsSchema,
|
||||||
secretRequestChannels: true
|
secretRequestChannels: validateMicrosoftTeamsChannelsSchema,
|
||||||
|
isAccessRequestNotificationEnabled: z.boolean(),
|
||||||
|
isSecretRequestNotificationEnabled: z.boolean()
|
||||||
})
|
})
|
||||||
|
]),
|
||||||
|
response: {
|
||||||
|
200: z.discriminatedUnion("integration", [
|
||||||
|
ProjectSlackConfigsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
isAccessRequestNotificationEnabled: true,
|
||||||
|
accessRequestChannels: true,
|
||||||
|
isSecretRequestNotificationEnabled: true,
|
||||||
|
secretRequestChannels: true
|
||||||
|
}).merge(
|
||||||
|
z.object({
|
||||||
|
integration: z.literal(WorkflowIntegration.SLACK),
|
||||||
|
integrationId: z.string()
|
||||||
|
})
|
||||||
|
),
|
||||||
|
ProjectMicrosoftTeamsConfigsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
isAccessRequestNotificationEnabled: true,
|
||||||
|
isSecretRequestNotificationEnabled: true
|
||||||
|
}).merge(
|
||||||
|
z.object({
|
||||||
|
integration: z.literal(WorkflowIntegration.MICROSOFT_TEAMS),
|
||||||
|
integrationId: z.string(),
|
||||||
|
accessRequestChannels: validateMicrosoftTeamsChannelsSchema,
|
||||||
|
secretRequestChannels: validateMicrosoftTeamsChannelsSchema
|
||||||
|
})
|
||||||
|
)
|
||||||
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const slackConfig = await server.services.project.updateProjectSlackConfig({
|
const workflowIntegrationConfig = await server.services.project.updateProjectWorkflowIntegration({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@@ -829,19 +919,20 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
projectId: req.params.workspaceId,
|
projectId: req.params.workspaceId,
|
||||||
event: {
|
event: {
|
||||||
type: EventType.UPDATE_PROJECT_SLACK_CONFIG,
|
type: EventType.UPDATE_PROJECT_WORKFLOW_INTEGRATION_CONFIG,
|
||||||
metadata: {
|
metadata: {
|
||||||
id: slackConfig.id,
|
id: workflowIntegrationConfig.id,
|
||||||
slackIntegrationId: slackConfig.slackIntegrationId,
|
integrationId: workflowIntegrationConfig.integrationId,
|
||||||
isAccessRequestNotificationEnabled: slackConfig.isAccessRequestNotificationEnabled,
|
integration: workflowIntegrationConfig.integration,
|
||||||
accessRequestChannels: slackConfig.accessRequestChannels,
|
isAccessRequestNotificationEnabled: workflowIntegrationConfig.isAccessRequestNotificationEnabled,
|
||||||
isSecretRequestNotificationEnabled: slackConfig.isSecretRequestNotificationEnabled,
|
accessRequestChannels: workflowIntegrationConfig.accessRequestChannels,
|
||||||
secretRequestChannels: slackConfig.secretRequestChannels
|
isSecretRequestNotificationEnabled: workflowIntegrationConfig.isSecretRequestNotificationEnabled,
|
||||||
|
secretRequestChannels: workflowIntegrationConfig.secretRequestChannels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return slackConfig;
|
return workflowIntegrationConfig;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
CreateHCVaultSyncSchema,
|
||||||
|
HCVaultSyncSchema,
|
||||||
|
UpdateHCVaultSyncSchema
|
||||||
|
} from "@app/services/secret-sync/hc-vault";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
|
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||||
|
|
||||||
|
export const registerHCVaultSyncRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSyncSecretsEndpoints({
|
||||||
|
destination: SecretSync.HCVault,
|
||||||
|
server,
|
||||||
|
responseSchema: HCVaultSyncSchema,
|
||||||
|
createSchema: CreateHCVaultSyncSchema,
|
||||||
|
updateSchema: UpdateHCVaultSyncSchema
|
||||||
|
});
|
@@ -8,6 +8,7 @@ import { registerCamundaSyncRouter } from "./camunda-sync-router";
|
|||||||
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
||||||
import { registerGcpSyncRouter } from "./gcp-sync-router";
|
import { registerGcpSyncRouter } from "./gcp-sync-router";
|
||||||
import { registerGitHubSyncRouter } from "./github-sync-router";
|
import { registerGitHubSyncRouter } from "./github-sync-router";
|
||||||
|
import { registerHCVaultSyncRouter } from "./hc-vault-sync-router";
|
||||||
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
|
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
|
||||||
import { registerTeamCitySyncRouter } from "./teamcity-sync-router";
|
import { registerTeamCitySyncRouter } from "./teamcity-sync-router";
|
||||||
import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router";
|
import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router";
|
||||||
@@ -29,5 +30,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
|
|||||||
[SecretSync.Camunda]: registerCamundaSyncRouter,
|
[SecretSync.Camunda]: registerCamundaSyncRouter,
|
||||||
[SecretSync.Vercel]: registerVercelSyncRouter,
|
[SecretSync.Vercel]: registerVercelSyncRouter,
|
||||||
[SecretSync.Windmill]: registerWindmillSyncRouter,
|
[SecretSync.Windmill]: registerWindmillSyncRouter,
|
||||||
|
[SecretSync.HCVault]: registerHCVaultSyncRouter,
|
||||||
[SecretSync.TeamCity]: registerTeamCitySyncRouter
|
[SecretSync.TeamCity]: registerTeamCitySyncRouter
|
||||||
};
|
};
|
||||||
|
@@ -22,6 +22,7 @@ import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secr
|
|||||||
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
|
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
|
||||||
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
|
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
|
||||||
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
||||||
|
import { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault";
|
||||||
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
|
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
|
||||||
import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity";
|
import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity";
|
||||||
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
|
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
|
||||||
@@ -41,6 +42,7 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
|
|||||||
CamundaSyncSchema,
|
CamundaSyncSchema,
|
||||||
VercelSyncSchema,
|
VercelSyncSchema,
|
||||||
WindmillSyncSchema,
|
WindmillSyncSchema,
|
||||||
|
HCVaultSyncSchema,
|
||||||
TeamCitySyncSchema
|
TeamCitySyncSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -57,6 +59,7 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
|||||||
CamundaSyncListItemSchema,
|
CamundaSyncListItemSchema,
|
||||||
VercelSyncListItemSchema,
|
VercelSyncListItemSchema,
|
||||||
WindmillSyncListItemSchema,
|
WindmillSyncListItemSchema,
|
||||||
|
HCVaultSyncListItemSchema,
|
||||||
TeamCitySyncListItemSchema
|
TeamCitySyncListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -9,18 +9,21 @@
|
|||||||
import { Authenticator } from "@fastify/passport";
|
import { Authenticator } from "@fastify/passport";
|
||||||
import fastifySession from "@fastify/session";
|
import fastifySession from "@fastify/session";
|
||||||
import RedisStore from "connect-redis";
|
import RedisStore from "connect-redis";
|
||||||
import { Strategy as GitHubStrategy } from "passport-github";
|
|
||||||
import { Strategy as GitLabStrategy } from "passport-gitlab2";
|
import { Strategy as GitLabStrategy } from "passport-gitlab2";
|
||||||
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
|
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
|
||||||
|
import { Strategy as OAuth2Strategy } from "passport-oauth2";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { INFISICAL_PROVIDER_GITHUB_ACCESS_TOKEN } from "@app/lib/config/const";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { fetchGithubEmails } from "@app/lib/requests/github";
|
import { ms } from "@app/lib/ms";
|
||||||
|
import { fetchGithubEmails, fetchGithubUser } from "@app/lib/requests/github";
|
||||||
import { authRateLimit } from "@app/server/config/rateLimiter";
|
import { authRateLimit } from "@app/server/config/rateLimiter";
|
||||||
import { AuthMethod } from "@app/services/auth/auth-type";
|
import { AuthMethod } from "@app/services/auth/auth-type";
|
||||||
import { OrgAuthMethod } from "@app/services/org/org-types";
|
import { OrgAuthMethod } from "@app/services/org/org-types";
|
||||||
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
|
|
||||||
export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@@ -42,6 +45,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
await server.register(passport.initialize());
|
await server.register(passport.initialize());
|
||||||
await server.register(passport.secureSession());
|
await server.register(passport.secureSession());
|
||||||
|
|
||||||
// passport oauth strategy for Google
|
// passport oauth strategy for Google
|
||||||
const isGoogleOauthActive = Boolean(appCfg.CLIENT_ID_GOOGLE_LOGIN && appCfg.CLIENT_SECRET_GOOGLE_LOGIN);
|
const isGoogleOauthActive = Boolean(appCfg.CLIENT_ID_GOOGLE_LOGIN && appCfg.CLIENT_SECRET_GOOGLE_LOGIN);
|
||||||
if (isGoogleOauthActive) {
|
if (isGoogleOauthActive) {
|
||||||
@@ -52,8 +56,9 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
clientID: appCfg.CLIENT_ID_GOOGLE_LOGIN as string,
|
clientID: appCfg.CLIENT_ID_GOOGLE_LOGIN as string,
|
||||||
clientSecret: appCfg.CLIENT_SECRET_GOOGLE_LOGIN as string,
|
clientSecret: appCfg.CLIENT_SECRET_GOOGLE_LOGIN as string,
|
||||||
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/google`,
|
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/google`,
|
||||||
scope: ["profile", " email"],
|
scope: ["profile", "email"],
|
||||||
state: true
|
state: true,
|
||||||
|
pkce: true
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
async (req, _accessToken, _refreshToken, profile, cb) => {
|
async (req, _accessToken, _refreshToken, profile, cb) => {
|
||||||
@@ -89,34 +94,44 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
const isGithubOauthActive = Boolean(appCfg.CLIENT_SECRET_GITHUB_LOGIN && appCfg.CLIENT_ID_GITHUB_LOGIN);
|
const isGithubOauthActive = Boolean(appCfg.CLIENT_SECRET_GITHUB_LOGIN && appCfg.CLIENT_ID_GITHUB_LOGIN);
|
||||||
if (isGithubOauthActive) {
|
if (isGithubOauthActive) {
|
||||||
passport.use(
|
passport.use(
|
||||||
new GitHubStrategy(
|
"github",
|
||||||
|
new OAuth2Strategy(
|
||||||
{
|
{
|
||||||
passReqToCallback: true,
|
authorizationURL: "https://github.com/login/oauth/authorize",
|
||||||
clientID: appCfg.CLIENT_ID_GITHUB_LOGIN as string,
|
tokenURL: "https://github.com/login/oauth/access_token",
|
||||||
clientSecret: appCfg.CLIENT_SECRET_GITHUB_LOGIN as string,
|
clientID: appCfg.CLIENT_ID_GITHUB_LOGIN!,
|
||||||
|
clientSecret: appCfg.CLIENT_SECRET_GITHUB_LOGIN!,
|
||||||
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/github`,
|
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/github`,
|
||||||
scope: ["user:email"],
|
scope: ["user:email", "read:org"],
|
||||||
// akhilmhdh: because the ts type for this is outdated by the maintainer
|
state: true,
|
||||||
state: true as unknown as string
|
pkce: true,
|
||||||
|
passReqToCallback: true
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
async (req, accessToken, _refreshToken, profile, cb) => {
|
async (req: any, accessToken: string, _refreshToken: string, _profile: any, done: Function) => {
|
||||||
// @ts-expect-error this is because this is express type and not fastify
|
|
||||||
const callbackPort = req.session.get("callbackPort");
|
|
||||||
try {
|
try {
|
||||||
const ghEmails = await fetchGithubEmails(accessToken);
|
const ghEmails = await fetchGithubEmails(accessToken);
|
||||||
const { email } = ghEmails.filter((gitHubEmail) => gitHubEmail.primary)[0];
|
const { email } = ghEmails.filter((gitHubEmail) => gitHubEmail.primary)[0];
|
||||||
|
|
||||||
|
if (!email) throw new Error("No primary email found");
|
||||||
|
|
||||||
|
// profile does not get automatically populated so we need to manually fetch user info
|
||||||
|
const user = await fetchGithubUser(accessToken);
|
||||||
|
|
||||||
|
const callbackPort = req.session.get("callbackPort");
|
||||||
|
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
|
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
|
||||||
email,
|
email,
|
||||||
firstName: profile.displayName || profile.username || "",
|
firstName: user.name || user.login,
|
||||||
lastName: "",
|
lastName: "",
|
||||||
authMethod: AuthMethod.GITHUB,
|
authMethod: AuthMethod.GITHUB,
|
||||||
callbackPort
|
callbackPort
|
||||||
});
|
});
|
||||||
return cb(null, { isUserCompleted, providerAuthToken });
|
|
||||||
} catch (error) {
|
done(null, { isUserCompleted, providerAuthToken, externalProviderAccessToken: accessToken });
|
||||||
logger.error(error);
|
} catch (err) {
|
||||||
cb(error as Error, false);
|
logger.error(err);
|
||||||
|
done(err as Error, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -136,7 +151,8 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
clientSecret: appCfg.CLIENT_SECRET_GITLAB_LOGIN,
|
clientSecret: appCfg.CLIENT_SECRET_GITLAB_LOGIN,
|
||||||
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/gitlab`,
|
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/gitlab`,
|
||||||
baseURL: appCfg.CLIENT_GITLAB_LOGIN_URL,
|
baseURL: appCfg.CLIENT_GITLAB_LOGIN_URL,
|
||||||
state: true
|
state: true,
|
||||||
|
pkce: true
|
||||||
},
|
},
|
||||||
async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => {
|
async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => {
|
||||||
try {
|
try {
|
||||||
@@ -166,17 +182,24 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
callback_port: z.string().optional()
|
callback_port: z.string().optional(),
|
||||||
|
is_admin_login: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => val === "true")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
preValidation: [
|
preValidation: [
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { callback_port: callbackPort } = req.query;
|
const { callback_port: callbackPort, is_admin_login: isAdminLogin } = req.query;
|
||||||
// ensure fresh session state per login attempt
|
// ensure fresh session state per login attempt
|
||||||
await req.session.regenerate();
|
await req.session.regenerate();
|
||||||
if (callbackPort) {
|
if (callbackPort) {
|
||||||
req.session.set("callbackPort", callbackPort);
|
req.session.set("callbackPort", callbackPort);
|
||||||
}
|
}
|
||||||
|
if (isAdminLogin) {
|
||||||
|
req.session.set("isAdminLogin", isAdminLogin);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
passport.authenticate("google", {
|
passport.authenticate("google", {
|
||||||
scope: ["profile", "email"],
|
scope: ["profile", "email"],
|
||||||
@@ -200,10 +223,13 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
// this is due to zod type difference
|
// this is due to zod type difference
|
||||||
}) as never,
|
}) as never,
|
||||||
handler: async (req, res) => {
|
handler: async (req, res) => {
|
||||||
|
const isAdminLogin = req.session.get("isAdminLogin");
|
||||||
await req.session.destroy();
|
await req.session.destroy();
|
||||||
if (req.passportUser.isUserCompleted) {
|
if (req.passportUser.isUserCompleted) {
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
|
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}${
|
||||||
|
isAdminLogin ? `&isAdminLogin=${isAdminLogin}` : ""
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
@@ -217,18 +243,26 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
callback_port: z.string().optional()
|
callback_port: z.string().optional(),
|
||||||
|
is_admin_login: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => val === "true")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
preValidation: [
|
preValidation: [
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { callback_port: callbackPort } = req.query;
|
const { callback_port: callbackPort, is_admin_login: isAdminLogin } = req.query;
|
||||||
// ensure fresh session state per login attempt
|
// ensure fresh session state per login attempt
|
||||||
await req.session.regenerate();
|
await req.session.regenerate();
|
||||||
if (callbackPort) {
|
if (callbackPort) {
|
||||||
req.session.set("callbackPort", callbackPort);
|
req.session.set("callbackPort", callbackPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAdminLogin) {
|
||||||
|
req.session.set("isAdminLogin", isAdminLogin);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
passport.authenticate("github", {
|
passport.authenticate("github", {
|
||||||
session: false,
|
session: false,
|
||||||
@@ -289,14 +323,32 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
// this is due to zod type difference
|
// this is due to zod type difference
|
||||||
}) as any,
|
}) as any,
|
||||||
handler: async (req, res) => {
|
handler: async (req, res) => {
|
||||||
|
const isAdminLogin = req.session.get("isAdminLogin");
|
||||||
await req.session.destroy();
|
await req.session.destroy();
|
||||||
|
|
||||||
|
if (req.passportUser.externalProviderAccessToken) {
|
||||||
|
void res.cookie(INFISICAL_PROVIDER_GITHUB_ACCESS_TOKEN, req.passportUser.externalProviderAccessToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED,
|
||||||
|
expires: new Date(Date.now() + ms(appCfg.JWT_PROVIDER_AUTH_LIFETIME))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (req.passportUser.isUserCompleted) {
|
if (req.passportUser.isUserCompleted) {
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
|
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}${
|
||||||
|
isAdminLogin ? `&isAdminLogin=${isAdminLogin}` : ""
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const serverCfg = await getServerCfg();
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
|
`${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}${
|
||||||
|
serverCfg.defaultAuthOrgId && !appCfg.isCloud ? `&defaultOrgAllowed=true` : ""
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -306,18 +358,26 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
callback_port: z.string().optional()
|
callback_port: z.string().optional(),
|
||||||
|
is_admin_login: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => val === "true")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
preValidation: [
|
preValidation: [
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { callback_port: callbackPort } = req.query;
|
const { callback_port: callbackPort, is_admin_login: isAdminLogin } = req.query;
|
||||||
// ensure fresh session state per login attempt
|
// ensure fresh session state per login attempt
|
||||||
await req.session.regenerate();
|
await req.session.regenerate();
|
||||||
if (callbackPort) {
|
if (callbackPort) {
|
||||||
req.session.set("callbackPort", callbackPort);
|
req.session.set("callbackPort", callbackPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAdminLogin) {
|
||||||
|
req.session.set("isAdminLogin", isAdminLogin);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
passport.authenticate("gitlab", {
|
passport.authenticate("gitlab", {
|
||||||
session: false,
|
session: false,
|
||||||
@@ -342,10 +402,13 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
}) as any,
|
}) as any,
|
||||||
handler: async (req, res) => {
|
handler: async (req, res) => {
|
||||||
|
const isAdminLogin = req.session.get("isAdminLogin");
|
||||||
await req.session.destroy();
|
await req.session.destroy();
|
||||||
if (req.passportUser.isUserCompleted) {
|
if (req.passportUser.isUserCompleted) {
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
|
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}${
|
||||||
|
isAdminLogin ? `&isAdminLogin=${isAdminLogin}` : ""
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
|
@@ -7,7 +7,8 @@ const sanitizedWorkflowIntegrationSchema = WorkflowIntegrationsSchema.pick({
|
|||||||
id: true,
|
id: true,
|
||||||
description: true,
|
description: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
integration: true
|
integration: true,
|
||||||
|
status: true
|
||||||
});
|
});
|
||||||
|
|
||||||
export const registerWorkflowIntegrationRouter = async (server: FastifyZodProvider) => {
|
export const registerWorkflowIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user