diff --git a/backend/package-lock.json b/backend/package-lock.json index 8f1112cb7..a871c38b1 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -98,7 +98,6 @@ "ms": "^2.1.3", "mysql2": "^3.9.8", "nanoid": "^3.3.8", - "nock": "^14.0.10", "node-forge": "^1.3.1", "nodemailer": "^6.9.9", "oci-sdk": "^2.108.0", @@ -178,6 +177,7 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-simple-import-sort": "^10.0.0", + "nock": "^14.0.10", "nodemon": "^3.0.2", "pino-pretty": "^10.2.3", "prompt-sync": "^4.2.0", @@ -9710,6 +9710,7 @@ "version": "0.39.8", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.8.tgz", "integrity": "sha512-2+BzZbjRO7Ct61k8fMNHEtoKjeWI9pIlHFTqBwZ5icHpqszIgEZbjb1MW5Z0+bITTCTl3gk4PDBxs9tA/csXvA==", + "dev": true, "license": "MIT", "dependencies": { "@open-draft/deferred-promise": "^2.2.0", @@ -10736,12 +10737,14 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, "license": "MIT" }, "node_modules/@open-draft/logger": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, "license": "MIT", "dependencies": { "is-node-process": "^1.2.0", @@ -10752,6 +10755,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, "license": "MIT" }, "node_modules/@opentelemetry/api": { @@ -23002,6 +23006,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, "license": "MIT" }, "node_modules/is-number": { @@ -23557,6 +23562,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, "license": "ISC" }, "node_modules/json5": { @@ -25130,6 +25136,7 @@ "version": "14.0.10", "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.10.tgz", "integrity": "sha512-Q7HjkpyPeLa0ZVZC5qpxBt5EyLczFJ91MEewQiIi9taWuA0KB/MDJlUWtON+7dGouVdADTQsf9RA7TZk6D8VMw==", + "dev": true, "license": "MIT", "dependencies": { "@mswjs/interceptors": "^0.39.5", @@ -27772,6 +27779,7 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, "license": "MIT" }, "node_modules/p-finally": { @@ -29179,6 +29187,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -31686,6 +31695,7 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, "license": "MIT" }, "node_modules/string_decoder": { diff --git a/backend/package.json b/backend/package.json index db94de681..aa97de2ed 100644 --- a/backend/package.json +++ b/backend/package.json @@ -123,6 +123,7 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-simple-import-sort": "^10.0.0", + "nock": "^14.0.10", "nodemon": "^3.0.2", "pino-pretty": "^10.2.3", "prompt-sync": "^4.2.0", @@ -226,7 +227,6 @@ "ms": "^2.1.3", "mysql2": "^3.9.8", "nanoid": "^3.3.8", - "nock": "^14.0.10", "node-forge": "^1.3.1", "nodemailer": "^6.9.9", "oci-sdk": "^2.108.0", diff --git a/backend/src/ee/routes/v1/pit-router.ts b/backend/src/ee/routes/v1/pit-router.ts index 26909d294..3fa992601 100644 --- a/backend/src/ee/routes/v1/pit-router.ts +++ b/backend/src/ee/routes/v1/pit-router.ts @@ -435,14 +435,7 @@ export const registerPITRouter = async (server: FastifyZodProvider) => { projectId: z.string().trim(), environment: z.string().trim(), secretPath: z.string().trim().default("/").transform(removeTrailingSlash), - message: z - .string() - .trim() - .min(1) - .max(255) - .refine((message) => message.trim() !== "", { - message: "Commit message cannot be empty" - }), + message: z.string().trim().max(255).optional(), changes: z.object({ secrets: z.object({ create: z @@ -546,7 +539,7 @@ export const registerPITRouter = async (server: FastifyZodProvider) => { projectId: req.body.projectId, environment: req.body.environment, secretPath: req.body.secretPath, - message: req.body.message, + message: req.body.message || "", changes: { secrets: req.body.changes.secrets, folders: req.body.changes.folders @@ -564,7 +557,7 @@ export const registerPITRouter = async (server: FastifyZodProvider) => { projectId: req.body.projectId, environment: req.body.environment, secretPath: req.body.secretPath, - message: req.body.message + message: req.body.message || "" } } }); diff --git a/backend/src/ee/services/app-connections/chef/chef-connection-schemas.ts b/backend/src/ee/services/app-connections/chef/chef-connection-schemas.ts index e5a3687a2..efe11c0f2 100644 --- a/backend/src/ee/services/app-connections/chef/chef-connection-schemas.ts +++ b/backend/src/ee/services/app-connections/chef/chef-connection-schemas.ts @@ -2,6 +2,7 @@ import z from "zod"; import { AppConnections } from "@app/lib/api-docs"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { APP_CONNECTION_NAME_MAP } from "@app/services/app-connection/app-connection-maps"; import { BaseAppConnectionSchema, GenericCreateAppConnectionFieldsSchema, @@ -48,7 +49,7 @@ export const SanitizedChefConnectionSchema = z.discriminatedUnion("method", [ BaseChefConnectionSchema.extend({ method: z.literal(ChefConnectionMethod.UserKey), credentials: ChefConnectionUserKeyCredentialsSchema.pick({ serverUrl: true, orgName: true, userName: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Chef]} (User Key)` })) ]); export const ValidateChefConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -70,8 +71,10 @@ export const UpdateChefConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Chef)); -export const ChefConnectionListItemSchema = z.object({ - name: z.literal("Chef"), - app: z.literal(AppConnection.Chef), - methods: z.nativeEnum(ChefConnectionMethod).array() -}); +export const ChefConnectionListItemSchema = z + .object({ + name: z.literal("Chef"), + app: z.literal(AppConnection.Chef), + methods: z.nativeEnum(ChefConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Chef] })); diff --git a/backend/src/ee/services/app-connections/oci/oci-connection-schemas.ts b/backend/src/ee/services/app-connections/oci/oci-connection-schemas.ts index f09564455..e7cead989 100644 --- a/backend/src/ee/services/app-connections/oci/oci-connection-schemas.ts +++ b/backend/src/ee/services/app-connections/oci/oci-connection-schemas.ts @@ -2,6 +2,7 @@ import z from "zod"; import { AppConnections } from "@app/lib/api-docs"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { APP_CONNECTION_NAME_MAP } from "@app/services/app-connection/app-connection-maps"; import { BaseAppConnectionSchema, GenericCreateAppConnectionFieldsSchema, @@ -34,7 +35,7 @@ export const SanitizedOCIConnectionSchema = z.discriminatedUnion("method", [ region: true, fingerprint: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.OCI]} (Access Key)` })) ]); export const ValidateOCIConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -58,8 +59,10 @@ export const UpdateOCIConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.OCI)); -export const OCIConnectionListItemSchema = z.object({ - name: z.literal("OCI"), - app: z.literal(AppConnection.OCI), - methods: z.nativeEnum(OCIConnectionMethod).array() -}); +export const OCIConnectionListItemSchema = z + .object({ + name: z.literal("OCI"), + app: z.literal(AppConnection.OCI), + methods: z.nativeEnum(OCIConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.OCI] })); diff --git a/backend/src/ee/services/app-connections/oracledb/oracledb-connection-schemas.ts b/backend/src/ee/services/app-connections/oracledb/oracledb-connection-schemas.ts index 38e0fc828..79996841e 100644 --- a/backend/src/ee/services/app-connections/oracledb/oracledb-connection-schemas.ts +++ b/backend/src/ee/services/app-connections/oracledb/oracledb-connection-schemas.ts @@ -2,6 +2,7 @@ import z from "zod"; import { AppConnections } from "@app/lib/api-docs"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { APP_CONNECTION_NAME_MAP } from "@app/services/app-connection/app-connection-maps"; import { BaseAppConnectionSchema, GenericCreateAppConnectionFieldsSchema, @@ -32,7 +33,7 @@ export const SanitizedOracleDBConnectionSchema = z.discriminatedUnion("method", sslRejectUnauthorized: true, sslCertificate: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.OracleDB]} (Username and Password)` })) ]); export const ValidateOracleDBConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -64,9 +65,11 @@ export const UpdateOracleDBConnectionSchema = z }) ); -export const OracleDBConnectionListItemSchema = z.object({ - name: z.literal("OracleDB"), - app: z.literal(AppConnection.OracleDB), - methods: z.nativeEnum(OracleDBConnectionMethod).array(), - supportsPlatformManagement: z.literal(true) -}); +export const OracleDBConnectionListItemSchema = z + .object({ + name: z.literal("OracleDB"), + app: z.literal(AppConnection.OracleDB), + methods: z.nativeEnum(OracleDBConnectionMethod).array(), + supportsPlatformManagement: z.literal(true) + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.OracleDB] })); diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index f91692a14..de3ce9af6 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -365,6 +365,8 @@ export enum EventType { LOAD_PROJECT_KMS_BACKUP = "load-project-kms-backup", ORG_ADMIN_ACCESS_PROJECT = "org-admin-accessed-project", ORG_ADMIN_BYPASS_SSO = "org-admin-bypassed-sso", + USER_LOGIN = "user-login", + SELECT_ORGANIZATION = "select-organization", CREATE_CERTIFICATE_TEMPLATE = "create-certificate-template", UPDATE_CERTIFICATE_TEMPLATE = "update-certificate-template", DELETE_CERTIFICATE_TEMPLATE = "delete-certificate-template", @@ -570,6 +572,7 @@ interface UserActorMetadata { email?: string | null; username: string; permission?: Record; + authMethod?: string; } interface ServiceActorMetadata { @@ -2657,6 +2660,22 @@ interface OrgAdminBypassSSOEvent { metadata: Record; // no metadata yet } +interface UserLoginEvent { + type: EventType.USER_LOGIN; + metadata: { + organizationId?: string; + authProvider?: string; + }; +} + +interface SelectOrganizationEvent { + type: EventType.SELECT_ORGANIZATION; + metadata: { + organizationId: string; + organizationName: string; + }; +} + interface CreateCertificateTemplateEstConfig { type: EventType.CREATE_CERTIFICATE_TEMPLATE_EST_CONFIG; metadata: { @@ -4535,4 +4554,6 @@ export type Event = | UpdateCertificateRenewalConfigEvent | DisableCertificateRenewalConfigEvent | AutomatedRenewCertificate - | AutomatedRenewCertificateFailed; + | AutomatedRenewCertificateFailed + | UserLoginEvent + | SelectOrganizationEvent; diff --git a/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-fns.ts b/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-fns.ts index 9b1fd14a0..cc96cf327 100644 --- a/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-fns.ts +++ b/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-fns.ts @@ -10,7 +10,7 @@ import { import { logger } from "@app/lib/logger"; import { DistinguishedNameRegex } from "@app/lib/regex"; import { encryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns"; -import { getLdapConnectionClient, LdapProvider, TLdapConnection } from "@app/services/app-connection/ldap"; +import { executeWithPotentialGateway, LdapProvider, TLdapConnection } from "@app/services/app-connection/ldap"; import { generatePassword } from "../shared/utils"; import { @@ -71,17 +71,18 @@ export const ldapPasswordRotationFactory: TRotationFactory< TLdapPasswordRotationWithConnection, TLdapPasswordRotationGeneratedCredentials, TLdapPasswordRotationInput["temporaryParameters"] -> = (secretRotation, appConnectionDAL, kmsService) => { +> = (secretRotation, appConnectionDAL, kmsService, gatewayService, gatewayV2Service) => { const { connection, parameters, secretsMapping, activeIndex } = secretRotation; const { dn, passwordRequirements } = parameters; const $verifyCredentials = async (credentials: Pick) => { try { - const client = await getLdapConnectionClient({ ...connection.credentials, ...credentials }); - - client.unbind(); - client.destroy(); + await executeWithPotentialGateway( + { ...connection, credentials: { ...connection.credentials, ...credentials } }, + gatewayV2Service, + async () => {} + ); } catch (error) { throw new Error(`Failed to verify credentials - ${(error as Error).message}`); } @@ -92,17 +93,7 @@ export const ldapPasswordRotationFactory: TRotationFactory< if (!credentials.url.startsWith("ldaps")) throw new Error("Password Rotation requires an LDAPS connection"); - const client = await getLdapConnectionClient( - currentPassword - ? { - ...credentials, - password: currentPassword, - dn - } - : credentials - ); const isConnectionRotation = credentials.dn === dn; - const password = generatePassword(passwordRequirements); let changes: ldap.Change[] | ldap.Change; @@ -147,22 +138,32 @@ export const ldapPasswordRotationFactory: TRotationFactory< throw new Error(`Unhandled provider: ${credentials.provider as LdapProvider}`); } - try { - const userDn = await getDN(dn, client); - await new Promise((resolve, reject) => { - client.modify(userDn, changes, (err) => { - if (err) { - logger.error(err, "LDAP Password Rotation Failed"); - reject(new Error(`Provider Modify Error: ${err.message}`)); - } else { - resolve(true); - } + await executeWithPotentialGateway( + { + ...connection, + credentials: currentPassword + ? { + ...credentials, + password: currentPassword, + dn + } + : credentials + }, + gatewayV2Service, + async (client) => { + const userDn = await getDN(dn, client); + await new Promise((resolve, reject) => { + client.modify(userDn, changes, (err) => { + if (err) { + logger.error(err, "LDAP Password Rotation Failed"); + reject(new Error(`Provider Modify Error: ${err.message}`)); + } else { + resolve(); + } + }); }); - }); - } finally { - client.unbind(); - client.destroy(); - } + } + ); await $verifyCredentials({ dn, password }); diff --git a/backend/src/ee/services/secret-sync/chef/chef-sync-schemas.ts b/backend/src/ee/services/secret-sync/chef/chef-sync-schemas.ts index 9702f97d3..03b5e7f77 100644 --- a/backend/src/ee/services/secret-sync/chef/chef-sync-schemas.ts +++ b/backend/src/ee/services/secret-sync/chef/chef-sync-schemas.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { SecretSyncs } from "@app/lib/api-docs"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; +import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps"; import { BaseSecretSyncSchema, GenericCreateSecretSyncFieldsSchema, @@ -25,10 +26,12 @@ const ChefSyncDestinationConfigSchema = z.object({ const ChefSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const ChefSyncSchema = BaseSecretSyncSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Chef), - destinationConfig: ChefSyncDestinationConfigSchema -}); +export const ChefSyncSchema = BaseSecretSyncSchema(SecretSync.Chef, ChefSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Chef), + destinationConfig: ChefSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Chef] })); export const CreateChefSyncSchema = GenericCreateSecretSyncFieldsSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({ destinationConfig: ChefSyncDestinationConfigSchema @@ -38,10 +41,12 @@ export const UpdateChefSyncSchema = GenericUpdateSecretSyncFieldsSchema(SecretSy destinationConfig: ChefSyncDestinationConfigSchema.optional() }); -export const ChefSyncListItemSchema = z.object({ - name: z.literal("Chef"), - connection: z.literal(AppConnection.Chef), - destination: z.literal(SecretSync.Chef), - canImportSecrets: z.literal(true), - enterprise: z.boolean() -}); +export const ChefSyncListItemSchema = z + .object({ + name: z.literal("Chef"), + connection: z.literal(AppConnection.Chef), + destination: z.literal(SecretSync.Chef), + canImportSecrets: z.literal(true), + enterprise: z.boolean() + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Chef] })); diff --git a/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts b/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts index a0bd29382..7f0319420 100644 --- a/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts +++ b/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts @@ -4,6 +4,7 @@ import { z } from "zod"; import { SecretSyncs } from "@app/lib/api-docs"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; +import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps"; import { BaseSecretSyncSchema, GenericCreateSecretSyncFieldsSchema, @@ -43,10 +44,12 @@ const OCIVaultSyncDestinationConfigSchema = z.object({ const OCIVaultSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const OCIVaultSyncSchema = BaseSecretSyncSchema(SecretSync.OCIVault, OCIVaultSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.OCIVault), - destinationConfig: OCIVaultSyncDestinationConfigSchema -}); +export const OCIVaultSyncSchema = BaseSecretSyncSchema(SecretSync.OCIVault, OCIVaultSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.OCIVault), + destinationConfig: OCIVaultSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.OCIVault] })); export const CreateOCIVaultSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.OCIVault, @@ -62,10 +65,12 @@ export const UpdateOCIVaultSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: OCIVaultSyncDestinationConfigSchema.optional() }); -export const OCIVaultSyncListItemSchema = z.object({ - name: z.literal("OCI Vault"), - connection: z.literal(AppConnection.OCI), - destination: z.literal(SecretSync.OCIVault), - canImportSecrets: z.literal(true), - enterprise: z.boolean() -}); +export const OCIVaultSyncListItemSchema = z + .object({ + name: z.literal("OCI Vault"), + connection: z.literal(AppConnection.OCI), + destination: z.literal(SecretSync.OCIVault), + canImportSecrets: z.literal(true), + enterprise: z.boolean() + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.OCIVault] })); diff --git a/backend/src/lib/validator/validate-url.ts b/backend/src/lib/validator/validate-url.ts index a4c07b37d..7b2b09209 100644 --- a/backend/src/lib/validator/validate-url.ts +++ b/backend/src/lib/validator/validate-url.ts @@ -8,10 +8,10 @@ import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "../errors"; import { isPrivateIp } from "../ip/ipRange"; -export const blockLocalAndPrivateIpAddresses = async (url: string) => { +export const blockLocalAndPrivateIpAddresses = async (url: string, isGateway = false) => { const appCfg = getConfig(); - if (appCfg.isDevelopmentMode) return; + if (appCfg.isDevelopmentMode || isGateway) return; const validUrl = new URL(url); diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 3195e670d..5dd7a1c22 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -643,7 +643,8 @@ export const registerRoutes = async ( projectDAL, identityDAL, userDAL, - externalGroupOrgRoleMappingDAL + externalGroupOrgRoleMappingDAL, + membershipRoleDAL }); const additionalPrivilegeService = additionalPrivilegeServiceFactory({ additionalPrivilegeDAL, diff --git a/backend/src/server/routes/v1/bdd-nock-router.ts b/backend/src/server/routes/v1/bdd-nock-router.ts index 597d31d9a..6a32cac20 100644 --- a/backend/src/server/routes/v1/bdd-nock-router.ts +++ b/backend/src/server/routes/v1/bdd-nock-router.ts @@ -1,88 +1,87 @@ -import nock, { Definition } from "nock"; -import { z } from "zod"; +// import { z } from "zod"; -import { getConfig } from "@app/lib/config/env"; -import { ForbiddenRequestError } from "@app/lib/errors"; -import { logger } from "@app/lib/logger"; -import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { AuthMode } from "@app/services/auth/auth-type"; +// import { getConfig } from "@app/lib/config/env"; +// import { ForbiddenRequestError } from "@app/lib/errors"; +// import { logger } from "@app/lib/logger"; +// import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +// import { AuthMode } from "@app/services/auth/auth-type"; -export const registerBddNockRouter = async (server: FastifyZodProvider) => { - const checkIfBddNockApiEnabled = () => { - const appCfg = getConfig(); - // Note: Please note that this API is only available in development mode and only for BDD tests. - // This endpoint should NEVER BE ENABLED IN PRODUCTION! - if (appCfg.NODE_ENV !== "development" || !appCfg.isBddNockApiEnabled) { - throw new ForbiddenRequestError({ message: "BDD Nock API is not enabled" }); - } - }; +// export const registerBddNockRouter = async (server: FastifyZodProvider) => { +// const checkIfBddNockApiEnabled = () => { +// const appCfg = getConfig(); +// // Note: Please note that this API is only available in development mode and only for BDD tests. +// // This endpoint should NEVER BE ENABLED IN PRODUCTION! +// if (appCfg.NODE_ENV !== "development" || !appCfg.isBddNockApiEnabled) { +// throw new ForbiddenRequestError({ message: "BDD Nock API is not enabled" }); +// } +// }; - server.route({ - method: "POST", - url: "/define", - schema: { - body: z.object({ definitions: z.unknown().array() }), - response: { - 200: z.object({ status: z.string() }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - checkIfBddNockApiEnabled(); - const { body } = req; - const { definitions } = body; - logger.info(definitions, "Defining nock"); - const processedDefinitions = definitions.map((definition: unknown) => { - const { path, ...rest } = definition as Definition; - return { - ...rest, - path: - path !== undefined && typeof path === "string" - ? path - : new RegExp((path as unknown as { regex: string }).regex ?? "") - } as Definition; - }); +// server.route({ +// method: "POST", +// url: "/define", +// schema: { +// body: z.object({ definitions: z.unknown().array() }), +// response: { +// 200: z.object({ status: z.string() }) +// } +// }, +// onRequest: verifyAuth([AuthMode.JWT]), +// handler: async (req) => { +// checkIfBddNockApiEnabled(); +// const { body } = req; +// const { definitions } = body; +// logger.info(definitions, "Defining nock"); +// const processedDefinitions = definitions.map((definition: unknown) => { +// const { path, ...rest } = definition as Definition; +// return { +// ...rest, +// path: +// path !== undefined && typeof path === "string" +// ? path +// : new RegExp((path as unknown as { regex: string }).regex ?? "") +// } as Definition; +// }); - nock.define(processedDefinitions); - // Ensure we are activating the nocks, because we could have called `nock.restore()` before this call. - if (!nock.isActive()) { - nock.activate(); - } - return { status: "ok" }; - } - }); +// nock.define(processedDefinitions); +// // Ensure we are activating the nocks, because we could have called `nock.restore()` before this call. +// if (!nock.isActive()) { +// nock.activate(); +// } +// return { status: "ok" }; +// } +// }); - server.route({ - method: "POST", - url: "/clean-all", - schema: { - response: { - 200: z.object({ status: z.string() }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async () => { - checkIfBddNockApiEnabled(); - logger.info("Cleaning all nocks"); - nock.cleanAll(); - return { status: "ok" }; - } - }); +// server.route({ +// method: "POST", +// url: "/clean-all", +// schema: { +// response: { +// 200: z.object({ status: z.string() }) +// } +// }, +// onRequest: verifyAuth([AuthMode.JWT]), +// handler: async () => { +// checkIfBddNockApiEnabled(); +// logger.info("Cleaning all nocks"); +// nock.cleanAll(); +// return { status: "ok" }; +// } +// }); - server.route({ - method: "POST", - url: "/restore", - schema: { - response: { - 200: z.object({ status: z.string() }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async () => { - checkIfBddNockApiEnabled(); - logger.info("Restore network requests from nock"); - nock.restore(); - return { status: "ok" }; - } - }); -}; +// server.route({ +// method: "POST", +// url: "/restore", +// schema: { +// response: { +// 200: z.object({ status: z.string() }) +// } +// }, +// onRequest: verifyAuth([AuthMode.JWT]), +// handler: async () => { +// checkIfBddNockApiEnabled(); +// logger.info("Restore network requests from nock"); +// nock.restore(); +// return { status: "ok" }; +// } +// }); +// }; diff --git a/backend/src/server/routes/v1/index.ts b/backend/src/server/routes/v1/index.ts index 7fb8b1d0b..4d2b1d4b6 100644 --- a/backend/src/server/routes/v1/index.ts +++ b/backend/src/server/routes/v1/index.ts @@ -9,7 +9,7 @@ import { registerSecretSyncRouter, SECRET_SYNC_REGISTER_ROUTER_MAP } from "@app/ import { registerAdminRouter } from "./admin-router"; import { registerAuthRoutes } from "./auth-router"; -import { registerBddNockRouter } from "./bdd-nock-router"; +// import { registerBddNockRouter } from "./bdd-nock-router"; import { registerProjectBotRouter } from "./bot-router"; import { registerCaRouter } from "./certificate-authority-router"; import { CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP } from "./certificate-authority-routers"; @@ -242,7 +242,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { // Note: This is a special route for BDD tests. It's only available in development mode and only for BDD tests. // This route should NEVER BE ENABLED IN PRODUCTION! - if (getConfig().isBddNockApiEnabled) { - await server.register(registerBddNockRouter, { prefix: "/bdd-nock" }); - } + // if (getConfig().isBddNockApiEnabled) { + // await server.register(registerBddNockRouter, { prefix: "/bdd-nock" }); + // } }; diff --git a/backend/src/services/app-connection/1password/1password-connection-schemas.ts b/backend/src/services/app-connection/1password/1password-connection-schemas.ts index da63dc32a..8be6dff1a 100644 --- a/backend/src/services/app-connection/1password/1password-connection-schemas.ts +++ b/backend/src/services/app-connection/1password/1password-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { OnePassConnectionMethod } from "./1password-connection-enums"; export const OnePassConnectionAccessTokenCredentialsSchema = z.object({ @@ -33,7 +34,7 @@ export const SanitizedOnePassConnectionSchema = z.discriminatedUnion("method", [ credentials: OnePassConnectionAccessTokenCredentialsSchema.pick({ instanceUrl: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.OnePass]} (API Token)` })) ]); export const ValidateOnePassConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -57,8 +58,10 @@ export const UpdateOnePassConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.OnePass)); -export const OnePassConnectionListItemSchema = z.object({ - name: z.literal("1Password"), - app: z.literal(AppConnection.OnePass), - methods: z.nativeEnum(OnePassConnectionMethod).array() -}); +export const OnePassConnectionListItemSchema = z + .object({ + name: z.literal("1Password"), + app: z.literal(AppConnection.OnePass), + methods: z.nativeEnum(OnePassConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.OnePass] })); diff --git a/backend/src/services/app-connection/auth0/auth0-connection-schemas.ts b/backend/src/services/app-connection/auth0/auth0-connection-schemas.ts index 67992a503..270e37ccb 100644 --- a/backend/src/services/app-connection/auth0/auth0-connection-schemas.ts +++ b/backend/src/services/app-connection/auth0/auth0-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { Auth0ConnectionMethod } from "./auth0-connection-enums"; export const Auth0ConnectionClientCredentialsInputCredentialsSchema = z.object({ @@ -59,7 +60,7 @@ export const SanitizedAuth0ConnectionSchema = z.discriminatedUnion("method", [ clientId: true, audience: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Auth0]} (Client Credentials)` })) ]); export const ValidateAuth0ConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -85,10 +86,12 @@ export const UpdateAuth0ConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Auth0)); -export const Auth0ConnectionListItemSchema = z.object({ - name: z.literal("Auth0"), - app: z.literal(AppConnection.Auth0), - // the below is preferable but currently breaks with our zod to json schema parser - // methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]), - methods: z.nativeEnum(Auth0ConnectionMethod).array() -}); +export const Auth0ConnectionListItemSchema = z + .object({ + name: z.literal("Auth0"), + app: z.literal(AppConnection.Auth0), + // the below is preferable but currently breaks with our zod to json schema parser + // methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]), + methods: z.nativeEnum(Auth0ConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Auth0] })); diff --git a/backend/src/services/app-connection/aws/aws-connection-schemas.ts b/backend/src/services/app-connection/aws/aws-connection-schemas.ts index 8cb19ba26..7e8fd36a8 100644 --- a/backend/src/services/app-connection/aws/aws-connection-schemas.ts +++ b/backend/src/services/app-connection/aws/aws-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { AwsConnectionMethod } from "./aws-connection-enums"; export const AwsConnectionAssumeRoleCredentialsSchema = z.object({ @@ -39,11 +40,11 @@ export const SanitizedAwsConnectionSchema = z.discriminatedUnion("method", [ BaseAwsConnectionSchema.extend({ method: z.literal(AwsConnectionMethod.AssumeRole), credentials: AwsConnectionAssumeRoleCredentialsSchema.pick({}) - }), + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AWS]} (Assume Role)` })), BaseAwsConnectionSchema.extend({ method: z.literal(AwsConnectionMethod.AccessKey), credentials: AwsConnectionAccessTokenCredentialsSchema.pick({ accessKeyId: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AWS]} (Access Key)` })) ]); export const ValidateAwsConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -72,11 +73,13 @@ export const UpdateAwsConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AWS)); -export const AwsConnectionListItemSchema = z.object({ - name: z.literal("AWS"), - app: z.literal(AppConnection.AWS), - // the below is preferable but currently breaks with our zod to json schema parser - // methods: z.tuple([z.literal(AwsConnectionMethod.AssumeRole), z.literal(AwsConnectionMethod.AccessKey)]), - methods: z.nativeEnum(AwsConnectionMethod).array(), - accessKeyId: z.string().optional() -}); +export const AwsConnectionListItemSchema = z + .object({ + name: z.literal("AWS"), + app: z.literal(AppConnection.AWS), + // the below is preferable but currently breaks with our zod to json schema parser + // methods: z.tuple([z.literal(AwsConnectionMethod.AssumeRole), z.literal(AwsConnectionMethod.AccessKey)]), + methods: z.nativeEnum(AwsConnectionMethod).array(), + accessKeyId: z.string().optional() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.AWS] })); diff --git a/backend/src/services/app-connection/azure-adcs/azure-adcs-connection-schemas.ts b/backend/src/services/app-connection/azure-adcs/azure-adcs-connection-schemas.ts index a43bb5954..1b55ef196 100644 --- a/backend/src/services/app-connection/azure-adcs/azure-adcs-connection-schemas.ts +++ b/backend/src/services/app-connection/azure-adcs/azure-adcs-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { AzureADCSConnectionMethod } from "./azure-adcs-connection-enums"; export const AzureADCSUsernamePasswordCredentialsSchema = z.object({ @@ -55,7 +56,7 @@ export const SanitizedAzureADCSConnectionSchema = z.discriminatedUnion("method", sslRejectUnauthorized: true, sslCertificate: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureADCS]} (Username and Password)` })) ]); export const ValidateAzureADCSConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -81,8 +82,10 @@ export const UpdateAzureADCSConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AzureADCS)); -export const AzureADCSConnectionListItemSchema = z.object({ - name: z.literal("Azure ADCS"), - app: z.literal(AppConnection.AzureADCS), - methods: z.nativeEnum(AzureADCSConnectionMethod).array() -}); +export const AzureADCSConnectionListItemSchema = z + .object({ + name: z.literal("Azure ADCS"), + app: z.literal(AppConnection.AzureADCS), + methods: z.nativeEnum(AzureADCSConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.AzureADCS] })); diff --git a/backend/src/services/app-connection/azure-app-configuration/azure-app-configuration-connection-schemas.ts b/backend/src/services/app-connection/azure-app-configuration/azure-app-configuration-connection-schemas.ts index 68579f9ba..49a557582 100644 --- a/backend/src/services/app-connection/azure-app-configuration/azure-app-configuration-connection-schemas.ts +++ b/backend/src/services/app-connection/azure-app-configuration/azure-app-configuration-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { AzureAppConfigurationConnectionMethod } from "./azure-app-configuration-connection-enums"; export const AzureAppConfigurationConnectionOAuthInputCredentialsSchema = z.object({ @@ -104,19 +105,23 @@ export const SanitizedAzureAppConfigurationConnectionSchema = z.discriminatedUni credentials: AzureAppConfigurationConnectionOAuthOutputCredentialsSchema.pick({ tenantId: true }) - }), + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureAppConfiguration]} (OAuth)` })), BaseAzureAppConfigurationConnectionSchema.extend({ method: z.literal(AzureAppConfigurationConnectionMethod.ClientSecret), credentials: AzureAppConfigurationConnectionClientSecretOutputCredentialsSchema.pick({ clientId: true, tenantId: true }) - }) + }).describe( + JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureAppConfiguration]} (Client Secret)` }) + ) ]); -export const AzureAppConfigurationConnectionListItemSchema = z.object({ - name: z.literal("Azure App Configuration"), - app: z.literal(AppConnection.AzureAppConfiguration), - methods: z.nativeEnum(AzureAppConfigurationConnectionMethod).array(), - oauthClientId: z.string().optional() -}); +export const AzureAppConfigurationConnectionListItemSchema = z + .object({ + name: z.literal("Azure App Configuration"), + app: z.literal(AppConnection.AzureAppConfiguration), + methods: z.nativeEnum(AzureAppConfigurationConnectionMethod).array(), + oauthClientId: z.string().optional() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.AzureAppConfiguration] })); diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts index dd387894e..910244d9e 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums"; export const AzureClientSecretsConnectionOAuthInputCredentialsSchema = z.object({ @@ -162,26 +163,30 @@ export const SanitizedAzureClientSecretsConnectionSchema = z.discriminatedUnion( credentials: AzureClientSecretsConnectionOAuthOutputCredentialsSchema.pick({ tenantId: true }) - }), + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureClientSecrets]} (OAuth)` })), BaseAzureClientSecretsConnectionSchema.extend({ method: z.literal(AzureClientSecretsConnectionMethod.ClientSecret), credentials: AzureClientSecretsConnectionClientSecretOutputCredentialsSchema.pick({ clientId: true, tenantId: true }) - }), + }).describe( + JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureClientSecrets]} (Client Secret)` }) + ), BaseAzureClientSecretsConnectionSchema.extend({ method: z.literal(AzureClientSecretsConnectionMethod.Certificate), credentials: AzureClientSecretsConnectionCertificateOutputCredentialsSchema.pick({ tenantId: true, clientId: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureClientSecrets]} (Certificate)` })) ]); -export const AzureClientSecretsConnectionListItemSchema = z.object({ - name: z.literal("Azure Client Secrets"), - app: z.literal(AppConnection.AzureClientSecrets), - methods: z.nativeEnum(AzureClientSecretsConnectionMethod).array(), - oauthClientId: z.string().optional() -}); +export const AzureClientSecretsConnectionListItemSchema = z + .object({ + name: z.literal("Azure Client Secrets"), + app: z.literal(AppConnection.AzureClientSecrets), + methods: z.nativeEnum(AzureClientSecretsConnectionMethod).array(), + oauthClientId: z.string().optional() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.AzureClientSecrets] })); diff --git a/backend/src/services/app-connection/azure-devops/azure-devops-schemas.ts b/backend/src/services/app-connection/azure-devops/azure-devops-schemas.ts index 2580d0114..c574770bb 100644 --- a/backend/src/services/app-connection/azure-devops/azure-devops-schemas.ts +++ b/backend/src/services/app-connection/azure-devops/azure-devops-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { AzureDevOpsConnectionMethod } from "./azure-devops-enums"; export const AzureDevOpsConnectionOAuthInputCredentialsSchema = z.object({ @@ -147,13 +148,13 @@ export const SanitizedAzureDevOpsConnectionSchema = z.discriminatedUnion("method tenantId: true, orgName: true }) - }), + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureDevOps]} (OAuth)` })), BaseAzureDevOpsConnectionSchema.extend({ method: z.literal(AzureDevOpsConnectionMethod.AccessToken), credentials: AzureDevOpsConnectionAccessTokenOutputCredentialsSchema.pick({ orgName: true }) - }), + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureDevOps]} (Access Token)` })), BaseAzureDevOpsConnectionSchema.extend({ method: z.literal(AzureDevOpsConnectionMethod.ClientSecret), credentials: AzureDevOpsConnectionClientSecretOutputCredentialsSchema.pick({ @@ -161,12 +162,14 @@ export const SanitizedAzureDevOpsConnectionSchema = z.discriminatedUnion("method tenantId: true, orgName: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureDevOps]} (Client Secret)` })) ]); -export const AzureDevOpsConnectionListItemSchema = z.object({ - name: z.literal("Azure DevOps"), - app: z.literal(AppConnection.AzureDevOps), - methods: z.nativeEnum(AzureDevOpsConnectionMethod).array(), - oauthClientId: z.string().optional() -}); +export const AzureDevOpsConnectionListItemSchema = z + .object({ + name: z.literal("Azure DevOps"), + app: z.literal(AppConnection.AzureDevOps), + methods: z.nativeEnum(AzureDevOpsConnectionMethod).array(), + oauthClientId: z.string().optional() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.AzureDevOps] })); diff --git a/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-schemas.ts b/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-schemas.ts index d86878bab..3c0323789 100644 --- a/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-schemas.ts +++ b/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { AzureKeyVaultConnectionMethod } from "./azure-key-vault-connection-enums"; export const AzureKeyVaultConnectionOAuthInputCredentialsSchema = z.object({ @@ -104,19 +105,21 @@ export const SanitizedAzureKeyVaultConnectionSchema = z.discriminatedUnion("meth credentials: AzureKeyVaultConnectionOAuthOutputCredentialsSchema.pick({ tenantId: true }) - }), + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureKeyVault]} (OAuth)` })), BaseAzureKeyVaultConnectionSchema.extend({ method: z.literal(AzureKeyVaultConnectionMethod.ClientSecret), credentials: AzureKeyVaultConnectionClientSecretOutputCredentialsSchema.pick({ clientId: true, tenantId: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.AzureKeyVault]} (Client Secret)` })) ]); -export const AzureKeyVaultConnectionListItemSchema = z.object({ - name: z.literal("Azure Key Vault"), - app: z.literal(AppConnection.AzureKeyVault), - methods: z.nativeEnum(AzureKeyVaultConnectionMethod).array(), - oauthClientId: z.string().optional() -}); +export const AzureKeyVaultConnectionListItemSchema = z + .object({ + name: z.literal("Azure Key Vault"), + app: z.literal(AppConnection.AzureKeyVault), + methods: z.nativeEnum(AzureKeyVaultConnectionMethod).array(), + oauthClientId: z.string().optional() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.AzureKeyVault] })); diff --git a/backend/src/services/app-connection/bitbucket/bitbucket-connection-schemas.ts b/backend/src/services/app-connection/bitbucket/bitbucket-connection-schemas.ts index fab1bf74c..5235446de 100644 --- a/backend/src/services/app-connection/bitbucket/bitbucket-connection-schemas.ts +++ b/backend/src/services/app-connection/bitbucket/bitbucket-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { BitbucketConnectionMethod } from "./bitbucket-connection-enums"; export const BitbucketConnectionAccessTokenCredentialsSchema = z.object({ @@ -39,7 +40,7 @@ export const SanitizedBitbucketConnectionSchema = z.discriminatedUnion("method", credentials: BitbucketConnectionAccessTokenCredentialsSchema.pick({ email: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Bitbucket]} (API Token)` })) ]); export const ValidateBitbucketConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -65,8 +66,10 @@ export const UpdateBitbucketConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Bitbucket)); -export const BitbucketConnectionListItemSchema = z.object({ - name: z.literal("Bitbucket"), - app: z.literal(AppConnection.Bitbucket), - methods: z.nativeEnum(BitbucketConnectionMethod).array() -}); +export const BitbucketConnectionListItemSchema = z + .object({ + name: z.literal("Bitbucket"), + app: z.literal(AppConnection.Bitbucket), + methods: z.nativeEnum(BitbucketConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Bitbucket] })); diff --git a/backend/src/services/app-connection/camunda/camunda-connection-schema.ts b/backend/src/services/app-connection/camunda/camunda-connection-schema.ts index fa769c650..c6c51a9e8 100644 --- a/backend/src/services/app-connection/camunda/camunda-connection-schema.ts +++ b/backend/src/services/app-connection/camunda/camunda-connection-schema.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { CamundaConnectionMethod } from "./camunda-connection-enums"; const BaseCamundaConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Camunda) }); @@ -44,7 +45,7 @@ export const SanitizedCamundaConnectionSchema = z.discriminatedUnion("method", [ credentials: CamundaConnectionClientCredentialsOutputCredentialsSchema.pick({ clientId: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Camunda]} (Client Credentials)` })) ]); export const ValidateCamundaConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -70,8 +71,10 @@ export const UpdateCamundaConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Camunda)); -export const CamundaConnectionListItemSchema = z.object({ - name: z.literal("Camunda"), - app: z.literal(AppConnection.Camunda), - methods: z.nativeEnum(CamundaConnectionMethod).array() -}); +export const CamundaConnectionListItemSchema = z + .object({ + name: z.literal("Camunda"), + app: z.literal(AppConnection.Camunda), + methods: z.nativeEnum(CamundaConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Camunda] })); diff --git a/backend/src/services/app-connection/checkly/checkly-connection-schemas.ts b/backend/src/services/app-connection/checkly/checkly-connection-schemas.ts index 174e5bce0..644a41a8b 100644 --- a/backend/src/services/app-connection/checkly/checkly-connection-schemas.ts +++ b/backend/src/services/app-connection/checkly/checkly-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { ChecklyConnectionMethod } from "./checkly-connection-constants"; export const ChecklyConnectionMethodSchema = z @@ -31,7 +32,7 @@ export const SanitizedChecklyConnectionSchema = z.discriminatedUnion("method", [ BaseChecklyConnectionSchema.extend({ method: ChecklyConnectionMethodSchema, credentials: ChecklyConnectionAccessTokenCredentialsSchema.pick({}) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Checkly]} (Access Token)` })) ]); export const ValidateChecklyConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -55,8 +56,10 @@ export const UpdateChecklyConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Checkly)); -export const ChecklyConnectionListItemSchema = z.object({ - name: z.literal("Checkly"), - app: z.literal(AppConnection.Checkly), - methods: z.nativeEnum(ChecklyConnectionMethod).array() -}); +export const ChecklyConnectionListItemSchema = z + .object({ + name: z.literal("Checkly"), + app: z.literal(AppConnection.Checkly), + methods: z.nativeEnum(ChecklyConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Checkly] })); diff --git a/backend/src/services/app-connection/cloudflare/cloudflare-connection-schema.ts b/backend/src/services/app-connection/cloudflare/cloudflare-connection-schema.ts index f3b26a2d6..a6ffc56ee 100644 --- a/backend/src/services/app-connection/cloudflare/cloudflare-connection-schema.ts +++ b/backend/src/services/app-connection/cloudflare/cloudflare-connection-schema.ts @@ -9,6 +9,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { CloudflareConnectionMethod } from "./cloudflare-connection-enum"; const accountIdCharacterValidator = characterValidator([ @@ -41,7 +42,7 @@ export const SanitizedCloudflareConnectionSchema = z.discriminatedUnion("method" BaseCloudflareConnectionSchema.extend({ method: z.literal(CloudflareConnectionMethod.APIToken), credentials: CloudflareConnectionApiTokenCredentialsSchema.pick({ accountId: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Cloudflare]} (API Token)` })) ]); export const ValidateCloudflareConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -67,8 +68,10 @@ export const UpdateCloudflareConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Cloudflare)); -export const CloudflareConnectionListItemSchema = z.object({ - name: z.literal("Cloudflare"), - app: z.literal(AppConnection.Cloudflare), - methods: z.nativeEnum(CloudflareConnectionMethod).array() -}); +export const CloudflareConnectionListItemSchema = z + .object({ + name: z.literal("Cloudflare"), + app: z.literal(AppConnection.Cloudflare), + methods: z.nativeEnum(CloudflareConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Cloudflare] })); diff --git a/backend/src/services/app-connection/databricks/databricks-connection-schemas.ts b/backend/src/services/app-connection/databricks/databricks-connection-schemas.ts index 2ac58b070..ac0f1e830 100644 --- a/backend/src/services/app-connection/databricks/databricks-connection-schemas.ts +++ b/backend/src/services/app-connection/databricks/databricks-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { DatabricksConnectionMethod } from "./databricks-connection-enums"; export const DatabricksConnectionServicePrincipalInputCredentialsSchema = z.object({ @@ -42,7 +43,7 @@ export const SanitizedDatabricksConnectionSchema = z.discriminatedUnion("method" clientId: true, workspaceUrl: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Databricks]} (Service Principal)` })) ]); export const ValidateDatabricksConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -68,10 +69,12 @@ export const UpdateDatabricksConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Databricks)); -export const DatabricksConnectionListItemSchema = z.object({ - name: z.literal("Databricks"), - app: z.literal(AppConnection.Databricks), - // the below is preferable but currently breaks with our zod to json schema parser - // methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]), - methods: z.nativeEnum(DatabricksConnectionMethod).array() -}); +export const DatabricksConnectionListItemSchema = z + .object({ + name: z.literal("Databricks"), + app: z.literal(AppConnection.Databricks), + // the below is preferable but currently breaks with our zod to json schema parser + // methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]), + methods: z.nativeEnum(DatabricksConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Databricks] })); diff --git a/backend/src/services/app-connection/digital-ocean/digital-ocean-connection-schemas.ts b/backend/src/services/app-connection/digital-ocean/digital-ocean-connection-schemas.ts index 449721a3d..9c1ea1d51 100644 --- a/backend/src/services/app-connection/digital-ocean/digital-ocean-connection-schemas.ts +++ b/backend/src/services/app-connection/digital-ocean/digital-ocean-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { DigitalOceanConnectionMethod } from "./digital-ocean-connection-constants"; export const DigitalOceanConnectionMethodSchema = z @@ -36,7 +37,7 @@ export const SanitizedDigitalOceanConnectionSchema = z.discriminatedUnion("metho BaseDigitalOceanConnectionSchema.extend({ method: DigitalOceanConnectionMethodSchema, credentials: DigitalOceanConnectionAccessTokenCredentialsSchema.pick({}) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.DigitalOcean]} (Access Token)` })) ]); export const ValidateDigitalOceanConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -60,8 +61,10 @@ export const UpdateDigitalOceanConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.DigitalOcean)); -export const DigitalOceanConnectionListItemSchema = z.object({ - name: z.literal("Digital Ocean"), - app: z.literal(AppConnection.DigitalOcean), - methods: z.nativeEnum(DigitalOceanConnectionMethod).array() -}); +export const DigitalOceanConnectionListItemSchema = z + .object({ + name: z.literal("Digital Ocean"), + app: z.literal(AppConnection.DigitalOcean), + methods: z.nativeEnum(DigitalOceanConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.DigitalOcean] })); diff --git a/backend/src/services/app-connection/flyio/flyio-connection-schemas.ts b/backend/src/services/app-connection/flyio/flyio-connection-schemas.ts index 4466d24df..aa19aef44 100644 --- a/backend/src/services/app-connection/flyio/flyio-connection-schemas.ts +++ b/backend/src/services/app-connection/flyio/flyio-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { FlyioConnectionMethod } from "./flyio-connection-enums"; export const FlyioConnectionAccessTokenCredentialsSchema = z.object({ @@ -31,7 +32,7 @@ export const SanitizedFlyioConnectionSchema = z.discriminatedUnion("method", [ BaseFlyioConnectionSchema.extend({ method: z.literal(FlyioConnectionMethod.AccessToken), credentials: FlyioConnectionAccessTokenCredentialsSchema.pick({}) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Flyio]} (Access Token)` })) ]); export const ValidateFlyioConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -55,8 +56,10 @@ export const UpdateFlyioConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Flyio)); -export const FlyioConnectionListItemSchema = z.object({ - name: z.literal("Fly.io"), - app: z.literal(AppConnection.Flyio), - methods: z.nativeEnum(FlyioConnectionMethod).array() -}); +export const FlyioConnectionListItemSchema = z + .object({ + name: z.literal("Fly.io"), + app: z.literal(AppConnection.Flyio), + methods: z.nativeEnum(FlyioConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Flyio] })); diff --git a/backend/src/services/app-connection/gcp/gcp-connection-schemas.ts b/backend/src/services/app-connection/gcp/gcp-connection-schemas.ts index 3637f06dc..374be92e7 100644 --- a/backend/src/services/app-connection/gcp/gcp-connection-schemas.ts +++ b/backend/src/services/app-connection/gcp/gcp-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { GcpConnectionMethod } from "./gcp-connection-enums"; export const GcpConnectionServiceAccountImpersonationCredentialsSchema = z.object({ @@ -30,7 +31,9 @@ export const SanitizedGcpConnectionSchema = z.discriminatedUnion("method", [ BaseGcpConnectionSchema.extend({ method: z.literal(GcpConnectionMethod.ServiceAccountImpersonation), credentials: GcpConnectionServiceAccountImpersonationCredentialsSchema.pick({}) - }) + }).describe( + JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GCP]} (Service Account Impersonation)` }) + ) ]); export const ValidateGcpConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -56,10 +59,12 @@ export const UpdateGcpConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.GCP)); -export const GcpConnectionListItemSchema = z.object({ - name: z.literal("GCP"), - app: z.literal(AppConnection.GCP), - // the below is preferable but currently breaks with our zod to json schema parser - // methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]), - methods: z.nativeEnum(GcpConnectionMethod).array() -}); +export const GcpConnectionListItemSchema = z + .object({ + name: z.literal("GCP"), + app: z.literal(AppConnection.GCP), + // the below is preferable but currently breaks with our zod to json schema parser + // methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]), + methods: z.nativeEnum(GcpConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.GCP] })); diff --git a/backend/src/services/app-connection/github-radar/github-radar-connection-schemas.ts b/backend/src/services/app-connection/github-radar/github-radar-connection-schemas.ts index ebdfa45e1..4b5b52c6d 100644 --- a/backend/src/services/app-connection/github-radar/github-radar-connection-schemas.ts +++ b/backend/src/services/app-connection/github-radar/github-radar-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { GitHubRadarConnectionMethod } from "./github-radar-connection-enums"; export const GitHubRadarConnectionInputCredentialsSchema = z.object({ @@ -53,14 +54,16 @@ export const SanitizedGitHubRadarConnectionSchema = z.discriminatedUnion("method BaseGitHubRadarConnectionSchema.extend({ method: z.literal(GitHubRadarConnectionMethod.App), credentials: GitHubRadarConnectionOutputCredentialsSchema.pick({}) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GitHubRadar]} (GitHub App)` })) ]); -export const GitHubRadarConnectionListItemSchema = z.object({ - name: z.literal("GitHub Radar"), - app: z.literal(AppConnection.GitHubRadar), - // the below is preferable but currently breaks with our zod to json schema parser - // methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]), - methods: z.nativeEnum(GitHubRadarConnectionMethod).array(), - appClientSlug: z.string().optional() -}); +export const GitHubRadarConnectionListItemSchema = z + .object({ + name: z.literal("GitHub Radar"), + app: z.literal(AppConnection.GitHubRadar), + // the below is preferable but currently breaks with our zod to json schema parser + // methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]), + methods: z.nativeEnum(GitHubRadarConnectionMethod).array(), + appClientSlug: z.string().optional() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.GitHubRadar] })); diff --git a/backend/src/services/app-connection/github/github-connection-schemas.ts b/backend/src/services/app-connection/github/github-connection-schemas.ts index 20ef2c0e0..165a1c59f 100644 --- a/backend/src/services/app-connection/github/github-connection-schemas.ts +++ b/backend/src/services/app-connection/github/github-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { GitHubConnectionMethod } from "./github-connection-enums"; export const GitHubConnectionOAuthInputCredentialsSchema = z.union([ @@ -161,29 +162,31 @@ export const SanitizedGitHubConnectionSchema = z.discriminatedUnion("method", [ instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(), host: z.string().optional() }) - }), + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GitHub]} (GitHub App)` })), BaseGitHubConnectionSchema.extend({ method: z.literal(GitHubConnectionMethod.OAuth), credentials: z.object({ instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(), host: z.string().optional() }) - }), + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GitHub]} (OAuth)` })), BaseGitHubConnectionSchema.extend({ method: z.literal(GitHubConnectionMethod.Pat), credentials: z.object({ instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(), host: z.string().optional() }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GitHub]} (Personal Access Token)` })) ]); -export const GitHubConnectionListItemSchema = z.object({ - name: z.literal("GitHub"), - app: z.literal(AppConnection.GitHub), - // the below is preferable but currently breaks with our zod to json schema parser - // methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]), - methods: z.nativeEnum(GitHubConnectionMethod).array(), - oauthClientId: z.string().optional(), - appClientSlug: z.string().optional() -}); +export const GitHubConnectionListItemSchema = z + .object({ + name: z.literal("GitHub"), + app: z.literal(AppConnection.GitHub), + // the below is preferable but currently breaks with our zod to json schema parser + // methods: z.tuple([z.literal(GitHubConnectionMethod.App), z.literal(GitHubConnectionMethod.OAuth)]), + methods: z.nativeEnum(GitHubConnectionMethod).array(), + oauthClientId: z.string().optional(), + appClientSlug: z.string().optional() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.GitHub] })); diff --git a/backend/src/services/app-connection/gitlab/gitlab-connection-schemas.ts b/backend/src/services/app-connection/gitlab/gitlab-connection-schemas.ts index 936a370bf..63c635472 100644 --- a/backend/src/services/app-connection/gitlab/gitlab-connection-schemas.ts +++ b/backend/src/services/app-connection/gitlab/gitlab-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { GitLabAccessTokenType, GitLabConnectionMethod } from "./gitlab-connection-enums"; export const GitLabConnectionAccessTokenCredentialsSchema = z.object({ @@ -84,13 +85,13 @@ export const SanitizedGitLabConnectionSchema = z.discriminatedUnion("method", [ instanceUrl: true, accessTokenType: true }) - }), + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GitLab]} (Access Token)` })), BaseGitLabConnectionSchema.extend({ method: z.literal(GitLabConnectionMethod.OAuth), credentials: GitLabConnectionOAuthOutputCredentialsSchema.pick({ instanceUrl: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.GitLab]} (OAuth)` })) ]); export const ValidateGitLabConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -130,9 +131,11 @@ export const UpdateGitLabConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.GitLab)); -export const GitLabConnectionListItemSchema = z.object({ - name: z.literal("GitLab"), - app: z.literal(AppConnection.GitLab), - methods: z.nativeEnum(GitLabConnectionMethod).array(), - oauthClientId: z.string().optional() -}); +export const GitLabConnectionListItemSchema = z + .object({ + name: z.literal("GitLab"), + app: z.literal(AppConnection.GitLab), + methods: z.nativeEnum(GitLabConnectionMethod).array(), + oauthClientId: z.string().optional() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.GitLab] })); diff --git a/backend/src/services/app-connection/hc-vault/hc-vault-connection-schemas.ts b/backend/src/services/app-connection/hc-vault/hc-vault-connection-schemas.ts index 57e8fbeaf..26af6db65 100644 --- a/backend/src/services/app-connection/hc-vault/hc-vault-connection-schemas.ts +++ b/backend/src/services/app-connection/hc-vault/hc-vault-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { HCVaultConnectionMethod } from "./hc-vault-connection-enums"; const InstanceUrlSchema = z @@ -59,7 +60,7 @@ export const SanitizedHCVaultConnectionSchema = z.discriminatedUnion("method", [ namespace: true, instanceUrl: true }) - }), + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.HCVault]} (Access Token)` })), BaseHCVaultConnectionSchema.extend({ method: z.literal(HCVaultConnectionMethod.AppRole), credentials: HCVaultConnectionAppRoleCredentialsSchema.pick({ @@ -67,7 +68,7 @@ export const SanitizedHCVaultConnectionSchema = z.discriminatedUnion("method", [ instanceUrl: true, roleId: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.HCVault]} (App Role)` })) ]); export const ValidateHCVaultConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -100,8 +101,10 @@ export const UpdateHCVaultConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.HCVault, { supportsGateways: true })); -export const HCVaultConnectionListItemSchema = z.object({ - name: z.literal("HCVault"), - app: z.literal(AppConnection.HCVault), - methods: z.nativeEnum(HCVaultConnectionMethod).array() -}); +export const HCVaultConnectionListItemSchema = z + .object({ + name: z.literal("HCVault"), + app: z.literal(AppConnection.HCVault), + methods: z.nativeEnum(HCVaultConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.HCVault] })); diff --git a/backend/src/services/app-connection/heroku/heroku-connection-schemas.ts b/backend/src/services/app-connection/heroku/heroku-connection-schemas.ts index 99d637dd5..710fc706a 100644 --- a/backend/src/services/app-connection/heroku/heroku-connection-schemas.ts +++ b/backend/src/services/app-connection/heroku/heroku-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { HerokuConnectionMethod } from "./heroku-connection-enums"; export const HerokuConnectionAuthTokenCredentialsSchema = z.object({ @@ -51,11 +52,11 @@ export const SanitizedHerokuConnectionSchema = z.discriminatedUnion("method", [ BaseHerokuConnectionSchema.extend({ method: z.literal(HerokuConnectionMethod.AuthToken), credentials: HerokuConnectionAuthTokenCredentialsSchema.pick({}) - }), + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Heroku]} (Auth Token)` })), BaseHerokuConnectionSchema.extend({ method: z.literal(HerokuConnectionMethod.OAuth), credentials: HerokuConnectionOAuthOutputCredentialsSchema.pick({}) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Heroku]} (OAuth)` })) ]); export const ValidateHerokuConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -95,9 +96,11 @@ export const UpdateHerokuConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Heroku)); -export const HerokuConnectionListItemSchema = z.object({ - name: z.literal("Heroku"), - app: z.literal(AppConnection.Heroku), - methods: z.nativeEnum(HerokuConnectionMethod).array(), - oauthClientId: z.string().optional() -}); +export const HerokuConnectionListItemSchema = z + .object({ + name: z.literal("Heroku"), + app: z.literal(AppConnection.Heroku), + methods: z.nativeEnum(HerokuConnectionMethod).array(), + oauthClientId: z.string().optional() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Heroku] })); diff --git a/backend/src/services/app-connection/humanitec/humanitec-connection-schemas.ts b/backend/src/services/app-connection/humanitec/humanitec-connection-schemas.ts index 4e6cb0078..7c0a5c1cd 100644 --- a/backend/src/services/app-connection/humanitec/humanitec-connection-schemas.ts +++ b/backend/src/services/app-connection/humanitec/humanitec-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { HumanitecConnectionMethod } from "./humanitec-connection-enums"; export const HumanitecConnectionAccessTokenCredentialsSchema = z.object({ @@ -25,7 +26,7 @@ export const SanitizedHumanitecConnectionSchema = z.discriminatedUnion("method", BaseHumanitecConnectionSchema.extend({ method: z.literal(HumanitecConnectionMethod.ApiToken), credentials: HumanitecConnectionAccessTokenCredentialsSchema.pick({}) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Humanitec]} (API Token)` })) ]); export const ValidateHumanitecConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -51,8 +52,10 @@ export const UpdateHumanitecConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Humanitec)); -export const HumanitecConnectionListItemSchema = z.object({ - name: z.literal("Humanitec"), - app: z.literal(AppConnection.Humanitec), - methods: z.nativeEnum(HumanitecConnectionMethod).array() -}); +export const HumanitecConnectionListItemSchema = z + .object({ + name: z.literal("Humanitec"), + app: z.literal(AppConnection.Humanitec), + methods: z.nativeEnum(HumanitecConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Humanitec] })); diff --git a/backend/src/services/app-connection/laravel-forge/laravel-forge-connection-schemas.ts b/backend/src/services/app-connection/laravel-forge/laravel-forge-connection-schemas.ts index 1647b38a1..e5c526be5 100644 --- a/backend/src/services/app-connection/laravel-forge/laravel-forge-connection-schemas.ts +++ b/backend/src/services/app-connection/laravel-forge/laravel-forge-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { LaravelForgeConnectionMethod } from "./laravel-forge-connection-enums"; export const LaravelForgeConnectionApiTokenCredentialsSchema = z.object({ @@ -25,7 +26,7 @@ export const SanitizedLaravelForgeConnectionSchema = z.discriminatedUnion("metho BaseLaravelForgeConnectionSchema.extend({ method: z.literal(LaravelForgeConnectionMethod.ApiToken), credentials: LaravelForgeConnectionApiTokenCredentialsSchema.pick({}) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.LaravelForge]} (API Token)` })) ]); export const ValidateLaravelForgeConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -51,8 +52,10 @@ export const UpdateLaravelForgeConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.LaravelForge)); -export const LaravelForgeConnectionListItemSchema = z.object({ - name: z.literal("Laravel Forge"), - app: z.literal(AppConnection.LaravelForge), - methods: z.nativeEnum(LaravelForgeConnectionMethod).array() -}); +export const LaravelForgeConnectionListItemSchema = z + .object({ + name: z.literal("Laravel Forge"), + app: z.literal(AppConnection.LaravelForge), + methods: z.nativeEnum(LaravelForgeConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.LaravelForge] })); diff --git a/backend/src/services/app-connection/ldap/ldap-connection-fns.ts b/backend/src/services/app-connection/ldap/ldap-connection-fns.ts index 03005c7d7..961dcff59 100644 --- a/backend/src/services/app-connection/ldap/ldap-connection-fns.ts +++ b/backend/src/services/app-connection/ldap/ldap-connection-fns.ts @@ -1,6 +1,11 @@ import ldap from "ldapjs"; +import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; +import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; +import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; +import { GatewayProxyProtocol } from "@app/lib/gateway"; +import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2"; import { logger } from "@app/lib/logger"; import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; @@ -8,6 +13,66 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums import { LdapConnectionMethod } from "./ldap-connection-enums"; import { TLdapConnectionConfig } from "./ldap-connection-types"; +const LDAP_TIMEOUT = 15_000; + +const parseLdapUrl = (url: string): { protocol: string; host: string; port: number } => { + const urlObj = new URL(url); + const isSSL = urlObj.protocol === "ldaps:"; + const defaultPort = isSSL ? 636 : 389; + + return { + protocol: urlObj.protocol.replace(":", ""), + host: urlObj.hostname, + port: urlObj.port ? parseInt(urlObj.port, 10) : defaultPort + }; +}; + +const constructLdapUrl = (protocol: string, host: string, port: number): string => { + return `${protocol}://${host}:${port}`; +}; + +const setupLdapClientHandlers = ( + client: ldap.Client, + dn: string, + password: string, + onSuccess: (client: ldap.Client) => T | Promise +): Promise => { + return new Promise((resolve, reject) => { + const handleError = (errorType: string, err: Error) => { + logger.error(err, errorType); + client.destroy(); + reject(new Error(`${errorType.replace("LDAP ", "")} - ${err.message}`)); + }; + + client.on("error", (err: Error) => handleError("LDAP Error", err)); + client.on("connectError", (err: Error) => handleError("LDAP Connection Error", err)); + client.on("connectRefused", (err: Error) => handleError("LDAP Connection Refused", err)); + client.on("connectTimeout", (err: Error) => handleError("LDAP Connection Timeout", err)); + + client.on("connect", () => { + client.bind(dn, password, (err) => { + if (err) { + logger.error(err, "LDAP Bind Error"); + client.destroy(); + reject(new Error(`Bind Error: ${err.message}`)); + return; + } + + try { + const result = onSuccess(client); + if (result instanceof Promise) { + result.then((value) => resolve(value)).catch(reject); + } else { + resolve(result); + } + } catch (error) { + reject(error); + } + }); + }); + }); +}; + export const getLdapConnectionListItem = () => { return { name: "LDAP" as const, @@ -16,8 +81,6 @@ export const getLdapConnectionListItem = () => { }; }; -const LDAP_TIMEOUT = 15_000; - export const getLdapConnectionClient = async ({ url, dn, @@ -25,78 +88,112 @@ export const getLdapConnectionClient = async ({ sslCertificate, sslRejectUnauthorized = true }: TLdapConnectionConfig["credentials"]) => { - await blockLocalAndPrivateIpAddresses(url); + await blockLocalAndPrivateIpAddresses(url, false); const isSSL = url.startsWith("ldaps"); - return new Promise((resolve, reject) => { - const client = ldap.createClient({ - url, - timeout: LDAP_TIMEOUT, - connectTimeout: LDAP_TIMEOUT, - tlsOptions: isSSL - ? { - rejectUnauthorized: sslRejectUnauthorized, - ca: sslCertificate ? [sslCertificate] : undefined - } - : undefined - }); - - client.on("error", (err: Error) => { - logger.error(err, "LDAP Error"); - client.destroy(); - reject(new Error(`Provider Error - ${err.message}`)); - }); - - client.on("connectError", (err: Error) => { - logger.error(err, "LDAP Connection Error"); - client.destroy(); - reject(new Error(`Provider Connect Error - ${err.message}`)); - }); - - client.on("connectRefused", (err: Error) => { - logger.error(err, "LDAP Connection Refused"); - client.destroy(); - reject(new Error(`Provider Connection Refused - ${err.message}`)); - }); - - client.on("connectTimeout", (err: Error) => { - logger.error(err, "LDAP Connection Timeout"); - client.destroy(); - reject(new Error(`Provider Connection Timeout - ${err.message}`)); - }); - - client.on("connect", () => { - client.bind(dn, password, (err) => { - if (err) { - logger.error(err, "LDAP Bind Error"); - reject(new Error(`Bind Error: ${err.message}`)); - client.destroy(); + const client = ldap.createClient({ + url, + timeout: LDAP_TIMEOUT, + connectTimeout: LDAP_TIMEOUT, + tlsOptions: isSSL + ? { + rejectUnauthorized: sslRejectUnauthorized, + ca: sslCertificate ? [sslCertificate] : undefined } - - resolve(client); - }); - }); + : undefined }); + + return setupLdapClientHandlers(client, dn, password, (ldapClient) => ldapClient); }; -export const validateLdapConnectionCredentials = async ({ credentials }: TLdapConnectionConfig) => { - let client: ldap.Client | undefined; +export const executeWithPotentialGateway = async ( + config: TLdapConnectionConfig, + gatewayV2Service: Pick, + operation: (client: ldap.Client) => Promise +): Promise => { + const { gatewayId, credentials } = config; + const { protocol, host, port } = parseLdapUrl(credentials.url); + const appCfg = getConfig(); - try { - client = await getLdapConnectionClient(credentials); + if (gatewayId && gatewayV2Service) { + await blockLocalAndPrivateIpAddresses(credentials.url, true); + const platformConnectionDetails = await gatewayV2Service.getPlatformConnectionDetailsByGatewayId({ + gatewayId, + targetHost: host, + targetPort: port + }); - // this shouldn't occur as handle connection error events in client but here as fallback - if (!client.connected) { - throw new BadRequestError({ message: "Unable to connect to LDAP server" }); + if (!platformConnectionDetails) { + throw new BadRequestError({ message: "Unable to connect to gateway, no platform connection details found" }); } - return credentials; - } catch (e: unknown) { - throw new BadRequestError({ - message: `Unable to validate connection: ${(e as Error).message || "verify credentials"}` - }); + return withGatewayV2Proxy( + async (proxyPort) => { + const proxyUrl = constructLdapUrl(protocol, "localhost", proxyPort); + const isSSL = protocol === "ldaps"; + + const client = ldap.createClient({ + url: proxyUrl, + timeout: LDAP_TIMEOUT, + connectTimeout: LDAP_TIMEOUT, + tlsOptions: isSSL + ? { + rejectUnauthorized: config.credentials.sslRejectUnauthorized, + ca: config.credentials.sslCertificate ? [config.credentials.sslCertificate] : undefined, + servername: host, + // bypass hostname verification for development + ...(appCfg.isDevelopmentMode ? { checkServerIdentity: () => undefined } : {}) + } + : undefined + }); + + return setupLdapClientHandlers(client, credentials.dn, credentials.password, async (ldapClient) => { + try { + return await operation(ldapClient); + } finally { + ldapClient.destroy(); + } + }); + }, + { + protocol: GatewayProxyProtocol.Tcp, + relayHost: platformConnectionDetails.relayHost, + gateway: platformConnectionDetails.gateway, + relay: platformConnectionDetails.relay + } + ); + } + + // Non-gateway path - calls getLdapConnectionClient which has validation + const client = await getLdapConnectionClient(credentials); + try { + return await operation(client); } finally { - client?.destroy(); + client.destroy(); + } +}; + +export const validateLdapConnectionCredentials = async ( + config: TLdapConnectionConfig, + gatewayService: Pick, + gatewayV2Service: Pick +) => { + try { + await executeWithPotentialGateway(config, gatewayV2Service, async (client) => { + // this shouldn't occur as handle connection error events in client but here as fallback + if (!client.connected) { + throw new BadRequestError({ message: "Unable to connect to LDAP server" }); + } + }); + + return config.credentials; + } catch (error) { + throw new BadRequestError({ + message: `Unable to validate connection: ${ + (error as Error)?.message?.replaceAll(config.credentials.password, "********************") ?? + "verify credentials" + }` + }); } }; diff --git a/backend/src/services/app-connection/ldap/ldap-connection-schemas.ts b/backend/src/services/app-connection/ldap/ldap-connection-schemas.ts index 134b9667b..54a6335f4 100644 --- a/backend/src/services/app-connection/ldap/ldap-connection-schemas.ts +++ b/backend/src/services/app-connection/ldap/ldap-connection-schemas.ts @@ -9,6 +9,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { LdapConnectionMethod, LdapProvider } from "./ldap-connection-enums"; export const LdapConnectionSimpleBindCredentialsSchema = z.object({ @@ -61,7 +62,7 @@ export const SanitizedLdapConnectionSchema = z.discriminatedUnion("method", [ sslRejectUnauthorized: true, sslCertificate: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.LDAP]} (Simple Bind)` })) ]); export const ValidateLdapConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -74,7 +75,9 @@ export const ValidateLdapConnectionCredentialsSchema = z.discriminatedUnion("met ]); export const CreateLdapConnectionSchema = ValidateLdapConnectionCredentialsSchema.and( - GenericCreateAppConnectionFieldsSchema(AppConnection.LDAP) + GenericCreateAppConnectionFieldsSchema(AppConnection.LDAP, { + supportsGateways: true + }) ); export const UpdateLdapConnectionSchema = z @@ -83,12 +86,18 @@ export const UpdateLdapConnectionSchema = z AppConnections.UPDATE(AppConnection.LDAP).credentials ) }) - .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.LDAP)); + .and( + GenericUpdateAppConnectionFieldsSchema(AppConnection.LDAP, { + supportsGateways: true + }) + ); -export const LdapConnectionListItemSchema = z.object({ - name: z.literal("LDAP"), - app: z.literal(AppConnection.LDAP), - // the below is preferable but currently breaks with our zod to json schema parser - // methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]), - methods: z.nativeEnum(LdapConnectionMethod).array() -}); +export const LdapConnectionListItemSchema = z + .object({ + name: z.literal("LDAP"), + app: z.literal(AppConnection.LDAP), + // the below is preferable but currently breaks with our zod to json schema parser + // methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]), + methods: z.nativeEnum(LdapConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.LDAP] })); diff --git a/backend/src/services/app-connection/ldap/ldap-connection-types.ts b/backend/src/services/app-connection/ldap/ldap-connection-types.ts index ee69b2542..581fca560 100644 --- a/backend/src/services/app-connection/ldap/ldap-connection-types.ts +++ b/backend/src/services/app-connection/ldap/ldap-connection-types.ts @@ -17,6 +17,9 @@ export type TLdapConnectionInput = z.infer & export type TValidateLdapConnectionCredentialsSchema = typeof ValidateLdapConnectionCredentialsSchema; -export type TLdapConnectionConfig = DiscriminativePick & { +export type TLdapConnectionConfig = DiscriminativePick< + TLdapConnectionInput, + "method" | "app" | "credentials" | "gatewayId" +> & { orgId: string; }; diff --git a/backend/src/services/app-connection/mssql/mssql-connection-schemas.ts b/backend/src/services/app-connection/mssql/mssql-connection-schemas.ts index f8d380949..d82b03c44 100644 --- a/backend/src/services/app-connection/mssql/mssql-connection-schemas.ts +++ b/backend/src/services/app-connection/mssql/mssql-connection-schemas.ts @@ -8,6 +8,7 @@ import { } from "@app/services/app-connection/app-connection-schemas"; import { AppConnection } from "../app-connection-enums"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { BaseSqlUsernameAndPasswordConnectionSchema } from "../shared/sql"; import { MsSqlConnectionMethod } from "./mssql-connection-enums"; @@ -34,7 +35,7 @@ export const SanitizedMsSqlConnectionSchema = z.discriminatedUnion("method", [ sslRejectUnauthorized: true, sslCertificate: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.MsSql]} (Username and Password)` })) ]); export const ValidateMsSqlConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -68,9 +69,11 @@ export const UpdateMsSqlConnectionSchema = z }) ); -export const MsSqlConnectionListItemSchema = z.object({ - name: z.literal("Microsoft SQL Server"), - app: z.literal(AppConnection.MsSql), - methods: z.nativeEnum(MsSqlConnectionMethod).array(), - supportsPlatformManagement: z.literal(true) -}); +export const MsSqlConnectionListItemSchema = z + .object({ + name: z.literal("Microsoft SQL Server"), + app: z.literal(AppConnection.MsSql), + methods: z.nativeEnum(MsSqlConnectionMethod).array(), + supportsPlatformManagement: z.literal(true) + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.MsSql] })); diff --git a/backend/src/services/app-connection/mysql/mysql-connection-schemas.ts b/backend/src/services/app-connection/mysql/mysql-connection-schemas.ts index 51a533395..3ed98f98b 100644 --- a/backend/src/services/app-connection/mysql/mysql-connection-schemas.ts +++ b/backend/src/services/app-connection/mysql/mysql-connection-schemas.ts @@ -8,6 +8,7 @@ import { } from "@app/services/app-connection/app-connection-schemas"; import { AppConnection } from "../app-connection-enums"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { BaseSqlUsernameAndPasswordConnectionSchema } from "../shared/sql"; import { MySqlConnectionMethod } from "./mysql-connection-enums"; @@ -32,7 +33,7 @@ export const SanitizedMySqlConnectionSchema = z.discriminatedUnion("method", [ sslRejectUnauthorized: true, sslCertificate: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.MySql]} (Username and Password)` })) ]); export const ValidateMySqlConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -66,9 +67,11 @@ export const UpdateMySqlConnectionSchema = z }) ); -export const MySqlConnectionListItemSchema = z.object({ - name: z.literal("MySQL"), - app: z.literal(AppConnection.MySql), - methods: z.nativeEnum(MySqlConnectionMethod).array(), - supportsPlatformManagement: z.literal(true) -}); +export const MySqlConnectionListItemSchema = z + .object({ + name: z.literal("MySQL"), + app: z.literal(AppConnection.MySql), + methods: z.nativeEnum(MySqlConnectionMethod).array(), + supportsPlatformManagement: z.literal(true) + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.MySql] })); diff --git a/backend/src/services/app-connection/netlify/netlify-connection-schemas.ts b/backend/src/services/app-connection/netlify/netlify-connection-schemas.ts index 45fb89760..31efafb19 100644 --- a/backend/src/services/app-connection/netlify/netlify-connection-schemas.ts +++ b/backend/src/services/app-connection/netlify/netlify-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { NetlifyConnectionMethod } from "./netlify-connection-constants"; export const NetlifyConnectionMethodSchema = z @@ -36,7 +37,7 @@ export const SanitizedNetlifyConnectionSchema = z.discriminatedUnion("method", [ BaseNetlifyConnectionSchema.extend({ method: NetlifyConnectionMethodSchema, credentials: NetlifyConnectionAccessTokenCredentialsSchema.pick({}) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Netlify]} (Access Token)` })) ]); export const ValidateNetlifyConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -60,8 +61,10 @@ export const UpdateNetlifyConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Netlify)); -export const NetlifyConnectionListItemSchema = z.object({ - name: z.literal("Netlify"), - app: z.literal(AppConnection.Netlify), - methods: z.nativeEnum(NetlifyConnectionMethod).array() -}); +export const NetlifyConnectionListItemSchema = z + .object({ + name: z.literal("Netlify"), + app: z.literal(AppConnection.Netlify), + methods: z.nativeEnum(NetlifyConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Netlify] })); diff --git a/backend/src/services/app-connection/northflank/northflank-connection-schemas.ts b/backend/src/services/app-connection/northflank/northflank-connection-schemas.ts index 95be757f6..cf0b06c00 100644 --- a/backend/src/services/app-connection/northflank/northflank-connection-schemas.ts +++ b/backend/src/services/app-connection/northflank/northflank-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { NorthflankConnectionMethod } from "./northflank-connection-enums"; export const NorthflankConnectionApiTokenCredentialsSchema = z.object({ @@ -27,7 +28,7 @@ export const SanitizedNorthflankConnectionSchema = z.discriminatedUnion("method" BaseNorthflankConnectionSchema.extend({ method: z.literal(NorthflankConnectionMethod.ApiToken), credentials: NorthflankConnectionApiTokenCredentialsSchema.pick({}) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Northflank]} (API Token)` })) ]); export const ValidateNorthflankConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -53,8 +54,10 @@ export const UpdateNorthflankConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Northflank)); -export const NorthflankConnectionListItemSchema = z.object({ - name: z.literal("Northflank"), - app: z.literal(AppConnection.Northflank), - methods: z.nativeEnum(NorthflankConnectionMethod).array() -}); +export const NorthflankConnectionListItemSchema = z + .object({ + name: z.literal("Northflank"), + app: z.literal(AppConnection.Northflank), + methods: z.nativeEnum(NorthflankConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Northflank] })); diff --git a/backend/src/services/app-connection/okta/okta-connection-schemas.ts b/backend/src/services/app-connection/okta/okta-connection-schemas.ts index 37ce0ec11..cdde954e2 100644 --- a/backend/src/services/app-connection/okta/okta-connection-schemas.ts +++ b/backend/src/services/app-connection/okta/okta-connection-schemas.ts @@ -9,6 +9,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { OktaConnectionMethod } from "./okta-connection-enums"; export const OktaConnectionApiTokenCredentialsSchema = z.object({ @@ -40,7 +41,7 @@ export const SanitizedOktaConnectionSchema = z.discriminatedUnion("method", [ credentials: OktaConnectionApiTokenCredentialsSchema.pick({ instanceUrl: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Okta]} (API Token)` })) ]); export const ValidateOktaConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -62,8 +63,10 @@ export const UpdateOktaConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Okta)); -export const OktaConnectionListItemSchema = z.object({ - name: z.literal("Okta"), - app: z.literal(AppConnection.Okta), - methods: z.nativeEnum(OktaConnectionMethod).array() -}); +export const OktaConnectionListItemSchema = z + .object({ + name: z.literal("Okta"), + app: z.literal(AppConnection.Okta), + methods: z.nativeEnum(OktaConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Okta] })); diff --git a/backend/src/services/app-connection/postgres/postgres-connection-schemas.ts b/backend/src/services/app-connection/postgres/postgres-connection-schemas.ts index da74bd669..a9e6e113b 100644 --- a/backend/src/services/app-connection/postgres/postgres-connection-schemas.ts +++ b/backend/src/services/app-connection/postgres/postgres-connection-schemas.ts @@ -8,6 +8,7 @@ import { } from "@app/services/app-connection/app-connection-schemas"; import { AppConnection } from "../app-connection-enums"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { BaseSqlUsernameAndPasswordConnectionSchema } from "../shared/sql"; import { PostgresConnectionMethod } from "./postgres-connection-enums"; @@ -32,7 +33,7 @@ export const SanitizedPostgresConnectionSchema = z.discriminatedUnion("method", sslRejectUnauthorized: true, sslCertificate: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Postgres]} (Username and Password)` })) ]); export const ValidatePostgresConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -66,9 +67,11 @@ export const UpdatePostgresConnectionSchema = z }) ); -export const PostgresConnectionListItemSchema = z.object({ - name: z.literal("PostgreSQL"), - app: z.literal(AppConnection.Postgres), - methods: z.nativeEnum(PostgresConnectionMethod).array(), - supportsPlatformManagement: z.literal(true) -}); +export const PostgresConnectionListItemSchema = z + .object({ + name: z.literal("PostgreSQL"), + app: z.literal(AppConnection.Postgres), + methods: z.nativeEnum(PostgresConnectionMethod).array(), + supportsPlatformManagement: z.literal(true) + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Postgres] })); diff --git a/backend/src/services/app-connection/railway/railway-connection-schemas.ts b/backend/src/services/app-connection/railway/railway-connection-schemas.ts index 066258f1e..8b4f87116 100644 --- a/backend/src/services/app-connection/railway/railway-connection-schemas.ts +++ b/backend/src/services/app-connection/railway/railway-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { RailwayConnectionMethod } from "./railway-connection-constants"; export const RailwayConnectionMethodSchema = z @@ -36,7 +37,7 @@ export const SanitizedRailwayConnectionSchema = z.discriminatedUnion("method", [ BaseRailwayConnectionSchema.extend({ method: RailwayConnectionMethodSchema, credentials: RailwayConnectionAccessTokenCredentialsSchema.pick({}) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Railway]} (Access Token)` })) ]); export const ValidateRailwayConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -60,11 +61,13 @@ export const UpdateRailwayConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Railway)); -export const RailwayConnectionListItemSchema = z.object({ - name: z.literal("Railway"), - app: z.literal(AppConnection.Railway), - methods: z.nativeEnum(RailwayConnectionMethod).array() -}); +export const RailwayConnectionListItemSchema = z + .object({ + name: z.literal("Railway"), + app: z.literal(AppConnection.Railway), + methods: z.nativeEnum(RailwayConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Railway] })); export const RailwayResourceSchema = z.object({ node: z.object({ diff --git a/backend/src/services/app-connection/redis/redis-connection-schemas.ts b/backend/src/services/app-connection/redis/redis-connection-schemas.ts index f29a2d036..8d5abadcf 100644 --- a/backend/src/services/app-connection/redis/redis-connection-schemas.ts +++ b/backend/src/services/app-connection/redis/redis-connection-schemas.ts @@ -8,6 +8,7 @@ import { } from "@app/services/app-connection/app-connection-schemas"; import { AppConnection } from "../app-connection-enums"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { RedisConnectionMethod } from "./redis-connection-enums"; export const BaseRedisUsernameAndPasswordConnectionSchema = z.object({ @@ -45,7 +46,7 @@ export const SanitizedRedisConnectionSchema = z.discriminatedUnion("method", [ sslRejectUnauthorized: true, sslCertificate: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Redis]} (Username and Password)` })) ]); export const ValidateRedisConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -79,9 +80,11 @@ export const UpdateRedisConnectionSchema = z }) ); -export const RedisConnectionListItemSchema = z.object({ - name: z.literal("Redis"), - app: z.literal(AppConnection.Redis), - methods: z.nativeEnum(RedisConnectionMethod).array(), - supportsPlatformManagement: z.literal(false) -}); +export const RedisConnectionListItemSchema = z + .object({ + name: z.literal("Redis"), + app: z.literal(AppConnection.Redis), + methods: z.nativeEnum(RedisConnectionMethod).array(), + supportsPlatformManagement: z.literal(false) + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Redis] })); diff --git a/backend/src/services/app-connection/render/render-connection-schema.ts b/backend/src/services/app-connection/render/render-connection-schema.ts index 77cc46714..cf1d35b02 100644 --- a/backend/src/services/app-connection/render/render-connection-schema.ts +++ b/backend/src/services/app-connection/render/render-connection-schema.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { RenderConnectionMethod } from "./render-connection-enums"; export const RenderConnectionApiKeyCredentialsSchema = z.object({ @@ -25,7 +26,7 @@ export const SanitizedRenderConnectionSchema = z.discriminatedUnion("method", [ BaseRenderConnectionSchema.extend({ method: z.literal(RenderConnectionMethod.ApiKey), credentials: RenderConnectionApiKeyCredentialsSchema.pick({}) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Render]} (API Key)` })) ]); export const ValidateRenderConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -49,8 +50,10 @@ export const UpdateRenderConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Render)); -export const RenderConnectionListItemSchema = z.object({ - name: z.literal("Render"), - app: z.literal(AppConnection.Render), - methods: z.nativeEnum(RenderConnectionMethod).array() -}); +export const RenderConnectionListItemSchema = z + .object({ + name: z.literal("Render"), + app: z.literal(AppConnection.Render), + methods: z.nativeEnum(RenderConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Render] })); diff --git a/backend/src/services/app-connection/supabase/supabase-connection-schemas.ts b/backend/src/services/app-connection/supabase/supabase-connection-schemas.ts index 9a06b6554..d66c2a7fc 100644 --- a/backend/src/services/app-connection/supabase/supabase-connection-schemas.ts +++ b/backend/src/services/app-connection/supabase/supabase-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { SupabaseConnectionMethod } from "./supabase-connection-constants"; export const SupabaseConnectionMethodSchema = z @@ -39,7 +40,7 @@ export const SanitizedSupabaseConnectionSchema = z.discriminatedUnion("method", credentials: SupabaseConnectionAccessTokenCredentialsSchema.pick({ instanceUrl: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Supabase]} (Access Token)` })) ]); export const ValidateSupabaseConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -63,8 +64,10 @@ export const UpdateSupabaseConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Supabase)); -export const SupabaseConnectionListItemSchema = z.object({ - name: z.literal("Supabase"), - app: z.literal(AppConnection.Supabase), - methods: z.nativeEnum(SupabaseConnectionMethod).array() -}); +export const SupabaseConnectionListItemSchema = z + .object({ + name: z.literal("Supabase"), + app: z.literal(AppConnection.Supabase), + methods: z.nativeEnum(SupabaseConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Supabase] })); diff --git a/backend/src/services/app-connection/teamcity/teamcity-connection-schemas.ts b/backend/src/services/app-connection/teamcity/teamcity-connection-schemas.ts index 30494e2ba..aafc61daf 100644 --- a/backend/src/services/app-connection/teamcity/teamcity-connection-schemas.ts +++ b/backend/src/services/app-connection/teamcity/teamcity-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { TeamCityConnectionMethod } from "./teamcity-connection-enums"; export const TeamCityConnectionAccessTokenCredentialsSchema = z.object({ @@ -37,7 +38,7 @@ export const SanitizedTeamCityConnectionSchema = z.discriminatedUnion("method", credentials: TeamCityConnectionAccessTokenCredentialsSchema.pick({ instanceUrl: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.TeamCity]} (Access Token)` })) ]); export const ValidateTeamCityConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -63,8 +64,10 @@ export const UpdateTeamCityConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.TeamCity)); -export const TeamCityConnectionListItemSchema = z.object({ - name: z.literal("TeamCity"), - app: z.literal(AppConnection.TeamCity), - methods: z.nativeEnum(TeamCityConnectionMethod).array() -}); +export const TeamCityConnectionListItemSchema = z + .object({ + name: z.literal("TeamCity"), + app: z.literal(AppConnection.TeamCity), + methods: z.nativeEnum(TeamCityConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.TeamCity] })); diff --git a/backend/src/services/app-connection/terraform-cloud/terraform-cloud-connection-schemas.ts b/backend/src/services/app-connection/terraform-cloud/terraform-cloud-connection-schemas.ts index 0d408ba4f..006d32217 100644 --- a/backend/src/services/app-connection/terraform-cloud/terraform-cloud-connection-schemas.ts +++ b/backend/src/services/app-connection/terraform-cloud/terraform-cloud-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { TerraformCloudConnectionMethod } from "./terraform-cloud-connection-enums"; export const TerraformCloudConnectionAccessTokenCredentialsSchema = z.object({ @@ -27,7 +28,7 @@ export const SanitizedTerraformCloudConnectionSchema = z.discriminatedUnion("met BaseTerraformCloudConnectionSchema.extend({ method: z.literal(TerraformCloudConnectionMethod.ApiToken), credentials: TerraformCloudConnectionAccessTokenCredentialsSchema.pick({}) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.TerraformCloud]} (API Token)` })) ]); export const ValidateTerraformCloudConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -53,8 +54,10 @@ export const UpdateTerraformCloudConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.TerraformCloud)); -export const TerraformCloudConnectionListItemSchema = z.object({ - name: z.literal("Terraform Cloud"), - app: z.literal(AppConnection.TerraformCloud), - methods: z.nativeEnum(TerraformCloudConnectionMethod).array() -}); +export const TerraformCloudConnectionListItemSchema = z + .object({ + name: z.literal("Terraform Cloud"), + app: z.literal(AppConnection.TerraformCloud), + methods: z.nativeEnum(TerraformCloudConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.TerraformCloud] })); diff --git a/backend/src/services/app-connection/vercel/vercel-connection-schemas.ts b/backend/src/services/app-connection/vercel/vercel-connection-schemas.ts index 60baa4f5c..e93aface0 100644 --- a/backend/src/services/app-connection/vercel/vercel-connection-schemas.ts +++ b/backend/src/services/app-connection/vercel/vercel-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { VercelConnectionMethod } from "./vercel-connection-enums"; export const VercelConnectionAccessTokenCredentialsSchema = z.object({ @@ -27,7 +28,7 @@ export const SanitizedVercelConnectionSchema = z.discriminatedUnion("method", [ BaseVercelConnectionSchema.extend({ method: z.literal(VercelConnectionMethod.ApiToken), credentials: VercelConnectionAccessTokenCredentialsSchema.pick({}) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Vercel]} (API Token)` })) ]); export const ValidateVercelConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -51,8 +52,10 @@ export const UpdateVercelConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Vercel)); -export const VercelConnectionListItemSchema = z.object({ - name: z.literal("Vercel"), - app: z.literal(AppConnection.Vercel), - methods: z.nativeEnum(VercelConnectionMethod).array() -}); +export const VercelConnectionListItemSchema = z + .object({ + name: z.literal("Vercel"), + app: z.literal(AppConnection.Vercel), + methods: z.nativeEnum(VercelConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Vercel] })); diff --git a/backend/src/services/app-connection/windmill/windmill-connection-schemas.ts b/backend/src/services/app-connection/windmill/windmill-connection-schemas.ts index eb7f74ecd..864a4635b 100644 --- a/backend/src/services/app-connection/windmill/windmill-connection-schemas.ts +++ b/backend/src/services/app-connection/windmill/windmill-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { WindmillConnectionMethod } from "./windmill-connection-enums"; export const WindmillConnectionAccessTokenCredentialsSchema = z.object({ @@ -37,7 +38,7 @@ export const SanitizedWindmillConnectionSchema = z.discriminatedUnion("method", credentials: WindmillConnectionAccessTokenCredentialsSchema.pick({ instanceUrl: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Windmill]} (Access Token)` })) ]); export const ValidateWindmillConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -63,8 +64,10 @@ export const UpdateWindmillConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Windmill)); -export const WindmillConnectionListItemSchema = z.object({ - name: z.literal("Windmill"), - app: z.literal(AppConnection.Windmill), - methods: z.nativeEnum(WindmillConnectionMethod).array() -}); +export const WindmillConnectionListItemSchema = z + .object({ + name: z.literal("Windmill"), + app: z.literal(AppConnection.Windmill), + methods: z.nativeEnum(WindmillConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Windmill] })); diff --git a/backend/src/services/app-connection/zabbix/zabbix-connection-schemas.ts b/backend/src/services/app-connection/zabbix/zabbix-connection-schemas.ts index 23bffd859..ce69de030 100644 --- a/backend/src/services/app-connection/zabbix/zabbix-connection-schemas.ts +++ b/backend/src/services/app-connection/zabbix/zabbix-connection-schemas.ts @@ -8,6 +8,7 @@ import { GenericUpdateAppConnectionFieldsSchema } from "@app/services/app-connection/app-connection-schemas"; +import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps"; import { ZabbixConnectionMethod } from "./zabbix-connection-enums"; export const ZabbixConnectionApiTokenCredentialsSchema = z.object({ @@ -31,7 +32,7 @@ export const SanitizedZabbixConnectionSchema = z.discriminatedUnion("method", [ BaseZabbixConnectionSchema.extend({ method: z.literal(ZabbixConnectionMethod.ApiToken), credentials: ZabbixConnectionApiTokenCredentialsSchema.pick({ instanceUrl: true }) - }) + }).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.Zabbix]} (API Token)` })) ]); export const ValidateZabbixConnectionCredentialsSchema = z.discriminatedUnion("method", [ @@ -55,8 +56,10 @@ export const UpdateZabbixConnectionSchema = z }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Zabbix)); -export const ZabbixConnectionListItemSchema = z.object({ - name: z.literal("Zabbix"), - app: z.literal(AppConnection.Zabbix), - methods: z.nativeEnum(ZabbixConnectionMethod).array() -}); +export const ZabbixConnectionListItemSchema = z + .object({ + name: z.literal("Zabbix"), + app: z.literal(AppConnection.Zabbix), + methods: z.nativeEnum(ZabbixConnectionMethod).array() + }) + .describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.Zabbix] })); diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index b9c759703..e2f0f5f16 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -454,6 +454,30 @@ export const authLoginServiceFactory = ({ }); } + if (organizationId) { + await auditLogService.createAuditLog({ + orgId: organizationId, + ipAddress: ip, + userAgent, + userAgentType: getUserAgentType(userAgent), + actor: { + type: ActorType.USER, + metadata: { + email: userEnc.email, + userId: userEnc.userId, + username: userEnc.username, + authMethod + } + }, + event: { + type: EventType.USER_LOGIN, + metadata: { + organizationId + } + } + }); + } + return { tokens: { accessToken: token.access, @@ -646,6 +670,29 @@ export const authLoginServiceFactory = ({ } } + await auditLogService.createAuditLog({ + orgId: organizationId, + ipAddress, + userAgent, + userAgentType: getUserAgentType(userAgent), + actor: { + type: ActorType.USER, + metadata: { + email: user.email, + userId: user.id, + username: user.username, + authMethod: decodedToken.authMethod + } + }, + event: { + type: EventType.SELECT_ORGANIZATION, + metadata: { + organizationId, + organizationName: selectedOrg.name + } + } + }); + return { ...tokens, user, @@ -1039,6 +1086,33 @@ export const authLoginServiceFactory = ({ organizationId }); + if (organizationId) { + await auditLogService.createAuditLog({ + orgId: organizationId, + ipAddress: ip, + userAgent, + userAgentType: getUserAgentType(userAgent), + actor: { + type: ActorType.USER, + metadata: { + email: userEnc.email, + userId: userEnc.userId, + username: userEnc.username, + authMethod: decodedProviderToken.authMethod + } + }, + event: { + type: EventType.USER_LOGIN, + metadata: { + organizationId, + ...(isAuthMethodSaml(decodedProviderToken.authMethod) && { + authProvider: decodedProviderToken.authMethod + }) + } + } + }); + } + return { token, isMfaEnabled: false, user: userEnc, decodedProviderToken } as const; }; diff --git a/backend/src/services/role/role-service.ts b/backend/src/services/role/role-service.ts index 3387dc96b..653a00b5c 100644 --- a/backend/src/services/role/role-service.ts +++ b/backend/src/services/role/role-service.ts @@ -6,6 +6,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { UnpackedPermissionSchema, unpackPermissions } from "@app/server/routes/sanitizedSchema/permission"; +import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal"; import { ActorType } from "../auth/auth-type"; import { TExternalGroupOrgRoleMappingDALFactory } from "../external-group-org-role-mapping/external-group-org-role-mapping-dal"; @@ -33,6 +34,7 @@ type TRoleServiceFactoryDep = { permissionService: Pick; projectDAL: Pick; externalGroupOrgRoleMappingDAL: Pick; + membershipRoleDAL: Pick; }; export type TRoleServiceFactory = ReturnType; @@ -43,7 +45,8 @@ export const roleServiceFactory = ({ projectDAL, identityDAL, userDAL, - externalGroupOrgRoleMappingDAL + externalGroupOrgRoleMappingDAL, + membershipRoleDAL }: TRoleServiceFactoryDep) => { const orgRoleFactory = newOrgRoleFactory({ permissionService, @@ -137,6 +140,23 @@ export const roleServiceFactory = ({ }); if (!existingRole) throw new NotFoundError({ message: `Role with ${dto.selector.id} not found` }); + const [roleUsageData] = await membershipRoleDAL.find( + { + customRoleId: dto.selector.id + }, + { count: true } + ); + + if (roleUsageData) { + const count = Number.parseInt(roleUsageData.count, 10); + if (count > 0) { + const plural = count > 1 ? "s" : ""; + throw new BadRequestError({ + message: `Role is assigned to ${count} identity membership${plural}. Re-assign membership role${plural} to delete this role.` + }); + } + } + const [role] = await roleDAL.delete({ id: existingRole.id, [scope.key]: scope.value diff --git a/backend/src/services/secret-sync/1password/1password-sync-schemas.ts b/backend/src/services/secret-sync/1password/1password-sync-schemas.ts index 9ee4ca496..70aff62f4 100644 --- a/backend/src/services/secret-sync/1password/1password-sync-schemas.ts +++ b/backend/src/services/secret-sync/1password/1password-sync-schemas.ts @@ -10,6 +10,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const OnePassSyncDestinationConfigSchema = z.object({ vaultId: z.string().trim().min(1, "Vault required").describe(SecretSyncs.DESTINATION_CONFIG.ONEPASS.vaultId), valueLabel: z.string().trim().optional().describe(SecretSyncs.DESTINATION_CONFIG.ONEPASS.valueLabel) @@ -17,10 +19,12 @@ const OnePassSyncDestinationConfigSchema = z.object({ const OnePassSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const OnePassSyncSchema = BaseSecretSyncSchema(SecretSync.OnePass, OnePassSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.OnePass), - destinationConfig: OnePassSyncDestinationConfigSchema -}); +export const OnePassSyncSchema = BaseSecretSyncSchema(SecretSync.OnePass, OnePassSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.OnePass), + destinationConfig: OnePassSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.OnePass] })); export const CreateOnePassSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.OnePass, @@ -36,9 +40,11 @@ export const UpdateOnePassSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: OnePassSyncDestinationConfigSchema.optional() }); -export const OnePassSyncListItemSchema = z.object({ - name: z.literal("1Password"), - connection: z.literal(AppConnection.OnePass), - destination: z.literal(SecretSync.OnePass), - canImportSecrets: z.literal(true) -}); +export const OnePassSyncListItemSchema = z + .object({ + name: z.literal("1Password"), + connection: z.literal(AppConnection.OnePass), + destination: z.literal(SecretSync.OnePass), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.OnePass] })); diff --git a/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-schemas.ts b/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-schemas.ts index 324b78130..587ac13e8 100644 --- a/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-schemas.ts +++ b/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-schemas.ts @@ -11,6 +11,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const tagFieldCharacterValidator = characterValidator([ CharacterType.AlphaNumeric, CharacterType.Spaces, @@ -105,10 +107,12 @@ export const AwsParameterStoreSyncSchema = BaseSecretSyncSchema( SecretSync.AWSParameterStore, AwsParameterStoreSyncOptionsConfig, AwsParameterStoreSyncOptionsSchema -).extend({ - destination: z.literal(SecretSync.AWSParameterStore), - destinationConfig: AwsParameterStoreSyncDestinationConfigSchema -}); +) + .extend({ + destination: z.literal(SecretSync.AWSParameterStore), + destinationConfig: AwsParameterStoreSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AWSParameterStore] })); export const CreateAwsParameterStoreSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.AWSParameterStore, @@ -126,9 +130,11 @@ export const UpdateAwsParameterStoreSyncSchema = GenericUpdateSecretSyncFieldsSc destinationConfig: AwsParameterStoreSyncDestinationConfigSchema.optional() }); -export const AwsParameterStoreSyncListItemSchema = z.object({ - name: z.literal("AWS Parameter Store"), - connection: z.literal(AppConnection.AWS), - destination: z.literal(SecretSync.AWSParameterStore), - canImportSecrets: z.literal(true) -}); +export const AwsParameterStoreSyncListItemSchema = z + .object({ + name: z.literal("AWS Parameter Store"), + connection: z.literal(AppConnection.AWS), + destination: z.literal(SecretSync.AWSParameterStore), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AWSParameterStore] })); diff --git a/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-schemas.ts b/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-schemas.ts index e80964721..44984e1b3 100644 --- a/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-schemas.ts +++ b/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-schemas.ts @@ -12,6 +12,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const AwsSecretsManagerSyncDestinationConfigSchema = z .discriminatedUnion("mappingBehavior", [ z.object({ @@ -119,10 +121,12 @@ export const AwsSecretsManagerSyncSchema = BaseSecretSyncSchema( SecretSync.AWSSecretsManager, AwsSecretsManagerSyncOptionsConfig, AwsSecretsManagerSyncOptionsSchema -).extend({ - destination: z.literal(SecretSync.AWSSecretsManager), - destinationConfig: AwsSecretsManagerSyncDestinationConfigSchema -}); +) + .extend({ + destination: z.literal(SecretSync.AWSSecretsManager), + destinationConfig: AwsSecretsManagerSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AWSSecretsManager] })); export const CreateAwsSecretsManagerSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.AWSSecretsManager, @@ -164,9 +168,11 @@ export const UpdateAwsSecretsManagerSyncSchema = GenericUpdateSecretSyncFieldsSc } }); -export const AwsSecretsManagerSyncListItemSchema = z.object({ - name: z.literal("AWS Secrets Manager"), - connection: z.literal(AppConnection.AWS), - destination: z.literal(SecretSync.AWSSecretsManager), - canImportSecrets: z.literal(true) -}); +export const AwsSecretsManagerSyncListItemSchema = z + .object({ + name: z.literal("AWS Secrets Manager"), + connection: z.literal(AppConnection.AWS), + destination: z.literal(SecretSync.AWSSecretsManager), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AWSSecretsManager] })); diff --git a/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-schemas.ts b/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-schemas.ts index c39581fda..b6f2a77d6 100644 --- a/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-schemas.ts +++ b/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-schemas.ts @@ -10,6 +10,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const AzureAppConfigurationSyncDestinationConfigSchema = z.object({ configurationUrl: z .string() @@ -23,10 +25,12 @@ const AzureAppConfigurationSyncOptionsConfig: TSyncOptionsConfig = { canImportSe export const AzureAppConfigurationSyncSchema = BaseSecretSyncSchema( SecretSync.AzureAppConfiguration, AzureAppConfigurationSyncOptionsConfig -).extend({ - destination: z.literal(SecretSync.AzureAppConfiguration), - destinationConfig: AzureAppConfigurationSyncDestinationConfigSchema -}); +) + .extend({ + destination: z.literal(SecretSync.AzureAppConfiguration), + destinationConfig: AzureAppConfigurationSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AzureAppConfiguration] })); export const CreateAzureAppConfigurationSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.AzureAppConfiguration, @@ -42,9 +46,11 @@ export const UpdateAzureAppConfigurationSyncSchema = GenericUpdateSecretSyncFiel destinationConfig: AzureAppConfigurationSyncDestinationConfigSchema.optional() }); -export const AzureAppConfigurationSyncListItemSchema = z.object({ - name: z.literal("Azure App Configuration"), - connection: z.literal(AppConnection.AzureAppConfiguration), - destination: z.literal(SecretSync.AzureAppConfiguration), - canImportSecrets: z.literal(true) -}); +export const AzureAppConfigurationSyncListItemSchema = z + .object({ + name: z.literal("Azure App Configuration"), + connection: z.literal(AppConnection.AzureAppConfiguration), + destination: z.literal(SecretSync.AzureAppConfiguration), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AzureAppConfiguration] })); diff --git a/backend/src/services/secret-sync/azure-devops/azure-devops-sync-schemas.ts b/backend/src/services/secret-sync/azure-devops/azure-devops-sync-schemas.ts index 69bc22447..2dc178b2b 100644 --- a/backend/src/services/secret-sync/azure-devops/azure-devops-sync-schemas.ts +++ b/backend/src/services/secret-sync/azure-devops/azure-devops-sync-schemas.ts @@ -10,6 +10,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + export const AzureDevOpsSyncDestinationConfigSchema = z.object({ devopsProjectId: z .string() @@ -23,10 +25,12 @@ export const AzureDevOpsSyncDestinationConfigSchema = z.object({ const AzureDevOpsSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false }; -export const AzureDevOpsSyncSchema = BaseSecretSyncSchema(SecretSync.AzureDevOps, AzureDevOpsSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.AzureDevOps), - destinationConfig: AzureDevOpsSyncDestinationConfigSchema -}); +export const AzureDevOpsSyncSchema = BaseSecretSyncSchema(SecretSync.AzureDevOps, AzureDevOpsSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.AzureDevOps), + destinationConfig: AzureDevOpsSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AzureDevOps] })); export const CreateAzureDevOpsSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.AzureDevOps, @@ -42,9 +46,11 @@ export const UpdateAzureDevOpsSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: AzureDevOpsSyncDestinationConfigSchema.optional() }); -export const AzureDevOpsSyncListItemSchema = z.object({ - name: z.literal("Azure DevOps"), - connection: z.literal(AppConnection.AzureDevOps), - destination: z.literal(SecretSync.AzureDevOps), - canImportSecrets: z.literal(false) -}); +export const AzureDevOpsSyncListItemSchema = z + .object({ + name: z.literal("Azure DevOps"), + connection: z.literal(AppConnection.AzureDevOps), + destination: z.literal(SecretSync.AzureDevOps), + canImportSecrets: z.literal(false) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AzureDevOps] })); diff --git a/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-schemas.ts b/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-schemas.ts index d528f531e..daf936a75 100644 --- a/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-schemas.ts +++ b/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-schemas.ts @@ -10,6 +10,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const AzureKeyVaultSyncDestinationConfigSchema = z.object({ vaultBaseUrl: z .string() @@ -20,13 +22,12 @@ const AzureKeyVaultSyncDestinationConfigSchema = z.object({ const AzureKeyVaultSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const AzureKeyVaultSyncSchema = BaseSecretSyncSchema( - SecretSync.AzureKeyVault, - AzureKeyVaultSyncOptionsConfig -).extend({ - destination: z.literal(SecretSync.AzureKeyVault), - destinationConfig: AzureKeyVaultSyncDestinationConfigSchema -}); +export const AzureKeyVaultSyncSchema = BaseSecretSyncSchema(SecretSync.AzureKeyVault, AzureKeyVaultSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.AzureKeyVault), + destinationConfig: AzureKeyVaultSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AzureKeyVault] })); export const CreateAzureKeyVaultSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.AzureKeyVault, @@ -42,9 +43,11 @@ export const UpdateAzureKeyVaultSyncSchema = GenericUpdateSecretSyncFieldsSchema destinationConfig: AzureKeyVaultSyncDestinationConfigSchema.optional() }); -export const AzureKeyVaultSyncListItemSchema = z.object({ - name: z.literal("Azure Key Vault"), - connection: z.literal(AppConnection.AzureKeyVault), - destination: z.literal(SecretSync.AzureKeyVault), - canImportSecrets: z.literal(true) -}); +export const AzureKeyVaultSyncListItemSchema = z + .object({ + name: z.literal("Azure Key Vault"), + connection: z.literal(AppConnection.AzureKeyVault), + destination: z.literal(SecretSync.AzureKeyVault), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.AzureKeyVault] })); diff --git a/backend/src/services/secret-sync/bitbucket/bitbucket-sync-schemas.ts b/backend/src/services/secret-sync/bitbucket/bitbucket-sync-schemas.ts index 985d86e8d..bfd2ac9bc 100644 --- a/backend/src/services/secret-sync/bitbucket/bitbucket-sync-schemas.ts +++ b/backend/src/services/secret-sync/bitbucket/bitbucket-sync-schemas.ts @@ -10,6 +10,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const BitbucketSyncDestinationConfigSchema = z.object({ repositorySlug: z.string().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.repositorySlug), environmentId: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.environmentId), @@ -18,10 +20,12 @@ const BitbucketSyncDestinationConfigSchema = z.object({ const BitbucketSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false }; -export const BitbucketSyncSchema = BaseSecretSyncSchema(SecretSync.Bitbucket, BitbucketSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Bitbucket), - destinationConfig: BitbucketSyncDestinationConfigSchema -}); +export const BitbucketSyncSchema = BaseSecretSyncSchema(SecretSync.Bitbucket, BitbucketSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Bitbucket), + destinationConfig: BitbucketSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Bitbucket] })); export const CreateBitbucketSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Bitbucket, @@ -37,9 +41,11 @@ export const UpdateBitbucketSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: BitbucketSyncDestinationConfigSchema.optional() }); -export const BitbucketSyncListItemSchema = z.object({ - name: z.literal("Bitbucket"), - connection: z.literal(AppConnection.Bitbucket), - destination: z.literal(SecretSync.Bitbucket), - canImportSecrets: z.literal(false) -}); +export const BitbucketSyncListItemSchema = z + .object({ + name: z.literal("Bitbucket"), + connection: z.literal(AppConnection.Bitbucket), + destination: z.literal(SecretSync.Bitbucket), + canImportSecrets: z.literal(false) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Bitbucket] })); diff --git a/backend/src/services/secret-sync/camunda/camunda-sync-schemas.ts b/backend/src/services/secret-sync/camunda/camunda-sync-schemas.ts index 726b5dfac..468563c89 100644 --- a/backend/src/services/secret-sync/camunda/camunda-sync-schemas.ts +++ b/backend/src/services/secret-sync/camunda/camunda-sync-schemas.ts @@ -10,6 +10,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const CamundaSyncDestinationConfigSchema = z.object({ scope: z.string().trim().min(1, "Camunda scope required").describe(SecretSyncs.DESTINATION_CONFIG.CAMUNDA.scope), clusterUUID: z @@ -20,10 +22,12 @@ const CamundaSyncDestinationConfigSchema = z.object({ const CamundaSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const CamundaSyncSchema = BaseSecretSyncSchema(SecretSync.Camunda, CamundaSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Camunda), - destinationConfig: CamundaSyncDestinationConfigSchema -}); +export const CamundaSyncSchema = BaseSecretSyncSchema(SecretSync.Camunda, CamundaSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Camunda), + destinationConfig: CamundaSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Camunda] })); export const CreateCamundaSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Camunda, @@ -39,9 +43,11 @@ export const UpdateCamundaSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: CamundaSyncDestinationConfigSchema.optional() }); -export const CamundaSyncListItemSchema = z.object({ - name: z.literal("Camunda"), - connection: z.literal(AppConnection.Camunda), - destination: z.literal(SecretSync.Camunda), - canImportSecrets: z.literal(true) -}); +export const CamundaSyncListItemSchema = z + .object({ + name: z.literal("Camunda"), + connection: z.literal(AppConnection.Camunda), + destination: z.literal(SecretSync.Camunda), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Camunda] })); diff --git a/backend/src/services/secret-sync/checkly/checkly-sync-schemas.ts b/backend/src/services/secret-sync/checkly/checkly-sync-schemas.ts index fc511b2d7..276e3702f 100644 --- a/backend/src/services/secret-sync/checkly/checkly-sync-schemas.ts +++ b/backend/src/services/secret-sync/checkly/checkly-sync-schemas.ts @@ -9,6 +9,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const ChecklySyncDestinationConfigSchema = z.object({ accountId: z.string().min(1, "Account ID is required").max(255, "Account ID must be less than 255 characters"), accountName: z @@ -26,10 +28,12 @@ const ChecklySyncDestinationConfigSchema = z.object({ const ChecklySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false }; -export const ChecklySyncSchema = BaseSecretSyncSchema(SecretSync.Checkly, ChecklySyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Checkly), - destinationConfig: ChecklySyncDestinationConfigSchema -}); +export const ChecklySyncSchema = BaseSecretSyncSchema(SecretSync.Checkly, ChecklySyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Checkly), + destinationConfig: ChecklySyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Checkly] })); export const CreateChecklySyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Checkly, @@ -45,9 +49,11 @@ export const UpdateChecklySyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: ChecklySyncDestinationConfigSchema.optional() }); -export const ChecklySyncListItemSchema = z.object({ - name: z.literal("Checkly"), - connection: z.literal(AppConnection.Checkly), - destination: z.literal(SecretSync.Checkly), - canImportSecrets: z.literal(false) -}); +export const ChecklySyncListItemSchema = z + .object({ + name: z.literal("Checkly"), + connection: z.literal(AppConnection.Checkly), + destination: z.literal(SecretSync.Checkly), + canImportSecrets: z.literal(false) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Checkly] })); diff --git a/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-schema.ts b/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-schema.ts index f0d814fb6..66fc2ca82 100644 --- a/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-schema.ts +++ b/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-schema.ts @@ -10,6 +10,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const CloudflarePagesSyncDestinationConfigSchema = z.object({ projectName: z .string() @@ -26,10 +28,12 @@ const CloudflarePagesSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: export const CloudflarePagesSyncSchema = BaseSecretSyncSchema( SecretSync.CloudflarePages, CloudflarePagesSyncOptionsConfig -).extend({ - destination: z.literal(SecretSync.CloudflarePages), - destinationConfig: CloudflarePagesSyncDestinationConfigSchema -}); +) + .extend({ + destination: z.literal(SecretSync.CloudflarePages), + destinationConfig: CloudflarePagesSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.CloudflarePages] })); export const CreateCloudflarePagesSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.CloudflarePages, @@ -45,9 +49,11 @@ export const UpdateCloudflarePagesSyncSchema = GenericUpdateSecretSyncFieldsSche destinationConfig: CloudflarePagesSyncDestinationConfigSchema.optional() }); -export const CloudflarePagesSyncListItemSchema = z.object({ - name: z.literal("Cloudflare Pages"), - connection: z.literal(AppConnection.Cloudflare), - destination: z.literal(SecretSync.CloudflarePages), - canImportSecrets: z.literal(false) -}); +export const CloudflarePagesSyncListItemSchema = z + .object({ + name: z.literal("Cloudflare Pages"), + connection: z.literal(AppConnection.Cloudflare), + destination: z.literal(SecretSync.CloudflarePages), + canImportSecrets: z.literal(false) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.CloudflarePages] })); diff --git a/backend/src/services/secret-sync/cloudflare-workers/cloudflare-workers-schemas.ts b/backend/src/services/secret-sync/cloudflare-workers/cloudflare-workers-schemas.ts index b5698867a..414014322 100644 --- a/backend/src/services/secret-sync/cloudflare-workers/cloudflare-workers-schemas.ts +++ b/backend/src/services/secret-sync/cloudflare-workers/cloudflare-workers-schemas.ts @@ -11,6 +11,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const CloudflareWorkersSyncDestinationConfigSchema = z.object({ scriptId: z .string() @@ -28,10 +30,12 @@ const CloudflareWorkersSyncOptionsConfig: TSyncOptionsConfig = { canImportSecret export const CloudflareWorkersSyncSchema = BaseSecretSyncSchema( SecretSync.CloudflareWorkers, CloudflareWorkersSyncOptionsConfig -).extend({ - destination: z.literal(SecretSync.CloudflareWorkers), - destinationConfig: CloudflareWorkersSyncDestinationConfigSchema -}); +) + .extend({ + destination: z.literal(SecretSync.CloudflareWorkers), + destinationConfig: CloudflareWorkersSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.CloudflareWorkers] })); export const CreateCloudflareWorkersSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.CloudflareWorkers, @@ -47,9 +51,11 @@ export const UpdateCloudflareWorkersSyncSchema = GenericUpdateSecretSyncFieldsSc destinationConfig: CloudflareWorkersSyncDestinationConfigSchema.optional() }); -export const CloudflareWorkersSyncListItemSchema = z.object({ - name: z.literal("Cloudflare Workers"), - connection: z.literal(AppConnection.Cloudflare), - destination: z.literal(SecretSync.CloudflareWorkers), - canImportSecrets: z.literal(false) -}); +export const CloudflareWorkersSyncListItemSchema = z + .object({ + name: z.literal("Cloudflare Workers"), + connection: z.literal(AppConnection.Cloudflare), + destination: z.literal(SecretSync.CloudflareWorkers), + canImportSecrets: z.literal(false) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.CloudflareWorkers] })); diff --git a/backend/src/services/secret-sync/databricks/databricks-sync-schemas.ts b/backend/src/services/secret-sync/databricks/databricks-sync-schemas.ts index c0f148244..934334eee 100644 --- a/backend/src/services/secret-sync/databricks/databricks-sync-schemas.ts +++ b/backend/src/services/secret-sync/databricks/databricks-sync-schemas.ts @@ -10,16 +10,20 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const DatabricksSyncDestinationConfigSchema = z.object({ scope: z.string().trim().min(1, "Databricks scope required").describe(SecretSyncs.DESTINATION_CONFIG.DATABRICKS.scope) }); const DatabricksSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false }; -export const DatabricksSyncSchema = BaseSecretSyncSchema(SecretSync.Databricks, DatabricksSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Databricks), - destinationConfig: DatabricksSyncDestinationConfigSchema -}); +export const DatabricksSyncSchema = BaseSecretSyncSchema(SecretSync.Databricks, DatabricksSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Databricks), + destinationConfig: DatabricksSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Databricks] })); export const CreateDatabricksSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Databricks, @@ -35,9 +39,11 @@ export const UpdateDatabricksSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: DatabricksSyncDestinationConfigSchema.optional() }); -export const DatabricksSyncListItemSchema = z.object({ - name: z.literal("Databricks"), - connection: z.literal(AppConnection.Databricks), - destination: z.literal(SecretSync.Databricks), - canImportSecrets: z.literal(false) -}); +export const DatabricksSyncListItemSchema = z + .object({ + name: z.literal("Databricks"), + connection: z.literal(AppConnection.Databricks), + destination: z.literal(SecretSync.Databricks), + canImportSecrets: z.literal(false) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Databricks] })); diff --git a/backend/src/services/secret-sync/digital-ocean-app-platform/digital-ocean-app-platform-sync-schemas.ts b/backend/src/services/secret-sync/digital-ocean-app-platform/digital-ocean-app-platform-sync-schemas.ts index 09b943d16..26014f83b 100644 --- a/backend/src/services/secret-sync/digital-ocean-app-platform/digital-ocean-app-platform-sync-schemas.ts +++ b/backend/src/services/secret-sync/digital-ocean-app-platform/digital-ocean-app-platform-sync-schemas.ts @@ -9,6 +9,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const DigitalOceanAppPlatformSyncDestinationConfigSchema = z.object({ appId: z.string().min(1, "Account ID is required").max(255, "Account ID must be less than 255 characters"), appName: z.string().min(1, "Account Name is required").max(255, "Account Name must be less than 255 characters") @@ -19,10 +21,12 @@ const DigitalOceanAppPlatformSyncOptionsConfig: TSyncOptionsConfig = { canImport export const DigitalOceanAppPlatformSyncSchema = BaseSecretSyncSchema( SecretSync.DigitalOceanAppPlatform, DigitalOceanAppPlatformSyncOptionsConfig -).extend({ - destination: z.literal(SecretSync.DigitalOceanAppPlatform), - destinationConfig: DigitalOceanAppPlatformSyncDestinationConfigSchema -}); +) + .extend({ + destination: z.literal(SecretSync.DigitalOceanAppPlatform), + destinationConfig: DigitalOceanAppPlatformSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.DigitalOceanAppPlatform] })); export const CreateDigitalOceanAppPlatformSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.DigitalOceanAppPlatform, @@ -38,9 +42,11 @@ export const UpdateDigitalOceanAppPlatformSyncSchema = GenericUpdateSecretSyncFi destinationConfig: DigitalOceanAppPlatformSyncDestinationConfigSchema.optional() }); -export const DigitalOceanAppPlatformSyncListItemSchema = z.object({ - name: z.literal("Digital Ocean App Platform"), - connection: z.literal(AppConnection.DigitalOcean), - destination: z.literal(SecretSync.DigitalOceanAppPlatform), - canImportSecrets: z.literal(false) -}); +export const DigitalOceanAppPlatformSyncListItemSchema = z + .object({ + name: z.literal("Digital Ocean App Platform"), + connection: z.literal(AppConnection.DigitalOcean), + destination: z.literal(SecretSync.DigitalOceanAppPlatform), + canImportSecrets: z.literal(false) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.DigitalOceanAppPlatform] })); diff --git a/backend/src/services/secret-sync/flyio/flyio-sync-schemas.ts b/backend/src/services/secret-sync/flyio/flyio-sync-schemas.ts index b353f94b4..4d74269d7 100644 --- a/backend/src/services/secret-sync/flyio/flyio-sync-schemas.ts +++ b/backend/src/services/secret-sync/flyio/flyio-sync-schemas.ts @@ -10,16 +10,20 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const FlyioSyncDestinationConfigSchema = z.object({ appId: z.string().trim().min(1, "App required").max(255).describe(SecretSyncs.DESTINATION_CONFIG.FLYIO.appId) }); const FlyioSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false }; -export const FlyioSyncSchema = BaseSecretSyncSchema(SecretSync.Flyio, FlyioSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Flyio), - destinationConfig: FlyioSyncDestinationConfigSchema -}); +export const FlyioSyncSchema = BaseSecretSyncSchema(SecretSync.Flyio, FlyioSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Flyio), + destinationConfig: FlyioSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Flyio] })); export const CreateFlyioSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Flyio, @@ -35,9 +39,11 @@ export const UpdateFlyioSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: FlyioSyncDestinationConfigSchema.optional() }); -export const FlyioSyncListItemSchema = z.object({ - name: z.literal("Fly.io"), - connection: z.literal(AppConnection.Flyio), - destination: z.literal(SecretSync.Flyio), - canImportSecrets: z.literal(false) -}); +export const FlyioSyncListItemSchema = z + .object({ + name: z.literal("Fly.io"), + connection: z.literal(AppConnection.Flyio), + destination: z.literal(SecretSync.Flyio), + canImportSecrets: z.literal(false) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Flyio] })); diff --git a/backend/src/services/secret-sync/gcp/gcp-sync-schemas.ts b/backend/src/services/secret-sync/gcp/gcp-sync-schemas.ts index 875ceaf70..eeef3e4a7 100644 --- a/backend/src/services/secret-sync/gcp/gcp-sync-schemas.ts +++ b/backend/src/services/secret-sync/gcp/gcp-sync-schemas.ts @@ -10,6 +10,7 @@ import { import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; import { SecretSync } from "../secret-sync-enums"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; import { GCPSecretManagerLocation, GcpSyncScope } from "./gcp-sync-enums"; const GcpSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; @@ -38,10 +39,12 @@ const GcpSyncDestinationConfigSchema = z.discriminatedUnion("scope", [ ) ]); -export const GcpSyncSchema = BaseSecretSyncSchema(SecretSync.GCPSecretManager, GcpSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.GCPSecretManager), - destinationConfig: GcpSyncDestinationConfigSchema -}); +export const GcpSyncSchema = BaseSecretSyncSchema(SecretSync.GCPSecretManager, GcpSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.GCPSecretManager), + destinationConfig: GcpSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.GCPSecretManager] })); export const CreateGcpSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.GCPSecretManager, @@ -57,9 +60,11 @@ export const UpdateGcpSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: GcpSyncDestinationConfigSchema.optional() }); -export const GcpSyncListItemSchema = z.object({ - name: z.literal("GCP Secret Manager"), - connection: z.literal(AppConnection.GCP), - destination: z.literal(SecretSync.GCPSecretManager), - canImportSecrets: z.literal(true) -}); +export const GcpSyncListItemSchema = z + .object({ + name: z.literal("GCP Secret Manager"), + connection: z.literal(AppConnection.GCP), + destination: z.literal(SecretSync.GCPSecretManager), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.GCPSecretManager] })); diff --git a/backend/src/services/secret-sync/github/github-sync-schemas.ts b/backend/src/services/secret-sync/github/github-sync-schemas.ts index 76bbc63a7..79f7b18f1 100644 --- a/backend/src/services/secret-sync/github/github-sync-schemas.ts +++ b/backend/src/services/secret-sync/github/github-sync-schemas.ts @@ -11,6 +11,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const GitHubSyncDestinationConfigSchema = z .discriminatedUnion("scope", [ z.object({ @@ -55,10 +57,12 @@ const GitHubSyncDestinationConfigSchema = z const GitHubSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false }; -export const GitHubSyncSchema = BaseSecretSyncSchema(SecretSync.GitHub, GitHubSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.GitHub), - destinationConfig: GitHubSyncDestinationConfigSchema -}); +export const GitHubSyncSchema = BaseSecretSyncSchema(SecretSync.GitHub, GitHubSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.GitHub), + destinationConfig: GitHubSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.GitHub] })); export const CreateGitHubSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.GitHub, @@ -74,9 +78,11 @@ export const UpdateGitHubSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: GitHubSyncDestinationConfigSchema.optional() }); -export const GitHubSyncListItemSchema = z.object({ - name: z.literal("GitHub"), - connection: z.literal(AppConnection.GitHub), - destination: z.literal(SecretSync.GitHub), - canImportSecrets: z.literal(false) -}); +export const GitHubSyncListItemSchema = z + .object({ + name: z.literal("GitHub"), + connection: z.literal(AppConnection.GitHub), + destination: z.literal(SecretSync.GitHub), + canImportSecrets: z.literal(false) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.GitHub] })); diff --git a/backend/src/services/secret-sync/gitlab/gitlab-sync-schemas.ts b/backend/src/services/secret-sync/gitlab/gitlab-sync-schemas.ts index 8797cc98d..d96d945d8 100644 --- a/backend/src/services/secret-sync/gitlab/gitlab-sync-schemas.ts +++ b/backend/src/services/secret-sync/gitlab/gitlab-sync-schemas.ts @@ -10,6 +10,7 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; import { GitLabSyncScope } from "./gitlab-sync-enums"; const GitLabSyncDestinationConfigSchema = z.discriminatedUnion("scope", [ @@ -70,10 +71,12 @@ const GitLabSyncDestinationConfigSchema = z.discriminatedUnion("scope", [ const GitLabSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false }; -export const GitLabSyncSchema = BaseSecretSyncSchema(SecretSync.GitLab, GitLabSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.GitLab), - destinationConfig: GitLabSyncDestinationConfigSchema -}); +export const GitLabSyncSchema = BaseSecretSyncSchema(SecretSync.GitLab, GitLabSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.GitLab), + destinationConfig: GitLabSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.GitLab] })); export const CreateGitLabSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.GitLab, @@ -89,9 +92,11 @@ export const UpdateGitLabSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: GitLabSyncDestinationConfigSchema.optional() }); -export const GitLabSyncListItemSchema = z.object({ - name: z.literal("GitLab"), - connection: z.literal(AppConnection.GitLab), - destination: z.literal(SecretSync.GitLab), - canImportSecrets: z.literal(false) -}); +export const GitLabSyncListItemSchema = z + .object({ + name: z.literal("GitLab"), + connection: z.literal(AppConnection.GitLab), + destination: z.literal(SecretSync.GitLab), + canImportSecrets: z.literal(false) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.GitLab] })); diff --git a/backend/src/services/secret-sync/hc-vault/hc-vault-sync-schemas.ts b/backend/src/services/secret-sync/hc-vault/hc-vault-sync-schemas.ts index f9096ac71..43c517432 100644 --- a/backend/src/services/secret-sync/hc-vault/hc-vault-sync-schemas.ts +++ b/backend/src/services/secret-sync/hc-vault/hc-vault-sync-schemas.ts @@ -11,6 +11,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const HCVaultSyncDestinationConfigSchema = z.object({ mount: z .string() @@ -33,10 +35,12 @@ const HCVaultSyncDestinationConfigSchema = z.object({ const HCVaultSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const HCVaultSyncSchema = BaseSecretSyncSchema(SecretSync.HCVault, HCVaultSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.HCVault), - destinationConfig: HCVaultSyncDestinationConfigSchema -}); +export const HCVaultSyncSchema = BaseSecretSyncSchema(SecretSync.HCVault, HCVaultSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.HCVault), + destinationConfig: HCVaultSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.HCVault] })); export const CreateHCVaultSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.HCVault, @@ -52,9 +56,11 @@ export const UpdateHCVaultSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: HCVaultSyncDestinationConfigSchema.optional() }); -export const HCVaultSyncListItemSchema = z.object({ - name: z.literal("Hashicorp Vault"), - connection: z.literal(AppConnection.HCVault), - destination: z.literal(SecretSync.HCVault), - canImportSecrets: z.literal(true) -}); +export const HCVaultSyncListItemSchema = z + .object({ + name: z.literal("Hashicorp Vault"), + connection: z.literal(AppConnection.HCVault), + destination: z.literal(SecretSync.HCVault), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.HCVault] })); diff --git a/backend/src/services/secret-sync/heroku/heroku-sync-schemas.ts b/backend/src/services/secret-sync/heroku/heroku-sync-schemas.ts index 5c9ba570e..fce757389 100644 --- a/backend/src/services/secret-sync/heroku/heroku-sync-schemas.ts +++ b/backend/src/services/secret-sync/heroku/heroku-sync-schemas.ts @@ -10,6 +10,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const HerokuSyncDestinationConfigSchema = z.object({ app: z.string().trim().min(1, "App required").describe(SecretSyncs.DESTINATION_CONFIG.HEROKU.app), appName: z.string().trim().min(1, "App name required").describe(SecretSyncs.DESTINATION_CONFIG.HEROKU.appName) @@ -17,10 +19,12 @@ const HerokuSyncDestinationConfigSchema = z.object({ const HerokuSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const HerokuSyncSchema = BaseSecretSyncSchema(SecretSync.Heroku, HerokuSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Heroku), - destinationConfig: HerokuSyncDestinationConfigSchema -}); +export const HerokuSyncSchema = BaseSecretSyncSchema(SecretSync.Heroku, HerokuSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Heroku), + destinationConfig: HerokuSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Heroku] })); export const CreateHerokuSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Heroku, @@ -36,9 +40,11 @@ export const UpdateHerokuSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: HerokuSyncDestinationConfigSchema.optional() }); -export const HerokuSyncListItemSchema = z.object({ - name: z.literal("Heroku"), - connection: z.literal(AppConnection.Heroku), - destination: z.literal(SecretSync.Heroku), - canImportSecrets: z.literal(true) -}); +export const HerokuSyncListItemSchema = z + .object({ + name: z.literal("Heroku"), + connection: z.literal(AppConnection.Heroku), + destination: z.literal(SecretSync.Heroku), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Heroku] })); diff --git a/backend/src/services/secret-sync/humanitec/humanitec-sync-schemas.ts b/backend/src/services/secret-sync/humanitec/humanitec-sync-schemas.ts index cd90ecfdc..7fcf5e2bc 100644 --- a/backend/src/services/secret-sync/humanitec/humanitec-sync-schemas.ts +++ b/backend/src/services/secret-sync/humanitec/humanitec-sync-schemas.ts @@ -11,6 +11,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const HumanitecSyncDestinationConfigSchema = z.discriminatedUnion("scope", [ z.object({ scope: z.literal(HumanitecSyncScope.Application).describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.scope), @@ -27,10 +29,12 @@ const HumanitecSyncDestinationConfigSchema = z.discriminatedUnion("scope", [ const HumanitecSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false }; -export const HumanitecSyncSchema = BaseSecretSyncSchema(SecretSync.Humanitec, HumanitecSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Humanitec), - destinationConfig: HumanitecSyncDestinationConfigSchema -}); +export const HumanitecSyncSchema = BaseSecretSyncSchema(SecretSync.Humanitec, HumanitecSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Humanitec), + destinationConfig: HumanitecSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Humanitec] })); export const CreateHumanitecSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Humanitec, @@ -46,9 +50,11 @@ export const UpdateHumanitecSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: HumanitecSyncDestinationConfigSchema.optional() }); -export const HumanitecSyncListItemSchema = z.object({ - name: z.literal("Humanitec"), - connection: z.literal(AppConnection.Humanitec), - destination: z.literal(SecretSync.Humanitec), - canImportSecrets: z.literal(false) -}); +export const HumanitecSyncListItemSchema = z + .object({ + name: z.literal("Humanitec"), + connection: z.literal(AppConnection.Humanitec), + destination: z.literal(SecretSync.Humanitec), + canImportSecrets: z.literal(false) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Humanitec] })); diff --git a/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-schemas.ts b/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-schemas.ts index 168ebde3b..207b7b8cd 100644 --- a/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-schemas.ts +++ b/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-schemas.ts @@ -11,6 +11,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const slugValidator = (val: string) => { return new RE2("^[a-z0-9.-]+$").test(val) && !new RE2(".[-]$").test(val); }; @@ -38,13 +40,12 @@ const LaravelForgeSyncDestinationConfigSchema = z.object({ const LaravelForgeSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const LaravelForgeSyncSchema = BaseSecretSyncSchema( - SecretSync.LaravelForge, - LaravelForgeSyncOptionsConfig -).extend({ - destination: z.literal(SecretSync.LaravelForge), - destinationConfig: LaravelForgeSyncDestinationConfigSchema -}); +export const LaravelForgeSyncSchema = BaseSecretSyncSchema(SecretSync.LaravelForge, LaravelForgeSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.LaravelForge), + destinationConfig: LaravelForgeSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.LaravelForge] })); export const CreateLaravelForgeSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.LaravelForge, @@ -60,9 +61,11 @@ export const UpdateLaravelForgeSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: LaravelForgeSyncDestinationConfigSchema.optional() }); -export const LaravelForgeSyncListItemSchema = z.object({ - name: z.literal("Laravel Forge"), - connection: z.literal(AppConnection.LaravelForge), - destination: z.literal(SecretSync.LaravelForge), - canImportSecrets: z.literal(true) -}); +export const LaravelForgeSyncListItemSchema = z + .object({ + name: z.literal("Laravel Forge"), + connection: z.literal(AppConnection.LaravelForge), + destination: z.literal(SecretSync.LaravelForge), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.LaravelForge] })); diff --git a/backend/src/services/secret-sync/netlify/netlify-sync-schemas.ts b/backend/src/services/secret-sync/netlify/netlify-sync-schemas.ts index cd9a985c9..7a40710ff 100644 --- a/backend/src/services/secret-sync/netlify/netlify-sync-schemas.ts +++ b/backend/src/services/secret-sync/netlify/netlify-sync-schemas.ts @@ -10,6 +10,7 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; import { NetlifySyncContext } from "./netlify-sync-constants"; const NetlifySyncDestinationConfigSchema = z.object({ @@ -41,10 +42,12 @@ const NetlifySyncDestinationConfigSchema = z.object({ const NetlifySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const NetlifySyncSchema = BaseSecretSyncSchema(SecretSync.Netlify, NetlifySyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Netlify), - destinationConfig: NetlifySyncDestinationConfigSchema -}); +export const NetlifySyncSchema = BaseSecretSyncSchema(SecretSync.Netlify, NetlifySyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Netlify), + destinationConfig: NetlifySyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Netlify] })); export const CreateNetlifySyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Netlify, @@ -60,9 +63,11 @@ export const UpdateNetlifySyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: NetlifySyncDestinationConfigSchema.optional() }); -export const NetlifySyncListItemSchema = z.object({ - name: z.literal("Netlify"), - connection: z.literal(AppConnection.Netlify), - destination: z.literal(SecretSync.Netlify), - canImportSecrets: z.literal(true) -}); +export const NetlifySyncListItemSchema = z + .object({ + name: z.literal("Netlify"), + connection: z.literal(AppConnection.Netlify), + destination: z.literal(SecretSync.Netlify), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Netlify] })); diff --git a/backend/src/services/secret-sync/northflank/northflank-sync-schemas.ts b/backend/src/services/secret-sync/northflank/northflank-sync-schemas.ts index 55cdeae23..97c6e4c45 100644 --- a/backend/src/services/secret-sync/northflank/northflank-sync-schemas.ts +++ b/backend/src/services/secret-sync/northflank/northflank-sync-schemas.ts @@ -10,6 +10,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const NorthflankSyncDestinationConfigSchema = z.object({ projectId: z .string() @@ -27,10 +29,12 @@ const NorthflankSyncDestinationConfigSchema = z.object({ const NorthflankSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const NorthflankSyncSchema = BaseSecretSyncSchema(SecretSync.Northflank, NorthflankSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Northflank), - destinationConfig: NorthflankSyncDestinationConfigSchema -}); +export const NorthflankSyncSchema = BaseSecretSyncSchema(SecretSync.Northflank, NorthflankSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Northflank), + destinationConfig: NorthflankSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Northflank] })); export const CreateNorthflankSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Northflank, @@ -46,9 +50,11 @@ export const UpdateNorthflankSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: NorthflankSyncDestinationConfigSchema.optional() }); -export const NorthflankSyncListItemSchema = z.object({ - name: z.literal("Northflank"), - connection: z.literal(AppConnection.Northflank), - destination: z.literal(SecretSync.Northflank), - canImportSecrets: z.literal(true) -}); +export const NorthflankSyncListItemSchema = z + .object({ + name: z.literal("Northflank"), + connection: z.literal(AppConnection.Northflank), + destination: z.literal(SecretSync.Northflank), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Northflank] })); diff --git a/backend/src/services/secret-sync/railway/railway-sync-schemas.ts b/backend/src/services/secret-sync/railway/railway-sync-schemas.ts index 56cea0408..2a5bec1ab 100644 --- a/backend/src/services/secret-sync/railway/railway-sync-schemas.ts +++ b/backend/src/services/secret-sync/railway/railway-sync-schemas.ts @@ -10,6 +10,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const RailwaySyncDestinationConfigSchema = z.object({ projectId: z .string() @@ -29,10 +31,12 @@ const RailwaySyncDestinationConfigSchema = z.object({ const RailwaySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const RailwaySyncSchema = BaseSecretSyncSchema(SecretSync.Railway, RailwaySyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Railway), - destinationConfig: RailwaySyncDestinationConfigSchema -}); +export const RailwaySyncSchema = BaseSecretSyncSchema(SecretSync.Railway, RailwaySyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Railway), + destinationConfig: RailwaySyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Railway] })); export const CreateRailwaySyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Railway, @@ -48,9 +52,11 @@ export const UpdateRailwaySyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: RailwaySyncDestinationConfigSchema.optional() }); -export const RailwaySyncListItemSchema = z.object({ - name: z.literal("Railway"), - connection: z.literal(AppConnection.Railway), - destination: z.literal(SecretSync.Railway), - canImportSecrets: z.literal(true) -}); +export const RailwaySyncListItemSchema = z + .object({ + name: z.literal("Railway"), + connection: z.literal(AppConnection.Railway), + destination: z.literal(SecretSync.Railway), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Railway] })); diff --git a/backend/src/services/secret-sync/render/render-sync-schemas.ts b/backend/src/services/secret-sync/render/render-sync-schemas.ts index 0bc116255..266f093f8 100644 --- a/backend/src/services/secret-sync/render/render-sync-schemas.ts +++ b/backend/src/services/secret-sync/render/render-sync-schemas.ts @@ -10,6 +10,7 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; import { RenderSyncScope, RenderSyncType } from "./render-sync-enums"; const RenderSyncDestinationConfigSchema = z.discriminatedUnion("scope", [ @@ -38,10 +39,12 @@ export const RenderSyncSchema = BaseSecretSyncSchema( SecretSync.Render, RenderSyncOptionsConfig, RenderSyncOptionsSchema -).extend({ - destination: z.literal(SecretSync.Render), - destinationConfig: RenderSyncDestinationConfigSchema -}); +) + .extend({ + destination: z.literal(SecretSync.Render), + destinationConfig: RenderSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Render] })); export const CreateRenderSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Render, @@ -59,9 +62,11 @@ export const UpdateRenderSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: RenderSyncDestinationConfigSchema.optional() }); -export const RenderSyncListItemSchema = z.object({ - name: z.literal("Render"), - connection: z.literal(AppConnection.Render), - destination: z.literal(SecretSync.Render), - canImportSecrets: z.literal(true) -}); +export const RenderSyncListItemSchema = z + .object({ + name: z.literal("Render"), + connection: z.literal(AppConnection.Render), + destination: z.literal(SecretSync.Render), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Render] })); diff --git a/backend/src/services/secret-sync/supabase/supabase-sync-schemas.ts b/backend/src/services/secret-sync/supabase/supabase-sync-schemas.ts index 633b40dab..cd888b556 100644 --- a/backend/src/services/secret-sync/supabase/supabase-sync-schemas.ts +++ b/backend/src/services/secret-sync/supabase/supabase-sync-schemas.ts @@ -9,6 +9,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const SupabaseSyncDestinationConfigSchema = z.object({ projectId: z.string().max(255).min(1, "Project ID is required"), projectName: z.string().max(255).min(1, "Project Name is required") @@ -16,10 +18,12 @@ const SupabaseSyncDestinationConfigSchema = z.object({ const SupabaseSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false }; -export const SupabaseSyncSchema = BaseSecretSyncSchema(SecretSync.Supabase, SupabaseSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Supabase), - destinationConfig: SupabaseSyncDestinationConfigSchema -}); +export const SupabaseSyncSchema = BaseSecretSyncSchema(SecretSync.Supabase, SupabaseSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Supabase), + destinationConfig: SupabaseSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Supabase] })); export const CreateSupabaseSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Supabase, @@ -35,9 +39,11 @@ export const UpdateSupabaseSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: SupabaseSyncDestinationConfigSchema.optional() }); -export const SupabaseSyncListItemSchema = z.object({ - name: z.literal("Supabase"), - connection: z.literal(AppConnection.Supabase), - destination: z.literal(SecretSync.Supabase), - canImportSecrets: z.literal(false) -}); +export const SupabaseSyncListItemSchema = z + .object({ + name: z.literal("Supabase"), + connection: z.literal(AppConnection.Supabase), + destination: z.literal(SecretSync.Supabase), + canImportSecrets: z.literal(false) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Supabase] })); diff --git a/backend/src/services/secret-sync/teamcity/teamcity-sync-schemas.ts b/backend/src/services/secret-sync/teamcity/teamcity-sync-schemas.ts index 21c09092f..ed56ae124 100644 --- a/backend/src/services/secret-sync/teamcity/teamcity-sync-schemas.ts +++ b/backend/src/services/secret-sync/teamcity/teamcity-sync-schemas.ts @@ -10,6 +10,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const TeamCitySyncDestinationConfigSchema = z.object({ project: z.string().trim().min(1, "Project required").describe(SecretSyncs.DESTINATION_CONFIG.TEAMCITY.project), buildConfig: z.string().trim().optional().describe(SecretSyncs.DESTINATION_CONFIG.TEAMCITY.buildConfig) @@ -17,10 +19,12 @@ const TeamCitySyncDestinationConfigSchema = z.object({ const TeamCitySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const TeamCitySyncSchema = BaseSecretSyncSchema(SecretSync.TeamCity, TeamCitySyncOptionsConfig).extend({ - destination: z.literal(SecretSync.TeamCity), - destinationConfig: TeamCitySyncDestinationConfigSchema -}); +export const TeamCitySyncSchema = BaseSecretSyncSchema(SecretSync.TeamCity, TeamCitySyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.TeamCity), + destinationConfig: TeamCitySyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.TeamCity] })); export const CreateTeamCitySyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.TeamCity, @@ -36,9 +40,11 @@ export const UpdateTeamCitySyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: TeamCitySyncDestinationConfigSchema.optional() }); -export const TeamCitySyncListItemSchema = z.object({ - name: z.literal("TeamCity"), - connection: z.literal(AppConnection.TeamCity), - destination: z.literal(SecretSync.TeamCity), - canImportSecrets: z.literal(true) -}); +export const TeamCitySyncListItemSchema = z + .object({ + name: z.literal("TeamCity"), + connection: z.literal(AppConnection.TeamCity), + destination: z.literal(SecretSync.TeamCity), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.TeamCity] })); diff --git a/backend/src/services/secret-sync/terraform-cloud/terraform-cloud-sync-schemas.ts b/backend/src/services/secret-sync/terraform-cloud/terraform-cloud-sync-schemas.ts index 359d7f4c5..1254b06ea 100644 --- a/backend/src/services/secret-sync/terraform-cloud/terraform-cloud-sync-schemas.ts +++ b/backend/src/services/secret-sync/terraform-cloud/terraform-cloud-sync-schemas.ts @@ -14,6 +14,8 @@ import { TerraformCloudSyncScope } from "@app/services/secret-sync/terraform-cloud/terraform-cloud-sync-enums"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const TerraformCloudSyncDestinationConfigSchema = z.discriminatedUnion("scope", [ z.object({ scope: z @@ -47,13 +49,12 @@ const TerraformCloudSyncDestinationConfigSchema = z.discriminatedUnion("scope", const TerraformCloudSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false }; -export const TerraformCloudSyncSchema = BaseSecretSyncSchema( - SecretSync.TerraformCloud, - TerraformCloudSyncOptionsConfig -).extend({ - destination: z.literal(SecretSync.TerraformCloud), - destinationConfig: TerraformCloudSyncDestinationConfigSchema -}); +export const TerraformCloudSyncSchema = BaseSecretSyncSchema(SecretSync.TerraformCloud, TerraformCloudSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.TerraformCloud), + destinationConfig: TerraformCloudSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.TerraformCloud] })); export const CreateTerraformCloudSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.TerraformCloud, @@ -69,9 +70,11 @@ export const UpdateTerraformCloudSyncSchema = GenericUpdateSecretSyncFieldsSchem destinationConfig: TerraformCloudSyncDestinationConfigSchema.optional() }); -export const TerraformCloudSyncListItemSchema = z.object({ - name: z.literal("Terraform Cloud"), - connection: z.literal(AppConnection.TerraformCloud), - destination: z.literal(SecretSync.TerraformCloud), - canImportSecrets: z.literal(false) -}); +export const TerraformCloudSyncListItemSchema = z + .object({ + name: z.literal("Terraform Cloud"), + connection: z.literal(AppConnection.TerraformCloud), + destination: z.literal(SecretSync.TerraformCloud), + canImportSecrets: z.literal(false) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.TerraformCloud] })); diff --git a/backend/src/services/secret-sync/vercel/vercel-sync-schemas.ts b/backend/src/services/secret-sync/vercel/vercel-sync-schemas.ts index 84d7a6da4..06cdb6cc1 100644 --- a/backend/src/services/secret-sync/vercel/vercel-sync-schemas.ts +++ b/backend/src/services/secret-sync/vercel/vercel-sync-schemas.ts @@ -10,6 +10,7 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; import { VercelEnvironmentType } from "./vercel-sync-enums"; const VercelSyncDestinationConfigSchema = z.object({ @@ -22,10 +23,12 @@ const VercelSyncDestinationConfigSchema = z.object({ const VercelSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const VercelSyncSchema = BaseSecretSyncSchema(SecretSync.Vercel, VercelSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Vercel), - destinationConfig: VercelSyncDestinationConfigSchema -}); +export const VercelSyncSchema = BaseSecretSyncSchema(SecretSync.Vercel, VercelSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Vercel), + destinationConfig: VercelSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Vercel] })); export const CreateVercelSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Vercel, @@ -41,9 +44,11 @@ export const UpdateVercelSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: VercelSyncDestinationConfigSchema.optional() }); -export const VercelSyncListItemSchema = z.object({ - name: z.literal("Vercel"), - connection: z.literal(AppConnection.Vercel), - destination: z.literal(SecretSync.Vercel), - canImportSecrets: z.literal(true) -}); +export const VercelSyncListItemSchema = z + .object({ + name: z.literal("Vercel"), + connection: z.literal(AppConnection.Vercel), + destination: z.literal(SecretSync.Vercel), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Vercel] })); diff --git a/backend/src/services/secret-sync/windmill/windmill-sync-schemas.ts b/backend/src/services/secret-sync/windmill/windmill-sync-schemas.ts index 5740e21c9..38fdfda15 100644 --- a/backend/src/services/secret-sync/windmill/windmill-sync-schemas.ts +++ b/backend/src/services/secret-sync/windmill/windmill-sync-schemas.ts @@ -11,6 +11,8 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; + const pathCharacterValidator = characterValidator([ CharacterType.AlphaNumeric, CharacterType.Underscore, @@ -39,10 +41,12 @@ const WindmillSyncDestinationConfigSchema = z.object({ const WindmillSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const WindmillSyncSchema = BaseSecretSyncSchema(SecretSync.Windmill, WindmillSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Windmill), - destinationConfig: WindmillSyncDestinationConfigSchema -}); +export const WindmillSyncSchema = BaseSecretSyncSchema(SecretSync.Windmill, WindmillSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Windmill), + destinationConfig: WindmillSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Windmill] })); export const CreateWindmillSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Windmill, @@ -58,9 +62,11 @@ export const UpdateWindmillSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: WindmillSyncDestinationConfigSchema.optional() }); -export const WindmillSyncListItemSchema = z.object({ - name: z.literal("Windmill"), - connection: z.literal(AppConnection.Windmill), - destination: z.literal(SecretSync.Windmill), - canImportSecrets: z.literal(true) -}); +export const WindmillSyncListItemSchema = z + .object({ + name: z.literal("Windmill"), + connection: z.literal(AppConnection.Windmill), + destination: z.literal(SecretSync.Windmill), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Windmill] })); diff --git a/backend/src/services/secret-sync/zabbix/zabbix-sync-schemas.ts b/backend/src/services/secret-sync/zabbix/zabbix-sync-schemas.ts index 94a729cb6..09cfbecdf 100644 --- a/backend/src/services/secret-sync/zabbix/zabbix-sync-schemas.ts +++ b/backend/src/services/secret-sync/zabbix/zabbix-sync-schemas.ts @@ -10,6 +10,7 @@ import { } from "@app/services/secret-sync/secret-sync-schemas"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; import { ZabbixSyncScope } from "./zabbix-sync-enums"; const ZabbixSyncDestinationConfigSchema = z.discriminatedUnion("scope", [ @@ -40,10 +41,12 @@ const ZabbixSyncDestinationConfigSchema = z.discriminatedUnion("scope", [ const ZabbixSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const ZabbixSyncSchema = BaseSecretSyncSchema(SecretSync.Zabbix, ZabbixSyncOptionsConfig).extend({ - destination: z.literal(SecretSync.Zabbix), - destinationConfig: ZabbixSyncDestinationConfigSchema -}); +export const ZabbixSyncSchema = BaseSecretSyncSchema(SecretSync.Zabbix, ZabbixSyncOptionsConfig) + .extend({ + destination: z.literal(SecretSync.Zabbix), + destinationConfig: ZabbixSyncDestinationConfigSchema + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Zabbix] })); export const CreateZabbixSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Zabbix, @@ -59,9 +62,11 @@ export const UpdateZabbixSyncSchema = GenericUpdateSecretSyncFieldsSchema( destinationConfig: ZabbixSyncDestinationConfigSchema.optional() }); -export const ZabbixSyncListItemSchema = z.object({ - name: z.literal("Zabbix"), - connection: z.literal(AppConnection.Zabbix), - destination: z.literal(SecretSync.Zabbix), - canImportSecrets: z.literal(true) -}); +export const ZabbixSyncListItemSchema = z + .object({ + name: z.literal("Zabbix"), + connection: z.literal(AppConnection.Zabbix), + destination: z.literal(SecretSync.Zabbix), + canImportSecrets: z.literal(true) + }) + .describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.Zabbix] })); diff --git a/docs/documentation/platform/workflow-integrations/slack-integration.mdx b/docs/documentation/platform/workflow-integrations/slack-integration.mdx index 2317baabe..38fc15ebe 100644 --- a/docs/documentation/platform/workflow-integrations/slack-integration.mdx +++ b/docs/documentation/platform/workflow-integrations/slack-integration.mdx @@ -17,13 +17,13 @@ This guide will provide step by step instructions on how to configure Slack inte ![org-slack-overview](/images/platform/workflow-integrations/slack-integration/org-slack-integration-overview.png) - Press "Add" and select "Slack" as the platform. + Press **Add** and select **Slack** as the platform. ![org-slack-initial-add](/images/platform/workflow-integrations/slack-integration/org-slack-integration-initial-add.png) Give your Slack integration a descriptive alias. You will use this to select the Slack integration for your project. ![org-slack-add-form](/images/platform/workflow-integrations/slack-integration/org-slack-integration-add-form.png) - Press **Connect Slack**. This opens up the Slack app installation flow. Select the Slack workspace you want to install the custom Slack app to and press **Allow**. + Press **Connect Slack**. This opens up the Slack app installation flow. Select the Slack workspace you want to install the custom Slack app to and press **Install Infisical**. ![org-slack-authenticate](/images/platform/workflow-integrations/slack-integration/cloud-org-slack-integration-authenticate.png) This completes the workflow integration creation flow. The projects in your organization can now use this Slack integration to send real-time updates to your Slack workspace. @@ -38,6 +38,7 @@ This guide will provide step by step instructions on how to configure Slack inte + Press **Add** and select **Slack** as the platform. ![project-slack-overview](/images/platform/workflow-integrations/slack-integration/project-slack-integration-overview.png) @@ -66,13 +67,13 @@ This guide will provide step by step instructions on how to configure Slack inte Before anything else, you need to setup the Slack app to be used by your Infisical instance. Because you're self-hosting, you will need to create this Slack application as demonstrated in the preceding step. + + Click the **Create Slack app** button. This will open up a new window with the + custom app creation flow on Slack. + ![admin-settings-slack-overview](/images/platform/workflow-integrations/slack-integration/admin-slack-integration-overview.png) - Click the "Create Slack app" button. This will open up a new window with the - custom app creation flow on Slack. - ![admin-slack-create-app](/images/platform/workflow-integrations/slack-integration/admin-slack-integration-create-app.png) - Select the Slack workspace you want to integrate with Infisical. ![admin-slack-app-workspace-select](/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-workspace-select.png) @@ -87,7 +88,7 @@ This guide will provide step by step instructions on how to configure Slack inte Copy the Client ID and Client Secret values from your newly created custom Slack app and add them to Infisical. ![admin-slack-app-credentials](/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-credentials.png) ![admin-slack-app-credentials-form](/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-credential-form.png) - Complete the admin setup by pressing Save. + Complete the admin setup by pressing **Save**. @@ -101,13 +102,13 @@ This guide will provide step by step instructions on how to configure Slack inte ![org-slack-overview](/images/platform/workflow-integrations/slack-integration/org-slack-integration-overview.png) - Press "Add" and select "Slack" as the platform. + Press **Add** and select **Slack** as the platform. ![org-slack-initial-add](/images/platform/workflow-integrations/slack-integration/org-slack-integration-initial-add.png) Give your Slack integration a descriptive alias. You will use this to select the Slack integration for your project. ![org-slack-add-form](/images/platform/workflow-integrations/slack-integration/org-slack-integration-add-form.png) - Press **Connect Slack**. This opens up the Slack app installation flow. Select the Slack workspace you want to install the custom Slack app to and press **Allow**. + Press **Connect Slack**. This opens up the Slack app installation flow. Select the Slack workspace you want to install the custom Slack app to and press **Install Infisical**. ![org-slack-authenticate](/images/platform/workflow-integrations/slack-integration/org-slack-integration-authenticate.png) Your Slack bot will then be added to your selected Slack workspace. This completes the workflow integration creation flow. Your projects in the organization can now use this Slack integration to send real-time updates to your Slack workspace. @@ -122,6 +123,7 @@ This guide will provide step by step instructions on how to configure Slack inte + Press **Add** and select **Slack** as the platform. ![project-slack-overview](/images/platform/workflow-integrations/slack-integration/project-slack-integration-overview.png) @@ -162,3 +164,87 @@ This guide will provide step by step instructions on how to configure Slack inte channels](/images/platform/workflow-integrations/slack-integration/private-slack-setup-channel-field.png) + +## Slack Events + +The Slack integration supports the following events that can be configured for your projects. Each event is triggered when specific actions occur within your Infisical instance. + + + + ### Access Request + This event is triggered when a user creates a new access approval request for a project. The notification includes details about the requester, the requested permissions, the secret path and environment, and whether the access is temporary or permanent. + + **When it's triggered:** + - A user submits a new access approval request through the Infisical UI + - The request requires approval based on configured access approval policies + - The notification is sent to the configured access request channels + + **Notification includes:** + - Requester's full name and email + - Requested permissions (read, write, etc.) + - Secret path and environment + - Access type (temporary or permanent) + - Optional user note + - Direct link to review the request + + ![access request notification](/images/platform/workflow-integrations/slack-integration/access-request-notification.png) + + ### Access Request Updated + This event is triggered when an existing access approval request is modified or updated. This helps approvers stay informed about changes to pending requests. + + **When it's triggered:** + - An access approval request is edited by the requester or another authorized user + - Changes are made to permissions, temporary range, or notes + - The notification is sent to the configured access request channels + + **Notification includes:** + - Original requester's information + - Editor's full name and email (who made the update) + - Updated permissions + - Updated secret path and environment + - Editor's note explaining the changes + - Direct link to review the updated request + + ![access request updated notification](/images/platform/workflow-integrations/slack-integration/access-request-updated-notification.png) + + + + ### Secret Approval + This event is triggered when a secret approval request is created. This occurs when a user attempts to create, update, or delete secrets that require approval based on secret approval policies. + + **When it's triggered:** + - A user creates, updates, or deletes secrets in a path protected by a secret approval policy + - The changes require approval before being applied + - The notification is sent to the configured secret request channels + + **Notification includes:** + - User's email who initiated the change + - Environment and secret path + - List of secret keys affected + - Direct link to review and approve the secret changes + + ![secret approval notification](/images/platform/workflow-integrations/slack-integration/secret-approval-notification.png) + + + + ### Secret Sync Error + This event is triggered when a secret sync operation fails. Secret syncs allow you to synchronize secrets between Infisical and external systems like GitHub, GitLab, AWS Secrets Manager, and others. + + **When it's triggered:** + - A secret sync fails to push secrets to the destination + - A secret sync fails to pull secrets from the source + - A secret sync fails to import secrets + - A secret sync fails to remove secrets + - Any other error occurs during the sync process + + **Notification includes:** + - Sync name and destination + - The action that failed + - Environment and secret path + - Project name + - Detailed error message explaining the failure + - Direct link to view and troubleshoot the sync configuration + + ![secret sync error notification](/images/platform/workflow-integrations/slack-integration/secret-sync-error-notification.png) + + diff --git a/docs/images/platform/workflow-integrations/slack-integration/access-request-notification.png b/docs/images/platform/workflow-integrations/slack-integration/access-request-notification.png new file mode 100644 index 000000000..ad40c8f4f Binary files /dev/null and b/docs/images/platform/workflow-integrations/slack-integration/access-request-notification.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/access-request-updated-notification.png b/docs/images/platform/workflow-integrations/slack-integration/access-request-updated-notification.png new file mode 100644 index 000000000..f7bbf51ae Binary files /dev/null and b/docs/images/platform/workflow-integrations/slack-integration/access-request-updated-notification.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-credential-form.png b/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-credential-form.png index 0b97be58a..1af15b074 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-credential-form.png and b/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-credential-form.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-credentials.png b/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-credentials.png index ddf245eff..136cea9d8 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-credentials.png and b/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-credentials.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-summary.png b/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-summary.png index 95e9fe4ba..31cd0c458 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-summary.png and b/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-summary.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-workspace-select.png b/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-workspace-select.png index 0c047ab1a..39955f547 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-workspace-select.png and b/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-app-workspace-select.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-create-app.png b/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-create-app.png deleted file mode 100644 index 502dbde0a..000000000 Binary files a/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-create-app.png and /dev/null differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-overview.png b/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-overview.png index 54ad6ca6e..3a200f296 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-overview.png and b/docs/images/platform/workflow-integrations/slack-integration/admin-slack-integration-overview.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/cloud-org-slack-integration-authenticate.png b/docs/images/platform/workflow-integrations/slack-integration/cloud-org-slack-integration-authenticate.png index 048e91f85..8b9e59eca 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/cloud-org-slack-integration-authenticate.png and b/docs/images/platform/workflow-integrations/slack-integration/cloud-org-slack-integration-authenticate.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-add-form.png b/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-add-form.png index 97b38d6e4..1554b9bd8 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-add-form.png and b/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-add-form.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-authenticate.png b/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-authenticate.png index 166e5849f..ef492c018 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-authenticate.png and b/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-authenticate.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-created.png b/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-created.png index f46f61d89..bf98abd0e 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-created.png and b/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-created.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-initial-add.png b/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-initial-add.png index 5bd66d932..5d9316d75 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-initial-add.png and b/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-initial-add.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-overview.png b/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-overview.png index 1f7386559..9bfbbb7f7 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-overview.png and b/docs/images/platform/workflow-integrations/slack-integration/org-slack-integration-overview.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/private-slack-setup-channel-field.png b/docs/images/platform/workflow-integrations/slack-integration/private-slack-setup-channel-field.png index 3f6bd0d1d..4864045f3 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/private-slack-setup-channel-field.png and b/docs/images/platform/workflow-integrations/slack-integration/private-slack-setup-channel-field.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/project-slack-integration-config.png b/docs/images/platform/workflow-integrations/slack-integration/project-slack-integration-config.png index c6dd70ad7..a844988d5 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/project-slack-integration-config.png and b/docs/images/platform/workflow-integrations/slack-integration/project-slack-integration-config.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/project-slack-integration-overview.png b/docs/images/platform/workflow-integrations/slack-integration/project-slack-integration-overview.png index 944db9cca..6806527da 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/project-slack-integration-overview.png and b/docs/images/platform/workflow-integrations/slack-integration/project-slack-integration-overview.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/project-slack-integration-select.png b/docs/images/platform/workflow-integrations/slack-integration/project-slack-integration-select.png index 073d3fd93..9367d55a5 100644 Binary files a/docs/images/platform/workflow-integrations/slack-integration/project-slack-integration-select.png and b/docs/images/platform/workflow-integrations/slack-integration/project-slack-integration-select.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/secret-approval-notification.png b/docs/images/platform/workflow-integrations/slack-integration/secret-approval-notification.png new file mode 100644 index 000000000..ef6d2ddad Binary files /dev/null and b/docs/images/platform/workflow-integrations/slack-integration/secret-approval-notification.png differ diff --git a/docs/images/platform/workflow-integrations/slack-integration/secret-sync-error-notification.png b/docs/images/platform/workflow-integrations/slack-integration/secret-sync-error-notification.png new file mode 100644 index 000000000..121864df6 Binary files /dev/null and b/docs/images/platform/workflow-integrations/slack-integration/secret-sync-error-notification.png differ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8965ce45e..e8fdda096 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -150,7 +150,8 @@ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", @@ -195,7 +196,6 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -488,7 +488,6 @@ "resolved": "https://registry.npmjs.org/@casl/ability/-/ability-6.7.2.tgz", "integrity": "sha512-KjKXlcjKbUz8dKw7PY56F7qlfOFgxTU6tnlJ8YrbDyWkJMIlHa6VRWzCD8RU20zbJUC1hExhOFggZjm6tf1mUw==", "license": "MIT", - "peer": true, "dependencies": { "@ucast/mongo2js": "^1.3.0" }, @@ -547,7 +546,6 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", - "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -1325,7 +1323,6 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.1.tgz", "integrity": "sha512-8dBIHbfsKlCk2jHQ9PoRBg2Z+4TwyE3vZICSnoDlnsHA6SiMlTwfmW6yX0lHsRmWJugkeb92sA0hZdkXJhuz+g==", "license": "MIT", - "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.1" }, @@ -2016,7 +2013,6 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.0.0", @@ -4412,7 +4408,6 @@ "integrity": "sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/utils": "^8.13.0", "eslint-visitor-keys": "^4.2.0", @@ -5038,7 +5033,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.95.1.tgz", "integrity": "sha512-P5x4yNhcdkYsCEoYeGZP8Q9Jlxf0WXJa4G/xvbmM905seZc9FqJqvCSRvX3dWTPOXRABhl4g+8DHqfft0c/AvQ==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/history": "1.95.0", "@tanstack/react-store": "^0.7.0", @@ -5250,6 +5244,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -5270,6 +5265,7 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -5280,6 +5276,7 @@ "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", @@ -5299,7 +5296,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@testing-library/user-event": { "version": "14.6.1", @@ -5307,6 +5305,7 @@ "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12", "npm": ">=6" @@ -5327,7 +5326,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -5380,6 +5380,7 @@ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" @@ -5453,7 +5454,8 @@ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/doctrine": { "version": "0.0.9", @@ -5589,7 +5591,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.16.tgz", "integrity": "sha512-oh8AMIC4Y2ciKufU8hnKgs+ufgbA/dhPTACaZPM86AbwX9QwnFtSoPWEeRUj8fge+v6kFt78BXcDhAU1SrrAsw==", "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -5601,7 +5602,6 @@ "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -5649,7 +5649,6 @@ "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.34.0", @@ -5690,7 +5689,6 @@ "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.34.0", "@typescript-eslint/types": "8.34.0", @@ -5962,6 +5960,7 @@ "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", @@ -5979,6 +5978,7 @@ "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", @@ -6006,6 +6006,7 @@ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "^1.0.0" } @@ -6016,6 +6017,7 @@ "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tinyrainbow": "^2.0.0" }, @@ -6029,6 +6031,7 @@ "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tinyspy": "^4.0.3" }, @@ -6042,6 +6045,7 @@ "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", @@ -6115,7 +6119,6 @@ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6232,6 +6235,7 @@ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">= 0.4" } @@ -6280,6 +6284,7 @@ "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -6360,6 +6365,7 @@ "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -6447,6 +6453,7 @@ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" } @@ -6457,6 +6464,7 @@ "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.0.1" }, @@ -6469,7 +6477,8 @@ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/asynckit": { "version": "0.4.0", @@ -6519,6 +6528,7 @@ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">= 0.4" } @@ -6620,6 +6630,7 @@ "integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "open": "^8.0.4" }, @@ -6856,7 +6867,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -7038,6 +7048,7 @@ "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -7108,6 +7119,7 @@ "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 16" } @@ -7493,7 +7505,8 @@ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cssesc": { "version": "3.0.0", @@ -7512,8 +7525,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cva": { "name": "class-variance-authority", @@ -7585,7 +7597,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -7639,7 +7650,8 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true, - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/data-view-buffer": { "version": "1.0.1", @@ -7749,6 +7761,7 @@ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -7783,6 +7796,7 @@ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -7904,7 +7918,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dom-helpers": { "version": "5.2.1", @@ -8114,6 +8129,7 @@ "integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -8197,7 +8213,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8239,6 +8254,7 @@ "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.3.4" }, @@ -8275,7 +8291,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -8332,7 +8347,6 @@ "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "eslint-config-airbnb-base": "^15.0.0", "object.assign": "^4.1.2", @@ -8355,7 +8369,6 @@ "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "confusing-browser-globals": "^1.0.10", "object.assign": "^4.1.2", @@ -8386,7 +8399,6 @@ "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8486,7 +8498,6 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -8616,6 +8627,7 @@ "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -8649,7 +8661,6 @@ "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -8673,6 +8684,7 @@ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -8686,6 +8698,7 @@ "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -8704,6 +8717,7 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" } @@ -8865,6 +8879,7 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -9895,7 +9910,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.23.2" }, @@ -9989,6 +10003,7 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -10265,6 +10280,7 @@ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "is-docker": "cli.js" }, @@ -10586,6 +10602,7 @@ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -10621,6 +10638,7 @@ "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", "license": "MIT", + "peer": true, "funding": { "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" @@ -10632,6 +10650,7 @@ "integrity": "sha512-x4WH0BWmrMmg4oHHl+duwubhrvczGlyuGAZu3nvrf0UXOfPu8IhZObFEr7DE/iv01YgVZrsOiRcqw2srkKEDIA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", @@ -10781,6 +10800,7 @@ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -10815,7 +10835,8 @@ "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", "dev": true, - "license": "CC0-1.0" + "license": "CC0-1.0", + "peer": true }, "node_modules/language-tags": { "version": "1.0.9", @@ -10823,6 +10844,7 @@ "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "language-subtag-registry": "^0.3.20" }, @@ -10855,6 +10877,7 @@ "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.102.tgz", "integrity": "sha512-g70kydI0I1sZU0ChO8mBbhw0oUW/8U0GHzygpvEIx8k+jgOpqnTSb/E+70toYVqHxBhrERD21TwD5QcZJQ40ZQ==", "license": "MIT", + "peer": true, "dependencies": { "isomorphic.js": "^0.2.4" }, @@ -11179,7 +11202,8 @@ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lru-cache": { "version": "5.1.1", @@ -11206,6 +11230,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -11897,6 +11922,7 @@ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=4" } @@ -12245,6 +12271,7 @@ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -12514,6 +12541,7 @@ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 14.16" } @@ -12665,7 +12693,6 @@ "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -12782,6 +12809,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -12797,6 +12825,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -12809,7 +12838,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/prismjs": { "version": "1.30.0", @@ -13010,7 +13040,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -13038,7 +13067,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -13130,7 +13158,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -13181,7 +13208,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.3.tgz", "integrity": "sha512-IK18V6GVbab4TAo1/cz3kqajxbDPGofdF0w7VHdCo0Nt8PrPlOZcuuDq9YYIV1BtjcX78x0XsldbQRQnQXWXmw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -13432,6 +13458,7 @@ "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", @@ -13449,6 +13476,7 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -13465,6 +13493,7 @@ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" @@ -13479,6 +13508,7 @@ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "min-indent": "^1.0.0" }, @@ -13693,7 +13723,6 @@ "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -14204,6 +14233,7 @@ "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -14219,6 +14249,7 @@ "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -14246,6 +14277,7 @@ "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" @@ -14466,8 +14498,7 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz", "integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.2.1", @@ -14571,6 +14602,7 @@ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=14.0.0" } @@ -14581,6 +14613,7 @@ "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=14.0.0" } @@ -14865,7 +14898,6 @@ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15251,7 +15283,6 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -15669,6 +15700,7 @@ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -15713,7 +15745,6 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -15864,7 +15895,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/frontend/src/hooks/api/secretSharing/types.ts b/frontend/src/hooks/api/secretSharing/types.ts index c35228fab..524346beb 100644 --- a/frontend/src/hooks/api/secretSharing/types.ts +++ b/frontend/src/hooks/api/secretSharing/types.ts @@ -59,6 +59,8 @@ export type TViewSharedSecretResponse = { tag: string; accessType: SecretSharingAccessType; orgName?: string; + expiresAt?: Date | string; + expiresAfterViews?: number | null; }; }; diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx index ec660b339..33f92a122 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx @@ -10,7 +10,7 @@ import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; import { useDeleteCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/mutations"; -import { TCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/types"; +import { type TCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/types"; import { CreateTemplateModal } from "./CreateTemplateModal"; import { TemplateList } from "./TemplateList"; @@ -84,7 +84,12 @@ export const CertificateTemplatesV2Tab = () => { - setIsCreateModalOpen(false)} /> + { + setIsCreateModalOpen(false); + }} + /> {selectedTemplate && ( <> diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx index b929ce36e..d55c1b5ef 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx @@ -36,13 +36,18 @@ import { CertDurationUnit, CertExtendedKeyUsageType, CertKeyUsageType, + CertSanInclude, CertSubjectAlternativeNameType, + CertSubjectAttributeInclude, CertSubjectAttributeType, SAN_INCLUDE_OPTIONS, SAN_TYPE_OPTIONS, SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS, - SUBJECT_ATTRIBUTE_TYPE_OPTIONS + SUBJECT_ATTRIBUTE_TYPE_OPTIONS, + TEMPLATE_PRESET_IDS, + type TemplatePresetId } from "./shared/certificate-constants"; +import { CERTIFICATE_TEMPLATE_PRESETS } from "./shared/template-presets"; import { KeyUsagesSection, TemplateFormData, templateSchema } from "./shared"; export type FormData = TemplateFormData; @@ -91,7 +96,7 @@ const SIGNATURE_ALGORITHMS = [ "SHA256-ECDSA", "SHA384-ECDSA", "SHA512-ECDSA" -]; +] as const; const KEY_ALGORITHMS = [ "RSA-2048", @@ -100,7 +105,7 @@ const KEY_ALGORITHMS = [ "ECDSA-P256", "ECDSA-P384", "ECDSA-P521" -]; +] as const; export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" }: Props) => { const { currentProject } = useProject(); @@ -117,7 +122,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" subj.allowed.forEach((allowedValue) => { attributes.push({ type: subj.type as CertSubjectAttributeType, - include: "optional", + include: CertSubjectAttributeInclude.OPTIONAL, value: [allowedValue] }); }); @@ -126,7 +131,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" subj.denied.forEach((deniedValue) => { attributes.push({ type: subj.type as CertSubjectAttributeType, - include: "prohibit", + include: CertSubjectAttributeInclude.PROHIBIT, value: [deniedValue] }); }); @@ -141,7 +146,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" san.required.forEach((requiredValue) => { subjectAlternativeNames.push({ type: san.type as CertSubjectAlternativeNameType, - include: "mandatory", + include: CertSanInclude.MANDATORY, value: [requiredValue] }); }); @@ -150,7 +155,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" san.allowed.forEach((allowedValue) => { subjectAlternativeNames.push({ type: san.type as CertSubjectAlternativeNameType, - include: "optional", + include: CertSanInclude.OPTIONAL, value: [allowedValue] }); }); @@ -159,7 +164,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" san.denied.forEach((deniedValue) => { subjectAlternativeNames.push({ type: san.type as CertSubjectAlternativeNameType, - include: "prohibit", + include: CertSanInclude.PROHIBIT, value: [deniedValue] }); }); @@ -219,6 +224,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" }; return { + preset: TEMPLATE_PRESET_IDS.CUSTOM, name: templateData.name || "", description: templateData.description || "", attributes, @@ -231,25 +237,30 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" }; }; - const getDefaultValues = (): FormData => ({ - name: "", - description: "", - attributes: [], - keyUsages: { requiredUsages: [], optionalUsages: [] }, - extendedKeyUsages: { requiredUsages: [], optionalUsages: [] }, - subjectAlternativeNames: [], - validity: { - maxDuration: { value: 365, unit: CertDurationUnit.DAYS } - }, - signatureAlgorithm: { - allowedAlgorithms: [] - }, - keyAlgorithm: { - allowedKeyTypes: [] - } - }); + const getDefaultValues = (): FormData & { preset: TemplatePresetId } => { + return { + preset: TEMPLATE_PRESET_IDS.CUSTOM, + name: "", + description: "", + attributes: [], + keyUsages: { requiredUsages: [], optionalUsages: [] }, + extendedKeyUsages: { requiredUsages: [], optionalUsages: [] }, + subjectAlternativeNames: [], + validity: { + maxDuration: { value: 365, unit: CertDurationUnit.DAYS } + }, + signatureAlgorithm: { + allowedAlgorithms: [] + }, + keyAlgorithm: { + allowedKeyTypes: [] + } + }; + }; - const { control, handleSubmit, reset, watch, setValue, formState } = useForm({ + const { control, handleSubmit, reset, watch, setValue, formState } = useForm< + FormData & { preset: TemplatePresetId } + >({ resolver: zodResolver(templateSchema), defaultValues: getDefaultValues(), mode: "onChange", @@ -260,7 +271,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" useEffect(() => { if (isEdit && template) { const convertedData = convertApiToUiFormat(template); - reset(convertedData); + reset({ ...convertedData, preset: TEMPLATE_PRESET_IDS.CUSTOM }); } else if (!isEdit) { reset(getDefaultValues()); } @@ -273,6 +284,37 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" requiredUsages: [], optionalUsages: [] }; + const watchedPreset = watch("preset") || TEMPLATE_PRESET_IDS.CUSTOM; + + const handlePresetChange = (presetId: TemplatePresetId) => { + setValue("preset", presetId); + + if (presetId === TEMPLATE_PRESET_IDS.CUSTOM) { + return; + } + + const selectedPreset = CERTIFICATE_TEMPLATE_PRESETS.find((p) => p.id === presetId); + if (selectedPreset) { + if (selectedPreset.formData.keyUsages) { + setValue("keyUsages", selectedPreset.formData.keyUsages); + } + if (selectedPreset.formData.extendedKeyUsages) { + setValue("extendedKeyUsages", selectedPreset.formData.extendedKeyUsages); + } + if (selectedPreset.formData.attributes) { + setValue("attributes", selectedPreset.formData.attributes); + } + if (selectedPreset.formData.subjectAlternativeNames) { + setValue("subjectAlternativeNames", selectedPreset.formData.subjectAlternativeNames); + } + if (selectedPreset.formData.signatureAlgorithm) { + setValue("signatureAlgorithm", selectedPreset.formData.signatureAlgorithm); + } + if (selectedPreset.formData.keyAlgorithm) { + setValue("keyAlgorithm", selectedPreset.formData.keyAlgorithm); + } + } + }; const consolidateByType = < T extends { type: string; allowed?: string[]; required?: string[]; denied?: string[] } @@ -309,9 +351,17 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" data.attributes?.map((attr) => { const result: AttributeTransform = { type: attr.type }; - if (attr.include === "optional" && attr.value && attr.value.length > 0) { + if ( + attr.include === CertSubjectAttributeInclude.OPTIONAL && + attr.value && + attr.value.length > 0 + ) { result.allowed = attr.value; - } else if (attr.include === "prohibit" && attr.value && attr.value.length > 0) { + } else if ( + attr.include === CertSubjectAttributeInclude.PROHIBIT && + attr.value && + attr.value.length > 0 + ) { result.denied = attr.value; } @@ -322,11 +372,11 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" data.subjectAlternativeNames?.map((san) => { const result: SanTransform = { type: san.type }; - if (san.include === "mandatory" && san.value && san.value.length > 0) { + if (san.include === CertSanInclude.MANDATORY && san.value && san.value.length > 0) { result.required = san.value; - } else if (san.include === "optional" && san.value && san.value.length > 0) { + } else if (san.include === CertSanInclude.OPTIONAL && san.value && san.value.length > 0) { result.allowed = san.value; - } else if (san.include === "prohibit" && san.value && san.value.length > 0) { + } else if (san.include === CertSanInclude.PROHIBIT && san.value && san.value.length > 0) { result.denied = san.value; } @@ -464,11 +514,19 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" value: ["*"] }; setValue("attributes", [...watchedAttributes, newAttribute]); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }; const removeAttribute = (index: number) => { const newAttributes = watchedAttributes.filter((_, i) => i !== index); setValue("attributes", newAttributes); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }; const addSan = () => { @@ -478,11 +536,19 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" value: ["*"] }; setValue("subjectAlternativeNames", [...watchedSans, newSan]); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }; const removeSan = (index: number) => { const newSans = watchedSans.filter((_, i) => i !== index); setValue("subjectAlternativeNames", newSans); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }; const handleKeyUsagesChange = (usages: { @@ -493,6 +559,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" requiredUsages: usages.requiredUsages, optionalUsages: usages.optionalUsages }); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }; const handleExtendedKeyUsagesChange = (usages: { @@ -503,6 +573,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" requiredUsages: usages.requiredUsages, optionalUsages: usages.optionalUsages }); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }; return ( @@ -555,6 +629,33 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" )} /> + + ( + + + + )} + /> Subject Attributes @@ -595,6 +696,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" type: value as CertSubjectAttributeType }; setValue("attributes", newAttributes); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }} className="w-48" > @@ -615,6 +720,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" value as (typeof SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS)[number] }; setValue("attributes", newAttributes); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }} className="w-32" > @@ -635,6 +744,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" value: e.target.value.trim() ? [e.target.value.trim()] : [] }; setValue("attributes", newAttributes); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }} className={`flex-1 ${ attr.value && attr.value.length > 0 && attr.value[0] === "" @@ -701,6 +814,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" type: value as CertSubjectAlternativeNameType }; setValue("subjectAlternativeNames", newSans); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }} className="w-36" > @@ -720,6 +837,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" include: value as (typeof SAN_INCLUDE_OPTIONS)[number] }; setValue("subjectAlternativeNames", newSans); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }} className="w-32" > @@ -740,6 +861,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" value: e.target.value.trim() ? [e.target.value.trim()] : [] }; setValue("subjectAlternativeNames", newSans); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }} className={`flex-1 ${ san.value && san.value.length > 0 && san.value[0] === "" diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/certificate-constants.ts b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/certificate-constants.ts index 50b69a2e2..47fdcd5a2 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/certificate-constants.ts +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/certificate-constants.ts @@ -150,8 +150,19 @@ export const SUBJECT_ATTRIBUTE_TYPE_OPTIONS = Object.values(CertSubjectAttribute export const ATTRIBUTE_RULE_OPTIONS = Object.values(CertAttributeRule); export const SAN_EFFECT_OPTIONS = Object.values(CertSanEffect); -export const SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS = ["optional", "prohibit"] as const; -export const SAN_INCLUDE_OPTIONS = ["mandatory", "optional", "prohibit"] as const; +export enum CertSubjectAttributeInclude { + OPTIONAL = "optional", + PROHIBIT = "prohibit" +} + +export enum CertSanInclude { + MANDATORY = "mandatory", + OPTIONAL = "optional", + PROHIBIT = "prohibit" +} + +export const SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS = Object.values(CertSubjectAttributeInclude); +export const SAN_INCLUDE_OPTIONS = Object.values(CertSanInclude); export const USAGE_STATES = { REQUIRED: "required", @@ -239,3 +250,27 @@ export const mapTemplateKeyAlgorithmToApi = (templateFormat: string): string => }; return mapping[templateFormat] || templateFormat; }; + +export const TEMPLATE_PRESET_IDS = { + CUSTOM: "custom", + TLS_SERVER: "tls-server", + TLS_CLIENT: "tls-client", + CODE_SIGNING: "code-signing", + DEVICE: "device", + USER: "user", + EMAIL_PROTECTION: "email-protection", + DUAL_PURPOSE_SERVER: "dual-purpose-server" +} as const; + +export type TemplatePresetId = (typeof TEMPLATE_PRESET_IDS)[keyof typeof TEMPLATE_PRESET_IDS]; + +export const ALGORITHM_FAMILIES = { + ECDSA: { + signature: ["SHA256-ECDSA", "SHA384-ECDSA", "SHA512-ECDSA"] as const, + key: ["ECDSA-P256", "ECDSA-P384", "ECDSA-P521"] as const + }, + RSA: { + signature: ["SHA256-RSA", "SHA384-RSA", "SHA512-RSA"] as const, + key: ["RSA-2048", "RSA-3072", "RSA-4096"] as const + } +} as const; diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/schemas.ts b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/schemas.ts index 0590c4160..137ca262c 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/schemas.ts +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/schemas.ts @@ -4,21 +4,22 @@ import { CertDurationUnit, CertExtendedKeyUsageType, CertKeyUsageType, + CertSanInclude, CertSubjectAlternativeNameType, + CertSubjectAttributeInclude, CertSubjectAttributeType, - SAN_INCLUDE_OPTIONS, - SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS + TEMPLATE_PRESET_IDS } from "./certificate-constants"; export const uiAttributeSchema = z.object({ type: z.nativeEnum(CertSubjectAttributeType), - include: z.enum(SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS), + include: z.nativeEnum(CertSubjectAttributeInclude), value: z.array(z.string().min(1, "Value cannot be empty")) }); export const uiSanSchema = z.object({ type: z.nativeEnum(CertSubjectAlternativeNameType), - include: z.enum(SAN_INCLUDE_OPTIONS), + include: z.nativeEnum(CertSanInclude), value: z.array(z.string().min(1, "Value cannot be empty")) }); @@ -51,7 +52,21 @@ export const uiKeyAlgorithmSchema = z.object({ .min(1, "At least one key type must be selected") }); +export const uiPresetSchema = z + .enum([ + TEMPLATE_PRESET_IDS.CUSTOM, + TEMPLATE_PRESET_IDS.TLS_SERVER, + TEMPLATE_PRESET_IDS.TLS_CLIENT, + TEMPLATE_PRESET_IDS.CODE_SIGNING, + TEMPLATE_PRESET_IDS.DEVICE, + TEMPLATE_PRESET_IDS.USER, + TEMPLATE_PRESET_IDS.EMAIL_PROTECTION, + TEMPLATE_PRESET_IDS.DUAL_PURPOSE_SERVER + ]) + .default(TEMPLATE_PRESET_IDS.CUSTOM); + export const templateSchema = z.object({ + preset: uiPresetSchema, name: z .string() .trim() diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/template-presets.ts b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/template-presets.ts new file mode 100644 index 000000000..9edad21ea --- /dev/null +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/template-presets.ts @@ -0,0 +1,385 @@ +import { + ALGORITHM_FAMILIES, + CertDurationUnit, + CertExtendedKeyUsageType, + CertKeyUsageType, + CertSanInclude, + CertSubjectAlternativeNameType, + CertSubjectAttributeInclude, + CertSubjectAttributeType, + TEMPLATE_PRESET_IDS, + type TemplatePresetId +} from "./certificate-constants"; +import { TemplateFormData } from "."; + +export interface CertificateTemplatePreset { + readonly id: TemplatePresetId; + readonly name: string; + readonly description: string; + readonly useCase: string; + readonly formData: Omit; +} + +export const CERTIFICATE_TEMPLATE_PRESETS: CertificateTemplatePreset[] = [ + { + id: TEMPLATE_PRESET_IDS.TLS_SERVER, + name: "TLS Server Certificate", + description: "Standard TLS/SSL server certificate for HTTPS services and API endpoints.", + useCase: "Web servers, API endpoints, HTTPS services", + formData: { + name: "TLS Server Certificate", + description: "Standard TLS/SSL server certificate for HTTPS services and API endpoints.", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], + optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.KEY_ENCIPHERMENT] + }, + extendedKeyUsages: { + requiredUsages: [CertExtendedKeyUsageType.SERVER_AUTH], + optionalUsages: [CertExtendedKeyUsageType.SERVER_AUTH] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.DNS_NAME, + include: CertSanInclude.OPTIONAL, + value: ["*"] + }, + { + type: CertSubjectAlternativeNameType.IP_ADDRESS, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key] + } + } + }, + { + id: TEMPLATE_PRESET_IDS.TLS_CLIENT, + name: "TLS Client Certificate", + description: "Client certificate for mutual TLS authentication and API access.", + useCase: "Client authentication, mTLS, API authentication", + formData: { + name: "TLS Client Certificate", + description: "Client certificate for mutual TLS authentication and API access.", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], + optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.KEY_AGREEMENT] + }, + extendedKeyUsages: { + requiredUsages: [CertExtendedKeyUsageType.CLIENT_AUTH], + optionalUsages: [CertExtendedKeyUsageType.CLIENT_AUTH] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.EMAIL, + include: CertSanInclude.OPTIONAL, + value: ["*"] + }, + { + type: CertSubjectAlternativeNameType.DNS_NAME, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key] + } + } + }, + { + id: TEMPLATE_PRESET_IDS.CODE_SIGNING, + name: "Code Signing Certificate", + description: + "Certificate for signing software, executables, and packages. Requires hardware security modules.", + useCase: "Software signing, executable authentication, package validation", + formData: { + name: "Code Signing Certificate", + description: + "Certificate for signing software, executables, and packages. Requires hardware security modules.", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.NON_REPUDIATION], + optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.NON_REPUDIATION] + }, + extendedKeyUsages: { + requiredUsages: [CertExtendedKeyUsageType.CODE_SIGNING], + optionalUsages: [ + CertExtendedKeyUsageType.CODE_SIGNING, + CertExtendedKeyUsageType.TIME_STAMPING + ] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.EMAIL, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.RSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.RSA.key] + } + } + }, + { + id: TEMPLATE_PRESET_IDS.DEVICE, + name: "Device Certificate", + description: + "Certificate for IoT devices and embedded systems authentication. IEEE 802.1AR compliant.", + useCase: "Device authentication, IoT security, embedded systems", + formData: { + name: "Device Certificate", + description: + "Certificate for IoT devices and embedded systems authentication. IEEE 802.1AR compliant.", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], + optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.KEY_AGREEMENT] + }, + extendedKeyUsages: { + requiredUsages: [CertExtendedKeyUsageType.CLIENT_AUTH], + optionalUsages: [CertExtendedKeyUsageType.CLIENT_AUTH, CertExtendedKeyUsageType.SERVER_AUTH] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.DNS_NAME, + include: CertSanInclude.OPTIONAL, + value: ["*"] + }, + { + type: CertSubjectAlternativeNameType.IP_ADDRESS, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key] + } + } + }, + { + id: TEMPLATE_PRESET_IDS.USER, + name: "User Certificate", + description: + "Personal certificate for user authentication and email signing. FIPS 201 PIV compliant.", + useCase: "Personal authentication, smart cards, email protection", + formData: { + name: "User Certificate", + description: + "Personal certificate for user authentication and email signing. FIPS 201 PIV compliant.", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], + optionalUsages: [ + CertKeyUsageType.DIGITAL_SIGNATURE, + CertKeyUsageType.KEY_ENCIPHERMENT, + CertKeyUsageType.KEY_AGREEMENT + ] + }, + extendedKeyUsages: { + requiredUsages: [ + CertExtendedKeyUsageType.CLIENT_AUTH, + CertExtendedKeyUsageType.EMAIL_PROTECTION + ], + optionalUsages: [ + CertExtendedKeyUsageType.CLIENT_AUTH, + CertExtendedKeyUsageType.EMAIL_PROTECTION + ] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.EMAIL, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key] + } + } + }, + { + id: TEMPLATE_PRESET_IDS.EMAIL_PROTECTION, + name: "Email Protection Certificate", + description: "S/MIME certificate for email encryption and digital signing. RFC 8550 compliant.", + useCase: "Email encryption, digital signing, secure messaging", + formData: { + name: "Email Protection Certificate", + description: + "S/MIME certificate for email encryption and digital signing. RFC 8550 compliant.", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], + optionalUsages: [ + CertKeyUsageType.DIGITAL_SIGNATURE, + CertKeyUsageType.KEY_ENCIPHERMENT, + CertKeyUsageType.KEY_AGREEMENT + ] + }, + extendedKeyUsages: { + requiredUsages: [CertExtendedKeyUsageType.EMAIL_PROTECTION], + optionalUsages: [CertExtendedKeyUsageType.EMAIL_PROTECTION] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.EMAIL, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.RSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.RSA.key] + } + } + }, + { + id: TEMPLATE_PRESET_IDS.DUAL_PURPOSE_SERVER, + name: "Dual-Purpose Server Certificate", + description: + "Certificate for services requiring both server and client authentication capabilities", + useCase: "Microservices, service mesh, dual authentication", + formData: { + name: "Dual-Purpose Server Certificate", + description: + "Certificate for services requiring both server and client authentication capabilities", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], + optionalUsages: [ + CertKeyUsageType.DIGITAL_SIGNATURE, + CertKeyUsageType.KEY_ENCIPHERMENT, + CertKeyUsageType.KEY_AGREEMENT + ] + }, + extendedKeyUsages: { + requiredUsages: [ + CertExtendedKeyUsageType.SERVER_AUTH, + CertExtendedKeyUsageType.CLIENT_AUTH + ], + optionalUsages: [CertExtendedKeyUsageType.SERVER_AUTH, CertExtendedKeyUsageType.CLIENT_AUTH] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.DNS_NAME, + include: CertSanInclude.OPTIONAL, + value: ["*"] + }, + { + type: CertSubjectAlternativeNameType.IP_ADDRESS, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key] + } + } + } +]; diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/LdapConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/LdapConnectionForm.tsx index 21e376c2e..d786a7d75 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/LdapConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/LdapConnectionForm.tsx @@ -4,8 +4,10 @@ import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tab } from "@headlessui/react"; import { zodResolver } from "@hookform/resolvers/zod"; +import { useQuery } from "@tanstack/react-query"; import { z } from "zod"; +import { OrgPermissionCan } from "@app/components/permissions"; import { Button, FormControl, @@ -18,8 +20,11 @@ import { TextArea, Tooltip } from "@app/components/v2"; +import { OrgPermissionSubjects, useSubscription } from "@app/context"; +import { OrgGatewayPermissionActions } from "@app/context/OrgPermissionContext/types"; import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; import { DistinguishedNameRegex, UserPrincipalNameRegex } from "@app/helpers/string"; +import { gatewaysQueryKeys } from "@app/hooks/api"; import { LdapConnectionMethod, LdapConnectionProvider, @@ -84,6 +89,7 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => { defaultValues: appConnection ?? { app: AppConnection.LDAP, method: LdapConnectionMethod.SimpleBind, + gatewayId: null, credentials: { provider: LdapConnectionProvider.ActiveDirectory, url: "", @@ -104,6 +110,8 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => { const selectedProvider = watch("credentials.provider"); const sslEnabled = watch("credentials.url")?.startsWith("ldaps://") ?? false; + const { subscription } = useSubscription(); + const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list()); return ( @@ -114,6 +122,57 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => { }} > {!isUpdate && } + {subscription.gateway && ( + + {(isAllowed) => ( + ( + + +
+ +
+
+
+ )} + /> + )} +
+ )}
{ <> val !== null, { message: "Relay is required" }) +}); + +const formSchemaWithIdentity = baseFormSchema.extend({ + identity: z + .object( + { + id: z.string(), + name: z.string() + }, + { required_error: "Identity is required" } + ) + .nullable() + .refine((val) => val !== null, { message: "Identity is required" }) +}); + +const formSchemaWithToken = baseFormSchema.extend({ + identityToken: z.string().min(1, "Token is required") +}); + +export const GatewayCliSystemdDeploymentMethod = () => { + const { protocol, hostname, port } = window.location; + const portSuffix = port && port !== "80" ? `:${port}` : ""; + const siteURL = `${protocol}//${hostname}${portSuffix}`; + + const navigate = useNavigate({ + from: ROUTE_PATHS.Organization.NetworkingPage.path + }); + + const [autogenerateToken, setAutogenerateToken] = useState(true); + const [step, setStep] = useState<"form" | "command">("form"); + const [name, setName] = useState(""); + const [relay, setRelay] = useState({ id: "_auto", name: "Auto Select Relay" }); + const [identity, setIdentity] = useState(null); + const [identityToken, setIdentityToken] = useState(""); + const [formErrors, setFormErrors] = useState([]); + + const errors = useMemo(() => { + const errorMap: Record = {}; + formErrors.forEach((issue) => { + if (issue.path.length > 0) { + errorMap[String(issue.path[0])] = issue.message; + } + }); + return errorMap; + }, [formErrors]); + + const { data: relays, isPending: isRelaysLoading } = useGetRelays(); + + const { currentOrg } = useOrganization(); + const organizationId = currentOrg?.id || ""; + + const { permission } = useOrgPermission(); + const canCreateToken = permission.can( + OrgPermissionIdentityActions.CreateToken, + OrgPermissionSubjects.Identity + ); + + const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = + useGetIdentityMembershipOrgs({ + organizationId, + limit: 20000 + }); + const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; + + const { mutateAsync: createToken, isPending: isCreatingToken } = + useCreateTokenIdentityTokenAuth(); + const { mutateAsync: addIdentityTokenAuth, isPending: isAddingTokenAuth } = + useAddIdentityTokenAuth(); + const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); + + const handleGenerateCommand = async () => { + setFormErrors([]); + + if (canCreateToken && autogenerateToken) { + const validation = formSchemaWithIdentity.safeParse({ + name, + relay, + identity + }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + + const validatedIdentity = validation.data.identity; + + try { + const { data: identityTokenAuth } = await refetch(); + if (!identityTokenAuth) { + await addIdentityTokenAuth({ + identityId: validatedIdentity.id, + organizationId, + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + createNotification({ + text: "Token authentication has been automatically enabled for the selected identity. By default, it is configured to allow all IP addresses with a default token TTL of 30 days. You can manage these settings in Access Control.", + type: "warning" + }); + } + + const token = await createToken({ + identityId: validatedIdentity.id, + name: `gateway token for ${name} (autogenerated)` + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for the selected identity.", + type: "info" + }); + setStep("command"); + } catch { + setIdentityToken(""); + } + } else { + const validation = formSchemaWithToken.safeParse({ + name, + relay, + identityToken + }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + setStep("command"); + } + }; + + const installCommand = useMemo(() => { + const relayPart = relay?.id !== "_auto" ? ` --relay=${relay?.name || ""}` : ""; + return `sudo infisical gateway systemd install --name=${name}${relayPart} --domain=${siteURL} --token=${identityToken}`; + }, [name, relay, identityToken, siteURL]); + + const startServiceCommand = "sudo systemctl start infisical-gateway"; + + if (step === "command") { + return ( + <> + +
+ + { + navigator.clipboard.writeText(installCommand); + createNotification({ + text: "Installation command copied to clipboard", + type: "info" + }); + }} + className="w-10" + > + + +
+ + +
+ + { + navigator.clipboard.writeText(startServiceCommand); + createNotification({ + text: "Start service command copied to clipboard", + type: "info" + }); + }} + className="w-10" + > + + +
+ + Install the Infisical CLI + + +
+ + + +
+ + ); + } + + return ( + <> + + setName(e.target.value)} + placeholder="Enter gateway name..." + isError={Boolean(errors.name)} + /> + {errors.name &&

{errors.name}

} + + + { + if ((newValue as SingleValue<{ id: string }>)?.id === "_create") { + navigate({ + search: (prev) => ({ ...prev, selectedTab: "relays", action: "deploy-relay" }) + }); + return; + } + + setRelay(newValue as SingleValue<{ id: string; name: string }>); + }} + isLoading={isRelaysLoading} + options={[ + { + id: "_auto", + name: "Auto Select Relay" + }, + { + id: "_create", + name: "Deploy New Relay" + }, + ...(relays || []) + ]} + placeholder="Select relay..." + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.id} + components={{ Option: RelayOption }} + /> + {errors.relay &&

{errors.relay}

} + + {canCreateToken && autogenerateToken ? ( + <> + + + setIdentity( + e as SingleValue<{ + id: string; + name: string; + }> + ) + } + isLoading={isIdentitiesLoading} + placeholder="Select identity..." + options={identityMembershipOrgs.map((membership) => membership.identity)} + getOptionValue={(option) => option.id} + getOptionLabel={(option) => option.name} + /> + {errors.identity &&

{errors.identity}

} + + ) : ( + <> + + setIdentityToken(e.target.value)} + placeholder="Enter identity token..." + isError={Boolean(errors.identityToken)} + /> + {errors.identityToken &&

{errors.identityToken}

} + + )} + + {canCreateToken && ( +
+ { + setAutogenerateToken(Boolean(e)); + }} + id="autogenerate-token" + className="mr-2" + > +
+ Automatically enable token auth and generate a token for identity + + Token authentication will be automatically enabled for the selected identity if + it isn't already configured. By default, it will be configured to allow all + IP addresses with a token TTL of 30 days. You can manage these settings in + Access Control. +
+
A token will automatically be generated to be used with the CLI command. + + } + > + +
+
+
+
+ )} + +
+ + + + +
+ + ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal.tsx index 170ea2eab..dacd08e74 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal.tsx @@ -4,6 +4,7 @@ import { Modal, ModalContent } from "@app/components/v2"; import { GatewayDeploymentMethodSelect } from "@app/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect"; import { GatewayCliDeploymentMethod } from "./GatewayCliDeploymentMethod"; +import { GatewayCliSystemdDeploymentMethod } from "./GatewayCliSystemdDeploymentMethod"; type Props = { isOpen: boolean; @@ -11,7 +12,8 @@ type Props = { }; export const GatewayDeploymentInfoMap = { - cli: { name: "CLI", image: "SSH.png", component: GatewayCliDeploymentMethod } + cli: { name: "CLI", image: "SSH.png", component: GatewayCliDeploymentMethod }, + systemd: { name: "CLI (systemd)", image: "SSH.png", component: GatewayCliSystemdDeploymentMethod } } as const; export type GatewayDeploymentMethod = keyof typeof GatewayDeploymentInfoMap; diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliDeploymentMethod.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliDeploymentMethod.tsx index 86aab570d..87f2ed75b 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliDeploymentMethod.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliDeploymentMethod.tsx @@ -156,15 +156,6 @@ export const RelayCliDeploymentMethod = () => { } }; - const handleIdentityChange = ( - selectedIdentity: SingleValue<{ - id: string; - name: string; - }> - ) => { - setIdentity(selectedIdentity); - }; - const command = useMemo(() => { return `infisical relay start --name=${name} --domain=${siteURL} --host=${host} --token=${identityToken}`; }, [name, siteURL, host, identityToken]); @@ -245,7 +236,7 @@ export const RelayCliDeploymentMethod = () => { - handleIdentityChange( + setIdentity( e as SingleValue<{ id: string; name: string; diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliSystemdDeploymentMethod.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliSystemdDeploymentMethod.tsx new file mode 100644 index 000000000..10e4d9edd --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliSystemdDeploymentMethod.tsx @@ -0,0 +1,362 @@ +import { useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { faCopy, faQuestionCircle, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + Checkbox, + FilterableSelect, + FormLabel, + IconButton, + Input, + ModalClose, + Tooltip +} from "@app/components/v2"; +import { + OrgPermissionIdentityActions, + OrgPermissionSubjects, + useOrganization, + useOrgPermission +} from "@app/context"; +import { + useAddIdentityTokenAuth, + useCreateTokenIdentityTokenAuth, + useGetIdentityMembershipOrgs, + useGetIdentityTokenAuth +} from "@app/hooks/api"; +import { slugSchema } from "@app/lib/schemas"; + +const baseFormSchema = z.object({ + name: slugSchema({ field: "name" }), + host: z.string().min(1, "Host is required") +}); + +const formSchemaWithIdentity = baseFormSchema.extend({ + identity: z + .object( + { + id: z.string(), + name: z.string() + }, + { required_error: "Identity is required" } + ) + .nullable() + .refine((val) => val !== null, { message: "Identity is required" }) +}); + +const formSchemaWithToken = baseFormSchema.extend({ + identityToken: z.string().min(1, "Token is required") +}); + +export const RelayCliSystemdDeploymentMethod = () => { + const { protocol, hostname, port } = window.location; + const portSuffix = port && port !== "80" ? `:${port}` : ""; + const siteURL = `${protocol}//${hostname}${portSuffix}`; + + const [autogenerateToken, setAutogenerateToken] = useState(true); + const [step, setStep] = useState<"form" | "command">("form"); + const [name, setName] = useState(""); + const [host, setHost] = useState(""); + + const [identity, setIdentity] = useState(null); + const [identityToken, setIdentityToken] = useState(""); + const [formErrors, setFormErrors] = useState([]); + + const errors = useMemo(() => { + const errorMap: Record = {}; + formErrors.forEach((issue) => { + if (issue.path.length > 0) { + errorMap[String(issue.path[0])] = issue.message; + } + }); + return errorMap; + }, [formErrors]); + + const { currentOrg } = useOrganization(); + const organizationId = currentOrg?.id || ""; + + const { permission } = useOrgPermission(); + const canCreateToken = permission.can( + OrgPermissionIdentityActions.CreateToken, + OrgPermissionSubjects.Identity + ); + + const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = + useGetIdentityMembershipOrgs({ + organizationId, + limit: 20000 + }); + const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; + + const { mutateAsync: createToken, isPending: isCreatingToken } = + useCreateTokenIdentityTokenAuth(); + const { mutateAsync: addIdentityTokenAuth, isPending: isAddingTokenAuth } = + useAddIdentityTokenAuth(); + const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); + + const handleGenerateCommand = async () => { + setFormErrors([]); + + if (canCreateToken && autogenerateToken) { + const validation = formSchemaWithIdentity.safeParse({ name, host, identity }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + + const validatedIdentity = validation.data.identity; + + try { + const { data: identityTokenAuth } = await refetch(); + if (!identityTokenAuth) { + await addIdentityTokenAuth({ + identityId: validatedIdentity.id, + organizationId, + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + createNotification({ + text: "Token authentication has been automatically enabled for the selected identity. By default, it is configured to allow all IP addresses with a default token TTL of 30 days. You can manage these settings in Access Control.", + type: "warning" + }); + } + + const token = await createToken({ + identityId: validatedIdentity.id, + name: `relay token for ${name} (autogenerated)` + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for the selected identity.", + type: "info" + }); + setStep("command"); + } catch { + setIdentityToken(""); + } + } else { + const validation = formSchemaWithToken.safeParse({ + name, + host, + identityToken + }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + setStep("command"); + } + }; + + const installCommand = useMemo(() => { + return `sudo infisical relay systemd install --name=${name} --domain=${siteURL} --host=${host} --token=${identityToken}`; + }, [name, siteURL, host, identityToken]); + + const startServiceCommand = "sudo systemctl start infisical-relay"; + const enableServiceCommand = "sudo systemctl enable infisical-relay"; + + if (step === "command") { + return ( + <> + +
+ + { + navigator.clipboard.writeText(installCommand); + createNotification({ + text: "Installation command copied to clipboard", + type: "info" + }); + }} + className="w-10" + > + + +
+ + +
+ + { + navigator.clipboard.writeText(startServiceCommand); + createNotification({ + text: "Start service command copied to clipboard", + type: "info" + }); + }} + className="w-10" + > + + +
+
+ + { + navigator.clipboard.writeText(enableServiceCommand); + createNotification({ + text: "Enable service command copied to clipboard", + type: "info" + }); + }} + className="w-10" + > + + +
+ + Install the Infisical CLI + + +
+ + + +
+ + ); + } + + return ( + <> + + setName(e.target.value)} + placeholder="Enter relay name..." + isError={Boolean(errors.name)} + /> + {errors.name &&

{errors.name}

} + + + setHost(e.target.value)} + placeholder="0.0.0.0" + isError={Boolean(errors.host)} + /> + {errors.host &&

{errors.host}

} + + {canCreateToken && autogenerateToken ? ( + <> + + + setIdentity( + e as SingleValue<{ + id: string; + name: string; + }> + ) + } + isLoading={isIdentitiesLoading} + placeholder="Select identity..." + options={identityMembershipOrgs.map((membership) => membership.identity)} + getOptionValue={(option) => option.id} + getOptionLabel={(option) => option.name} + /> + {errors.identity &&

{errors.identity}

} + + ) : ( + <> + + setIdentityToken(e.target.value)} + placeholder="Enter identity token..." + isError={Boolean(errors.identityToken)} + /> + {errors.identityToken &&

{errors.identityToken}

} + + )} + + {canCreateToken && ( +
+ { + setAutogenerateToken(Boolean(e)); + }} + id="autogenerate-token" + className="mr-2" + > +
+ Automatically enable token auth and generate a token for identity + + Token authentication will be automatically enabled for the selected identity if + it isn't already configured. By default, it will be configured to allow all + IP addresses with a token TTL of 30 days. You can manage these settings in + Access Control. +
+
A token will automatically be generated to be used with the CLI command. + + } + > + +
+
+
+
+ )} + +
+ + + + +
+ + ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal.tsx index aab599925..ab4a58a32 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal.tsx @@ -4,6 +4,7 @@ import { Modal, ModalContent } from "@app/components/v2"; import { RelayDeploymentMethodSelect } from "@app/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeploymentMethodSelect"; import { RelayCliDeploymentMethod } from "./RelayCliDeploymentMethod"; +import { RelayCliSystemdDeploymentMethod } from "./RelayCliSystemdDeploymentMethod"; import { RelayTerraformDeploymentMethod } from "./RelayTerraformDeploymentMethod"; type Props = { @@ -13,6 +14,7 @@ type Props = { export const RelayDeploymentInfoMap = { cli: { name: "CLI", image: "SSH.png", component: RelayCliDeploymentMethod }, + systemd: { name: "CLI (systemd)", image: "SSH.png", component: RelayCliSystemdDeploymentMethod }, terraform: { name: "Terraform", image: "Terraform.png", diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayTerraformDeploymentMethod.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayTerraformDeploymentMethod.tsx index a8e1693a5..d9c67d610 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayTerraformDeploymentMethod.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayTerraformDeploymentMethod.tsx @@ -190,15 +190,6 @@ export const RelayTerraformDeploymentMethod = () => { } }; - const handleIdentityChange = ( - selectedIdentity: SingleValue<{ - id: string; - name: string; - }> - ) => { - setIdentity(selectedIdentity); - }; - const terraformCommand = useMemo(() => { return `terraform { required_providers { @@ -365,7 +356,7 @@ resource "aws_eip_association" "eip_assoc" { - handleIdentityChange( + setIdentity( e as SingleValue<{ id: string; name: string; diff --git a/frontend/src/pages/public/ViewSharedSecretByIDPage/components/SecretContainer.tsx b/frontend/src/pages/public/ViewSharedSecretByIDPage/components/SecretContainer.tsx index 1b2e785fe..3193a1bf5 100644 --- a/frontend/src/pages/public/ViewSharedSecretByIDPage/components/SecretContainer.tsx +++ b/frontend/src/pages/public/ViewSharedSecretByIDPage/components/SecretContainer.tsx @@ -13,6 +13,8 @@ import { Button, IconButton } from "@app/components/v2"; import { useTimedReset, useToggle } from "@app/hooks"; import { TViewSharedSecretResponse } from "@app/hooks/api/secretSharing"; +import { SecretShareInfo } from "./SecretShareInfo"; + type Props = { secret: TViewSharedSecretResponse["secret"]; secretKey: string | null; @@ -71,6 +73,7 @@ export const SecretContainer = ({ secret, secretKey: key }: Props) => {
+