Merge branch 'main' into ENG-2685

This commit is contained in:
Andrey Lyubavin
2025-05-05 13:37:43 -04:00
481 changed files with 19235 additions and 2374 deletions
@@ -111,6 +111,11 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
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);
if (!authMode) return;
+46 -3
View File
@@ -103,6 +103,9 @@ import { sshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal";
import { sshHostLoginUserMappingDALFactory } from "@app/ee/services/ssh-host/ssh-host-login-user-mapping-dal";
import { sshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service";
import { sshHostLoginUserDALFactory } from "@app/ee/services/ssh-host/ssh-login-user-dal";
import { sshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal";
import { sshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal";
import { sshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service";
import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal";
import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
import { TKeyStoreFactory } from "@app/keystore/keystore";
@@ -175,6 +178,9 @@ import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal";
import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
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 { orgBotDALFactory } from "@app/services/org/org-bot-dal";
import { orgDALFactory } from "@app/services/org/org-dal";
@@ -400,6 +406,8 @@ export const registerRoutes = async (
const sshHostDAL = sshHostDALFactory(db);
const sshHostLoginUserDAL = sshHostLoginUserDALFactory(db);
const sshHostLoginUserMappingDAL = sshHostLoginUserMappingDALFactory(db);
const sshHostGroupDAL = sshHostGroupDALFactory(db);
const sshHostGroupMembershipDAL = sshHostGroupMembershipDALFactory(db);
const kmsDAL = kmskeyDALFactory(db);
const internalKmsDAL = internalKmsDALFactory(db);
@@ -427,6 +435,8 @@ export const registerRoutes = async (
const githubOrgSyncDAL = githubOrgSyncDALFactory(db);
const secretRotationV2DAL = secretRotationV2DALFactory(db, folderDAL);
const microsoftTeamsIntegrationDAL = microsoftTeamsIntegrationDALFactory(db);
const projectMicrosoftTeamsConfigDAL = projectMicrosoftTeamsConfigDALFactory(db);
const permissionService = permissionServiceFactory({
permissionDAL,
@@ -624,6 +634,7 @@ export const registerRoutes = async (
tokenService,
orgDAL,
totpService,
orgMembershipDAL,
auditLogService
});
const passwordService = authPaswordServiceFactory({
@@ -688,6 +699,15 @@ export const registerRoutes = async (
orgDAL,
externalGroupOrgRoleMappingDAL
});
const microsoftTeamsService = microsoftTeamsServiceFactory({
microsoftTeamsIntegrationDAL,
permissionService,
workflowIntegrationDAL,
kmsService,
serverCfgDAL: superAdminDAL
});
const superAdminService = superAdminServiceFactory({
userDAL,
identityDAL,
@@ -701,7 +721,8 @@ export const registerRoutes = async (
orgService,
keyStore,
licenseService,
kmsService
kmsService,
microsoftTeamsService
});
const orgAdminService = orgAdminServiceFactory({
@@ -852,6 +873,18 @@ export const registerRoutes = async (
kmsService
});
const sshHostGroupService = sshHostGroupServiceFactory({
projectDAL,
sshHostDAL,
sshHostGroupDAL,
sshHostGroupMembershipDAL,
sshHostLoginUserDAL,
sshHostLoginUserMappingDAL,
userDAL,
permissionService,
licenseService
});
const certificateAuthorityService = certificateAuthorityServiceFactory({
certificateAuthorityDAL,
certificateAuthorityCertDAL,
@@ -1022,6 +1055,7 @@ export const registerRoutes = async (
sshCertificateDAL,
sshCertificateTemplateDAL,
sshHostDAL,
sshHostGroupDAL,
projectUserMembershipRoleDAL,
identityProjectMembershipRoleDAL,
keyStore,
@@ -1030,6 +1064,8 @@ export const registerRoutes = async (
certificateTemplateDAL,
projectSlackConfigDAL,
slackIntegrationDAL,
projectMicrosoftTeamsConfigDAL,
microsoftTeamsIntegrationDAL,
projectTemplateService,
groupProjectDAL,
smtpService
@@ -1154,7 +1190,9 @@ export const registerRoutes = async (
userDAL,
licenseService,
projectSlackConfigDAL,
resourceMetadataDAL
resourceMetadataDAL,
projectMicrosoftTeamsConfigDAL,
microsoftTeamsService
});
const secretService = secretServiceFactory({
@@ -1216,7 +1254,9 @@ export const registerRoutes = async (
accessApprovalPolicyApproverDAL,
projectSlackConfigDAL,
kmsService,
groupDAL
groupDAL,
microsoftTeamsService,
projectMicrosoftTeamsConfigDAL
});
const secretReplicationService = secretReplicationServiceFactory({
@@ -1614,6 +1654,7 @@ export const registerRoutes = async (
await dailyResourceCleanUp.startCleanUp();
await dailyExpiringPkiItemAlert.startSendingAlerts();
await kmsService.startService();
await microsoftTeamsService.start();
// inject all services
server.decorate<FastifyZodProvider["services"]>("services", {
@@ -1673,6 +1714,7 @@ export const registerRoutes = async (
sshCertificateAuthority: sshCertificateAuthorityService,
sshCertificateTemplate: sshCertificateTemplateService,
sshHost: sshHostService,
sshHostGroup: sshHostGroupService,
certificateAuthority: certificateAuthorityService,
certificateTemplate: certificateTemplateService,
certificateAuthorityCrl: certificateAuthorityCrlService,
@@ -1706,6 +1748,7 @@ export const registerRoutes = async (
kmipOperation: kmipOperationService,
gateway: gatewayService,
secretRotationV2: secretRotationV2Service,
microsoftTeams: microsoftTeamsService,
assumePrivileges: assumePrivilegeService,
githubOrgSync: githubOrgSyncConfigService
});
+19 -6
View File
@@ -27,7 +27,10 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
createdAt: true,
updatedAt: true,
encryptedSlackClientId: true,
encryptedSlackClientSecret: true
encryptedSlackClientSecret: true,
encryptedMicrosoftTeamsAppId: true,
encryptedMicrosoftTeamsClientSecret: true,
encryptedMicrosoftTeamsBotId: true
}).extend({
isMigrationModeOn: z.boolean(),
defaultAuthOrgSlug: z.string().nullable(),
@@ -74,6 +77,9 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
}),
slackClientId: z.string().optional(),
slackClientSecret: z.string().optional(),
microsoftTeamsAppId: z.string().optional(),
microsoftTeamsClientSecret: z.string().optional(),
microsoftTeamsBotId: z.string().optional(),
authConsentContent: z
.string()
.trim()
@@ -197,15 +203,22 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/integrations/slack/config",
url: "/integrations",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({
clientId: z.string(),
clientSecret: z.string()
slack: z.object({
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 () => {
const adminSlackConfig = await server.services.superAdmin.getAdminSlackConfig();
const adminIntegrationsConfig = await server.services.superAdmin.getAdminIntegrationsConfig();
return adminSlackConfig;
return adminIntegrationsConfig;
}
});
@@ -10,6 +10,10 @@ import {
AzureAppConfigurationConnectionListItemSchema,
SanitizedAzureAppConfigurationConnectionSchema
} from "@app/services/app-connection/azure-app-configuration";
import {
AzureClientSecretsConnectionListItemSchema,
SanitizedAzureClientSecretsConnectionSchema
} from "@app/services/app-connection/azure-client-secrets";
import {
AzureKeyVaultConnectionListItemSchema,
SanitizedAzureKeyVaultConnectionSchema
@@ -24,6 +28,10 @@ import {
} from "@app/services/app-connection/databricks";
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
import {
HCVaultConnectionListItemSchema,
SanitizedHCVaultConnectionSchema
} from "@app/services/app-connection/hc-vault";
import {
HumanitecConnectionListItemSchema,
SanitizedHumanitecConnectionSchema
@@ -63,8 +71,10 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedPostgresConnectionSchema.options,
...SanitizedMsSqlConnectionSchema.options,
...SanitizedCamundaConnectionSchema.options,
...SanitizedWindmillConnectionSchema.options,
...SanitizedAuth0ConnectionSchema.options,
...SanitizedHCVaultConnectionSchema.options,
...SanitizedAzureClientSecretsConnectionSchema.options,
...SanitizedWindmillConnectionSchema.options,
...SanitizedLdapConnectionSchema.options,
...SanitizedTeamCityConnectionSchema.options
]);
@@ -82,8 +92,10 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
PostgresConnectionListItemSchema,
MsSqlConnectionListItemSchema,
CamundaConnectionListItemSchema,
WindmillConnectionListItemSchema,
Auth0ConnectionListItemSchema,
HCVaultConnectionListItemSchema,
AzureClientSecretsConnectionListItemSchema,
WindmillConnectionListItemSchema,
LdapConnectionListItemSchema,
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;
}
});
};
@@ -3,11 +3,13 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
import { registerAuth0ConnectionRouter } from "./auth0-connection-router";
import { registerAwsConnectionRouter } from "./aws-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 { registerCamundaConnectionRouter } from "./camunda-connection-router";
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
import { registerGcpConnectionRouter } from "./gcp-connection-router";
import { registerGitHubConnectionRouter } from "./github-connection-router";
import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router";
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
import { registerLdapConnectionRouter } from "./ldap-connection-router";
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
@@ -26,6 +28,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.GCP]: registerGcpConnectionRouter,
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter,
[AppConnection.AzureClientSecrets]: registerAzureClientSecretsConnectionRouter,
[AppConnection.Databricks]: registerDatabricksConnectionRouter,
[AppConnection.Humanitec]: registerHumanitecConnectionRouter,
[AppConnection.TerraformCloud]: registerTerraformCloudConnectionRouter,
@@ -35,6 +38,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.Camunda]: registerCamundaConnectionRouter,
[AppConnection.Windmill]: registerWindmillConnectionRouter,
[AppConnection.Auth0]: registerAuth0ConnectionRouter,
[AppConnection.HCVault]: registerHCVaultConnectionRouter,
[AppConnection.LDAP]: registerLdapConnectionRouter,
[AppConnection.TeamCity]: registerTeamCityConnectionRouter
};
+14 -1
View File
@@ -2,6 +2,7 @@ import jwt from "jsonwebtoken";
import { z } from "zod";
import { getConfig } from "@app/lib/config/env";
import { getMinExpiresIn } from "@app/lib/fn";
import { authRateLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode, AuthTokenType } from "@app/services/auth/auth-type";
@@ -79,6 +80,18 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
handler: async (req) => {
const { decodedToken, tokenVersion } = await server.services.authToken.validateRefreshToken(req.cookies.jid);
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(
{
@@ -92,7 +105,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
mfaMethod: decodedToken.mfaMethod
},
appCfg.AUTH_SECRET,
{ expiresIn: appCfg.JWT_AUTH_LIFETIME }
{ expiresIn }
);
return { token, organizationId: decodedToken.organizationId };
+2
View File
@@ -26,6 +26,7 @@ import { registerIdentityUaRouter } from "./identity-universal-auth-router";
import { registerIntegrationAuthRouter } from "./integration-auth-router";
import { registerIntegrationRouter } from "./integration-router";
import { registerInviteOrgRouter } from "./invite-org-router";
import { registerMicrosoftTeamsRouter } from "./microsoft-teams-router";
import { registerOrgAdminRouter } from "./org-admin-router";
import { registerOrgRouter } from "./organization-router";
import { registerPasswordRouter } from "./password-router";
@@ -79,6 +80,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
async (workflowIntegrationRouter) => {
await workflowIntegrationRouter.register(registerWorkflowIntegrationRouter);
await workflowIntegrationRouter.register(registerSlackRouter, { prefix: "/slack" });
await workflowIntegrationRouter.register(registerMicrosoftTeamsRouter, { prefix: "/microsoft-teams" });
},
{ prefix: "/workflow-integrations" }
);
@@ -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 {
@@ -263,7 +264,18 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
enforceMfa: z.boolean().optional(),
selectedMfaMethod: z.nativeEnum(MfaMethod).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: {
200: z.object({
+141 -50
View File
@@ -14,6 +14,7 @@ import {
UserEncryptionKeysSchema,
UsersSchema
} 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 { ApiDocsTags, PROJECTS } from "@app/lib/api-docs";
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 { verifyAuth } from "@app/server/plugins/auth/verify-auth";
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 { validateSlackChannelsField } from "@app/services/slack/slack-auth-validators";
import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types";
import { integrationAuthPubSchema, SanitizedProjectSchema } from "../sanitizedSchemas";
import { sanitizedServiceTokenSchema } from "../v2/service-token-router";
@@ -740,55 +743,112 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:workspaceId/slack-config",
url: "/:workspaceId/workflow-integration-config/:integration",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()
workspaceId: z.string().trim(),
integration: z.nativeEnum(WorkflowIntegration)
}),
response: {
200: ProjectSlackConfigsSchema.pick({
id: true,
slackIntegrationId: true,
isAccessRequestNotificationEnabled: true,
accessRequestChannels: true,
isSecretRequestNotificationEnabled: true,
secretRequestChannels: true
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,
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]),
handler: async (req) => {
const slackConfig = await server.services.project.getProjectSlackConfig({
const deletedIntegration = await server.services.project.deleteProjectWorkflowIntegration({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId
projectId: req.params.projectId,
integration: req.params.integration,
integrationId: req.params.integrationId
});
if (slackConfig) {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.GET_PROJECT_SLACK_CONFIG,
metadata: {
id: slackConfig.id
}
}
});
}
return slackConfig;
return {
integrationConfig: deletedIntegration
};
}
});
server.route({
method: "PUT",
url: "/:workspaceId/slack-config",
url: "/:workspaceId/workflow-integration",
config: {
rateLimit: readLimit
},
@@ -796,27 +856,57 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
slackIntegrationId: z.string(),
isAccessRequestNotificationEnabled: z.boolean(),
accessRequestChannels: validateSlackChannelsField,
isSecretRequestNotificationEnabled: z.boolean(),
secretRequestChannels: validateSlackChannelsField
}),
response: {
200: ProjectSlackConfigsSchema.pick({
id: true,
slackIntegrationId: true,
isAccessRequestNotificationEnabled: true,
accessRequestChannels: true,
isSecretRequestNotificationEnabled: true,
secretRequestChannels: true
body: z.discriminatedUnion("integration", [
z.object({
integration: z.literal(WorkflowIntegration.SLACK),
integrationId: z.string(),
accessRequestChannels: validateSlackChannelsField,
secretRequestChannels: validateSlackChannelsField,
isAccessRequestNotificationEnabled: z.boolean(),
isSecretRequestNotificationEnabled: z.boolean()
}),
z.object({
integration: z.literal(WorkflowIntegration.MICROSOFT_TEAMS),
integrationId: z.string(),
accessRequestChannels: validateMicrosoftTeamsChannelsSchema,
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]),
handler: async (req) => {
const slackConfig = await server.services.project.updateProjectSlackConfig({
const workflowIntegrationConfig = await server.services.project.updateProjectWorkflowIntegration({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
@@ -829,19 +919,20 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.UPDATE_PROJECT_SLACK_CONFIG,
type: EventType.UPDATE_PROJECT_WORKFLOW_INTEGRATION_CONFIG,
metadata: {
id: slackConfig.id,
slackIntegrationId: slackConfig.slackIntegrationId,
isAccessRequestNotificationEnabled: slackConfig.isAccessRequestNotificationEnabled,
accessRequestChannels: slackConfig.accessRequestChannels,
isSecretRequestNotificationEnabled: slackConfig.isSecretRequestNotificationEnabled,
secretRequestChannels: slackConfig.secretRequestChannels
id: workflowIntegrationConfig.id,
integrationId: workflowIntegrationConfig.integrationId,
integration: workflowIntegrationConfig.integration,
isAccessRequestNotificationEnabled: workflowIntegrationConfig.isAccessRequestNotificationEnabled,
accessRequestChannels: workflowIntegrationConfig.accessRequestChannels,
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 { registerGcpSyncRouter } from "./gcp-sync-router";
import { registerGitHubSyncRouter } from "./github-sync-router";
import { registerHCVaultSyncRouter } from "./hc-vault-sync-router";
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
import { registerTeamCitySyncRouter } from "./teamcity-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.Vercel]: registerVercelSyncRouter,
[SecretSync.Windmill]: registerWindmillSyncRouter,
[SecretSync.HCVault]: registerHCVaultSyncRouter,
[SecretSync.TeamCity]: registerTeamCitySyncRouter
};
@@ -22,6 +22,7 @@ import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secr
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
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 { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity";
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
@@ -41,6 +42,7 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
CamundaSyncSchema,
VercelSyncSchema,
WindmillSyncSchema,
HCVaultSyncSchema,
TeamCitySyncSchema
]);
@@ -57,6 +59,7 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
CamundaSyncListItemSchema,
VercelSyncListItemSchema,
WindmillSyncListItemSchema,
HCVaultSyncListItemSchema,
TeamCitySyncListItemSchema
]);
+6 -1
View File
@@ -23,6 +23,7 @@ import { fetchGithubEmails, fetchGithubUser } from "@app/lib/requests/github";
import { authRateLimit } from "@app/server/config/rateLimiter";
import { AuthMethod } from "@app/services/auth/auth-type";
import { OrgAuthMethod } from "@app/services/org/org-types";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
export const registerSsoRouter = async (server: FastifyZodProvider) => {
const appCfg = getConfig();
@@ -342,8 +343,12 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
}`
);
}
const serverCfg = await getServerCfg();
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` : ""
}`
);
}
});
@@ -7,7 +7,8 @@ const sanitizedWorkflowIntegrationSchema = WorkflowIntegrationsSchema.pick({
id: true,
description: true,
slug: true,
integration: true
integration: true,
status: true
});
export const registerWorkflowIntegrationRouter = async (server: FastifyZodProvider) => {
+42 -1
View File
@@ -14,6 +14,8 @@ import { sanitizedSshCa } from "@app/ee/services/ssh/ssh-certificate-authority-s
import { sanitizedSshCertificate } from "@app/ee/services/ssh-certificate/ssh-certificate-schema";
import { sanitizedSshCertificateTemplate } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-schema";
import { loginMappingSchema, sanitizedSshHost } from "@app/ee/services/ssh-host/ssh-host-schema";
import { LoginMappingSource } from "@app/ee/services/ssh-host/ssh-host-types";
import { sanitizedSshHostGroup } from "@app/ee/services/ssh-host-group/ssh-host-group-schema";
import { ApiDocsTags, PROJECTS } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
@@ -631,7 +633,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
200: z.object({
hosts: z.array(
sanitizedSshHost.extend({
loginMappings: z.array(loginMappingSchema)
loginMappings: loginMappingSchema
.extend({
source: z.nativeEnum(LoginMappingSource)
})
.array()
})
)
})
@@ -650,4 +656,39 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
return { hosts };
}
});
server.route({
method: "GET",
url: "/:projectId/ssh-host-groups",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim().describe(PROJECTS.LIST_SSH_HOST_GROUPS.projectId)
}),
response: {
200: z.object({
groups: z.array(
sanitizedSshHostGroup.extend({
loginMappings: loginMappingSchema.array(),
hostCount: z.number()
})
)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const groups = await server.services.project.listProjectSshHostGroups({
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
projectId: req.params.projectId
});
return { groups };
}
});
};
+35 -18
View File
@@ -88,24 +88,41 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
rateLimit: authRateLimit
},
schema: {
body: z.object({
email: z.string().trim(),
firstName: z.string().trim(),
lastName: z.string().trim().optional(),
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
publicKey: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim(),
organizationName: GenericResourceNameSchema,
providerAuthToken: z.string().trim().optional().nullish(),
attributionSource: z.string().trim().optional(),
password: z.string()
}),
body: z
.object({
email: z.string().trim(),
firstName: z.string().trim(),
lastName: z.string().trim().optional(),
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
publicKey: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim(),
providerAuthToken: z.string().trim().optional().nullish(),
attributionSource: z.string().trim().optional(),
password: z.string()
})
.and(
z.preprocess(
(data) => {
if (typeof data === "object" && data && "useDefaultOrg" in data === false) {
return { ...data, useDefaultOrg: false };
}
return data;
},
z.discriminatedUnion("useDefaultOrg", [
z.object({ useDefaultOrg: z.literal(true) }),
z.object({
useDefaultOrg: z.literal(false),
organizationName: GenericResourceNameSchema
})
])
)
),
response: {
200: z.object({
message: z.string(),