From 094b48a2b1edac3b6aab7db959f604fa23ea6dc9 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Tue, 2 Apr 2024 16:29:41 +0530 Subject: [PATCH] feat(server): updated integration and integration auth with description --- backend/src/lib/api-docs/constants.ts | 71 +++++++++++++++ .../routes/v1/integration-auth-router.ts | 63 +++++++++---- .../server/routes/v1/integration-router.ts | 90 ++++++++++++------- .../src/server/routes/v1/project-router.ts | 12 ++- 4 files changed, 187 insertions(+), 49 deletions(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 70ba7e87b..0df831526 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -479,3 +479,74 @@ export const PROJECT_USER_ADDITIONAL_PRIVILEGE = { projectMembershipId: "Project membership id of user" } }; + +export const INTEGRATION_AUTH = { + GET: { + integrationAuthId: "The id of integration authentication object." + }, + DELETE: { + integration: "The slug of integration to be deleted from authorized.", + projectId: "The ID of the project to delete integration authorized." + }, + DELETE_BY_ID: { + integrationAuthId: "The id of integration authentication object to delete." + }, + CREATE_ACCESS_TOKEN: { + workspaceId: "The ID of the project to create integration auth.", + integration: "The slug of integration for the auth object.", + accessId: "The unique authorized access id of the external integration provider.", + accessToken: "The unique authorized access token of the external integration provider.", + url: "", + namespace: "", + refreshToken: "The refresh token for integration authorization." + }, + LIST_AUTHORIZATION: { + workspaceId: "The ID of the project to list integration auth." + } +}; + +export const INTEGRATION = { + CREATE: { + integrationAuthId: "The ID of the integration auth object to link with integration.", + app: "The name of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.", + isActive: "Whether the integration should be active or disabled.", + appId: + "The ID of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.", + secretPath: "The path of the secrets to sync secrets from.", + sourceEnvironment: "The environment to sync secret from.", + targetEnvironment: + "The target environment of the integration provider. Used in cloudflare pages, TeamCity, Gitlab integrations.", + targetEnvironmentId: + "The target environment id of the integration provider. Used in cloudflare pages, teamcity, gitlab integrations.", + targetService: + "The service based grouping identifier of the external provider. Used in Terraform cloud, Checkly, Railway and NorthFlank", + targetServiceId: + "The service based grouping identifier ID of the external provider. Used in Terraform cloud, Checkly, Railway and NorthFlank", + owner: "External integration providers service entity owner. Used in Github.", + path: "Path to save the synced secrets. Used by Gitlab, AWS Parameter Store, Vault", + region: "AWS region to sync secrets to.", + scope: "Scope of the provider. Used by Github, Qovery", + metadata: { + secretPrefix: "The prefix for the saved secret. Used by GCP", + secretSuffix: "The suffix for the saved secret. Used by GCP", + initialSyncBehavoir: "Type of syncing behavoir with the integration", + shouldAutoRedeploy: "Used by Render to trigger auto deploy", + secretGCPLabel: "The label for the GCP secrets" + } + }, + UPDATE: { + integrationId: "The ID of the integration object.", + app: "The name of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.", + appId: + "The ID of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.", + isActive: "Whether the integration should be active or disabled.", + secretPath: "The path of the secrets to sync secrets from.", + owner: "External integration providers service entity owner. Used in Github.", + targetEnvironment: + "The target environment of the integration provider. Used in cloudflare pages, TeamCity, Gitlab integrations.", + environment: "The environment to sync secrets from." + }, + DELETE: { + integrationId: "The ID of the integration object." + } +}; diff --git a/backend/src/server/routes/v1/integration-auth-router.ts b/backend/src/server/routes/v1/integration-auth-router.ts index 004ca298d..5fa1a08bc 100644 --- a/backend/src/server/routes/v1/integration-auth-router.ts +++ b/backend/src/server/routes/v1/integration-auth-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { INTEGRATION_AUTH } from "@app/lib/api-docs"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -10,8 +11,14 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) server.route({ url: "/integration-options", method: "GET", - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "List of integrations available.", + security: [ + { + bearerAuth: [] + } + ], response: { 200: z.object({ integrationOptions: z @@ -38,10 +45,16 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) server.route({ url: "/:integrationAuthId", method: "GET", - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "Get details of an integration authorization by auth object id.", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ - integrationAuthId: z.string().trim() + integrationAuthId: z.string().trim().describe(INTEGRATION_AUTH.GET.integrationAuthId) }), response: { 200: z.object({ @@ -64,11 +77,17 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) server.route({ url: "/", method: "DELETE", - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "Remove all integration's auth object from the project.", + security: [ + { + bearerAuth: [] + } + ], querystring: z.object({ - integration: z.string().trim(), - projectId: z.string().trim() + integration: z.string().trim().describe(INTEGRATION_AUTH.DELETE.integration), + projectId: z.string().trim().describe(INTEGRATION_AUTH.DELETE.projectId) }), response: { 200: z.object({ @@ -104,10 +123,16 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) server.route({ url: "/:integrationAuthId", method: "DELETE", - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "Remove an integration auth object by object id.", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ - integrationAuthId: z.string().trim() + integrationAuthId: z.string().trim().describe(INTEGRATION_AUTH.DELETE_BY_ID.integrationAuthId) }), response: { 200: z.object({ @@ -183,16 +208,22 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) server.route({ url: "/access-token", method: "POST", - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "Create the integration authentication object.", + security: [ + { + bearerAuth: [] + } + ], body: z.object({ - workspaceId: z.string().trim(), - integration: z.string().trim(), - accessId: z.string().trim().optional(), - accessToken: z.string().trim().optional(), - url: z.string().url().trim().optional(), - namespace: z.string().trim().optional(), - refreshToken: z.string().trim().optional() + workspaceId: z.string().trim().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.workspaceId), + integration: z.string().trim().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.integration), + accessId: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.accessId), + accessToken: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.accessToken), + url: z.string().url().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.url), + namespace: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.namespace), + refreshToken: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.refreshToken) }), response: { 200: z.object({ diff --git a/backend/src/server/routes/v1/integration-router.ts b/backend/src/server/routes/v1/integration-router.ts index 4859ca584..0bc434dbe 100644 --- a/backend/src/server/routes/v1/integration-router.ts +++ b/backend/src/server/routes/v1/integration-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { IntegrationsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { INTEGRATION } from "@app/lib/api-docs"; import { removeTrailingSlash, shake } from "@app/lib/fn"; import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; @@ -13,33 +14,45 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { url: "/", method: "POST", schema: { + description: "Create an integration to sync secrets.", + security: [ + { + bearerAuth: [] + } + ], body: z.object({ - integrationAuthId: z.string().trim(), - app: z.string().trim().optional(), - isActive: z.boolean(), - appId: z.string().trim().optional(), - secretPath: z.string().trim().default("/").transform(removeTrailingSlash), - sourceEnvironment: z.string().trim(), - targetEnvironment: z.string().trim().optional(), - targetEnvironmentId: z.string().trim().optional(), - targetService: z.string().trim().optional(), - targetServiceId: z.string().trim().optional(), - owner: z.string().trim().optional(), - path: z.string().trim().optional(), - region: z.string().trim().optional(), - scope: z.string().trim().optional(), + integrationAuthId: z.string().trim().describe(INTEGRATION.CREATE.integrationAuthId), + app: z.string().trim().optional().describe(INTEGRATION.CREATE.app), + isActive: z.boolean().describe(INTEGRATION.CREATE.isActive).default(true), + appId: z.string().trim().optional().describe(INTEGRATION.CREATE.appId), + secretPath: z + .string() + .trim() + .default("/") + .transform(removeTrailingSlash) + .describe(INTEGRATION.CREATE.secretPath), + sourceEnvironment: z.string().trim().describe(INTEGRATION.CREATE.sourceEnvironment), + targetEnvironment: z.string().trim().optional().describe(INTEGRATION.CREATE.targetEnvironment), + targetEnvironmentId: z.string().trim().optional().describe(INTEGRATION.CREATE.targetEnvironmentId), + targetService: z.string().trim().optional().describe(INTEGRATION.CREATE.targetService), + targetServiceId: z.string().trim().optional().describe(INTEGRATION.CREATE.targetServiceId), + owner: z.string().trim().optional().describe(INTEGRATION.CREATE.owner), + path: z.string().trim().optional().describe(INTEGRATION.CREATE.path), + region: z.string().trim().optional().describe(INTEGRATION.CREATE.region), + scope: z.string().trim().optional().describe(INTEGRATION.CREATE.scope), metadata: z .object({ - secretPrefix: z.string().optional(), - secretSuffix: z.string().optional(), - initialSyncBehavior: z.string().optional(), - shouldAutoRedeploy: z.boolean().optional(), + secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix), + secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix), + initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir), + shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy), secretGCPLabel: z .object({ labelName: z.string(), labelValue: z.string() }) .optional() + .describe(INTEGRATION.CREATE.metadata.secretGCPLabel) }) .optional() }), @@ -49,7 +62,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const { integration, integrationAuth } = await server.services.integration.createIntegration({ actorId: req.permission.id, @@ -102,17 +115,28 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { url: "/:integrationId", method: "PATCH", schema: { + description: "Update an integration by integration id", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ - integrationId: z.string().trim() + integrationId: z.string().trim().describe(INTEGRATION.UPDATE.integrationId) }), body: z.object({ - app: z.string().trim(), - appId: z.string().trim(), - isActive: z.boolean(), - secretPath: z.string().trim().default("/").transform(removeTrailingSlash), - targetEnvironment: z.string().trim(), - owner: z.string().trim(), - environment: z.string().trim() + app: z.string().trim().describe(INTEGRATION.UPDATE.app), + appId: z.string().trim().describe(INTEGRATION.UPDATE.appId), + isActive: z.boolean().describe(INTEGRATION.UPDATE.isActive), + secretPath: z + .string() + .trim() + .default("/") + .transform(removeTrailingSlash) + .describe(INTEGRATION.UPDATE.secretPath), + targetEnvironment: z.string().trim().describe(INTEGRATION.UPDATE.targetEnvironment), + owner: z.string().trim().describe(INTEGRATION.UPDATE.owner), + environment: z.string().trim().describe(INTEGRATION.UPDATE.environment) }), response: { 200: z.object({ @@ -120,7 +144,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const integration = await server.services.integration.updateIntegration({ actorId: req.permission.id, @@ -138,8 +162,14 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { url: "/:integrationId", method: "DELETE", schema: { + description: "Remove an integration using the integration object ID", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ - integrationId: z.string().trim() + integrationId: z.string().trim().describe(INTEGRATION.DELETE.integrationId) }), response: { 200: z.object({ @@ -147,7 +177,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const integration = await server.services.integration.deleteIntegration({ actorId: req.permission.id, diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index 55f1fdda9..ca01820a9 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -7,7 +7,7 @@ import { UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas"; -import { PROJECTS } from "@app/lib/api-docs"; +import { INTEGRATION_AUTH, PROJECTS } from "@app/lib/api-docs"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; import { ProjectFilterType } from "@app/services/project/project-types"; @@ -332,8 +332,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { url: "/:workspaceId/authorizations", method: "GET", schema: { + description: "List integration auth objects for a workspace.", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ - workspaceId: z.string().trim() + workspaceId: z.string().trim().describe(INTEGRATION_AUTH.LIST_AUTHORIZATION.workspaceId) }), response: { 200: z.object({ @@ -341,7 +347,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const authorizations = await server.services.integrationAuth.listIntegrationAuthByProjectId({ actorId: req.permission.id,