diff --git a/backend/package-lock.json b/backend/package-lock.json index 8f1112cb7b..a871c38b16 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 db94de6819..aa97de2ed4 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 26909d294b..3fa9926018 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 e5a3687a20..efe11c0f2d 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 f09564455a..e7cead989e 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 38e0fc8282..79996841eb 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 f91692a144..de3ce9af6a 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/pki-acme/pki-acme-fns.ts b/backend/src/ee/services/pki-acme/pki-acme-fns.ts index cc7ddb9b13..a5206d0363 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-fns.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-fns.ts @@ -1,6 +1,8 @@ -import { getConfig } from "@app/lib/config/env"; import RE2 from "re2"; import { z } from "zod"; + +import { getConfig } from "@app/lib/config/env"; + import { AcmeAccountDoesNotExistError } from "./pki-acme-errors"; export const buildUrl = (profileId: string, path: string): string => { diff --git a/backend/src/ee/services/pki-acme/pki-acme-service.ts b/backend/src/ee/services/pki-acme/pki-acme-service.ts index dadb809d0f..43da08b1cd 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -12,29 +12,14 @@ import { z, ZodError } from "zod"; import { TPkiAcmeAccounts } from "@app/db/schemas/pki-acme-accounts"; import { TPkiAcmeAuths } from "@app/db/schemas/pki-acme-auths"; import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; +import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { isPrivateIp } from "@app/lib/ip/ipRange"; import { logger } from "@app/lib/logger"; -import { ActorType } from "@app/services/auth/auth-type"; -import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; -import { - EnrollmentType, - TCertificateProfileWithConfigs -} from "@app/services/certificate-profile/certificate-profile-types"; -import { TCertificateV3ServiceFactory } from "@app/services/certificate-v3/certificate-v3-service"; -import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { TProjectDALFactory } from "@app/services/project/project-dal"; -import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns"; - -import { getConfig } from "@app/lib/config/env"; import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; -import { orderCertificate } from "@app/services/certificate-authority/acme/acme-certificate-authority-fns"; -import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; -import { CaType } from "@app/services/certificate-authority/certificate-authority-enums"; -import { TExternalCertificateAuthorityDALFactory } from "@app/services/certificate-authority/external-certificate-authority-dal"; -import { extractCertificateRequestFromCSR } from "@app/services/certificate-common/certificate-csr-utils"; +import { ActorType } from "@app/services/auth/auth-type"; +import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; import { @@ -42,6 +27,21 @@ import { CertKeyUsage, CertSubjectAlternativeNameType } from "@app/services/certificate/certificate-types"; +import { orderCertificate } from "@app/services/certificate-authority/acme/acme-certificate-authority-fns"; +import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; +import { CaType } from "@app/services/certificate-authority/certificate-authority-enums"; +import { TExternalCertificateAuthorityDALFactory } from "@app/services/certificate-authority/external-certificate-authority-dal"; +import { extractCertificateRequestFromCSR } from "@app/services/certificate-common/certificate-csr-utils"; +import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; +import { + EnrollmentType, + TCertificateProfileWithConfigs +} from "@app/services/certificate-profile/certificate-profile-types"; +import { TCertificateV3ServiceFactory } from "@app/services/certificate-v3/certificate-v3-service"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { TProjectDALFactory } from "@app/services/project/project-dal"; +import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns"; + import { TLicenseServiceFactory } from "../license/license-service"; import { TPkiAcmeAccountDALFactory } from "./pki-acme-account-dal"; import { TPkiAcmeAuthDALFactory } from "./pki-acme-auth-dal"; 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 9b1fd14a02..cc96cf327d 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 9702f97d3f..03b5e7f777 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 a0bd293825..7f03194208 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 a4c07b37dc..7b2b09209a 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 3195e670db..5dd7a1c22a 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 ad4777772a..6a32cac20a 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 { z } from "zod"; +// import { z } from "zod"; -import { getConfig } from "@app/lib/config/env"; -import { ForbiddenRequestError } from "@app/lib/errors"; -import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { AuthMode } from "@app/services/auth/auth-type"; -import { logger } from "@app/lib/logger"; -import nock, { Definition } from "nock"; +// 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 4d8b87e603..68099e50e4 100644 --- a/backend/src/server/routes/v1/index.ts +++ b/backend/src/server/routes/v1/index.ts @@ -6,10 +6,9 @@ import { registerCmekRouter } from "@app/server/routes/v1/cmek-router"; import { registerDashboardRouter } from "@app/server/routes/v1/dashboard-router"; import { registerSecretSyncRouter, SECRET_SYNC_REGISTER_ROUTER_MAP } from "@app/server/routes/v1/secret-sync-routers"; -import { getConfig } from "@app/lib/config/env"; 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 +241,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 da63dc32a2..8be6dff1a3 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 67992a503b..270e37ccb7 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 8cb19ba261..7e8fd36a81 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 a43bb59542..1b55ef1962 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 68579f9ba7..49a557582a 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 dd387894ed..910244d9e9 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 2580d01144..c574770bb7 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 d86878bab7..3c03237896 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 fab1bf74cc..5235446de3 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 fa769c6501..c6c51a9e89 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 174e5bce03..644a41a8b2 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 f3b26a2d67..a6ffc56eef 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 2ac58b0706..ac0f1e830d 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 449721a3d3..9c1ea1d515 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 4466d24df1..aa19aef444 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 3637f06dc9..374be92e77 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 ebdfa45e1c..4b5b52c6dd 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 20ef2c0e0c..165a1c59f8 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 936a370bf4..63c635472c 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 57e8fbeaff..26af6db65e 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 99d637dd51..710fc706af 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 4e6cb00782..7c0a5c1cd4 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 1647b38a12..e5c526be5c 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 03005c7d70..961dcff597 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 134b9667bd..54a6335f49 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 ee69b2542e..581fca5604 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 f8d3809491..d82b03c446 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 51a5333959..3ed98f98b4 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 45fb897604..31efafb19d 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 95be757f64..cf0b06c00f 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 37ce0ec118..cdde954e2a 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 da74bd669c..a9e6e113be 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 066258f1ed..8b4f871164 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 f29a2d036f..8d5abadcf5 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 77cc46714a..cf1d35b022 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 9a06b65547..d66c2a7fc3 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 30494e2baa..aafc61daf3 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 0d408ba4fa..006d322178 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 60baa4f5c6..e93aface07 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 eb7f74ecd5..864a4635bc 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 23bffd8596..ce69de0300 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 b9c7597030..e2f0f5f165 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/certificate-authority/acme/acme-certificate-authority-fns.ts b/backend/src/services/certificate-authority/acme/acme-certificate-authority-fns.ts index 52761e6a0a..ff95083c6c 100644 --- a/backend/src/services/certificate-authority/acme/acme-certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/acme/acme-certificate-authority-fns.ts @@ -1,7 +1,9 @@ import * as x509 from "@peculiar/x509"; import acme, { CsrBuffer } from "acme-client"; +import { Knex } from "knex"; import { TableName } from "@app/db/schemas"; +import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, CryptographyError, NotFoundError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; @@ -29,8 +31,6 @@ import { triggerAutoSyncForSubscriber } from "@app/services/pki-sync/pki-sync-ut import { TProjectDALFactory } from "@app/services/project/project-dal"; import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns"; -import { getConfig } from "@app/lib/config/env"; -import { Knex } from "knex"; import { TCertificateAuthorityDALFactory } from "../certificate-authority-dal"; import { CaStatus, CaType } from "../certificate-authority-enums"; import { keyAlgorithmToAlgCfg } from "../certificate-authority-fns"; diff --git a/backend/src/services/certificate-profile/certificate-profile-service.test.ts b/backend/src/services/certificate-profile/certificate-profile-service.test.ts index 8865ffb9ba..1e31d5788a 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.test.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.test.ts @@ -5,16 +5,16 @@ import { ForbiddenError } from "@casl/ability"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import type { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; -import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { ActorType, AuthMethod } from "../auth/auth-type"; +import type { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal"; +import type { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal"; import type { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal"; import type { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal"; import type { TCertificateTemplateV2DALFactory } from "../certificate-template-v2/certificate-template-v2-dal"; -import type { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal"; -import type { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal"; import { TAcmeEnrollmentConfigDALFactory } from "../enrollment-config/acme-enrollment-config-dal"; import type { TApiEnrollmentConfigDALFactory } from "../enrollment-config/api-enrollment-config-dal"; import type { TEstEnrollmentConfigDALFactory } from "../enrollment-config/est-enrollment-config-dal"; diff --git a/backend/src/services/certificate-profile/certificate-profile-service.ts b/backend/src/services/certificate-profile/certificate-profile-service.ts index fe58f958ff..87063c6dae 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.ts @@ -2,6 +2,7 @@ import { ForbiddenError } from "@casl/ability"; import * as x509 from "@peculiar/x509"; import { ActionProjectType } from "@app/db/schemas"; +import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionCertificateActions, @@ -14,14 +15,13 @@ import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; -import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { ActorAuthMethod, ActorType } from "../auth/auth-type"; -import { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal"; -import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal"; -import { TCertificateTemplateV2DALFactory } from "../certificate-template-v2/certificate-template-v2-dal"; import { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal"; import { getCertificateCredentials, isCertChainValid } from "../certificate/certificate-fns"; import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal"; +import { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal"; +import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal"; +import { TCertificateTemplateV2DALFactory } from "../certificate-template-v2/certificate-template-v2-dal"; import { TAcmeEnrollmentConfigDALFactory } from "../enrollment-config/acme-enrollment-config-dal"; import { TApiEnrollmentConfigDALFactory } from "../enrollment-config/api-enrollment-config-dal"; import { TAcmeConfigData, TApiConfigData, TEstConfigData } from "../enrollment-config/enrollment-config-types"; diff --git a/backend/src/services/role/role-service.ts b/backend/src/services/role/role-service.ts index 3387dc96be..653a00b5ce 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 9ee4ca496c..70aff62f45 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 324b78130b..587ac13e87 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 e809647216..44984e1b37 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 c39581fdad..b6f2a77d65 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 69bc224478..2dc178b2ba 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 d528f531e1..daf936a753 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 985d86e8db..bfd2ac9bc7 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 726b5dfacc..468563c894 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 fc511b2d7c..276e3702f2 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 f0d814fb67..66fc2ca822 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 b5698867ac..414014322a 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 c0f1482444..934334eee2 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 09b943d16c..26014f83ba 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 b353f94b43..4d74269d7f 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 875ceaf70a..eeef3e4a7b 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 76bbc63a70..79f7b18f11 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 8797cc98d7..d96d945d81 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 f9096ac711..43c517432b 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 5c9ba570ee..fce757389c 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 cd90ecfdc1..7fcf5e2bcf 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 168ebde3b7..207b7b8cdc 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 cd9a985c92..7a40710ffd 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 55cdeae232..97c6e4c45e 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 56cea04082..2a5bec1abd 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 0bc116255e..266f093f8c 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 633b40dab1..cd888b556d 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 21c09092fa..ed56ae1240 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 359d7f4c58..1254b06eaf 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 84d7a6da48..06cdb6cc14 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 5740e21c97..38fdfda150 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 94a729cb63..09cfbecdf6 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/docs.json b/docs/docs.json index e60978b6f2..aea022fd4b 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -2826,7 +2826,7 @@ "href": "https://infisical.com" }, "api": { - "openapi": "http://localhost:8080/api/docs/json", + "openapi": "https://app.infisical.com/api/docs/json", "mdx": { "server": ["https://app.infisical.com"] } diff --git a/docs/documentation/platform/workflow-integrations/slack-integration.mdx b/docs/documentation/platform/workflow-integrations/slack-integration.mdx index 2317baabee..38fc15ebe7 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 0000000000..ad40c8f4fd 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 0000000000..f7bbf51ae3 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 0b97be58a4..1af15b0748 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 ddf245effa..136cea9d85 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 95e9fe4ba3..31cd0c4585 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 0c047ab1aa..39955f5472 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 502dbde0a6..0000000000 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 54ad6ca6ed..3a200f2965 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 048e91f853..8b9e59eca4 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 97b38d6e45..1554b9bd84 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 166e5849f8..ef492c0187 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 f46f61d894..bf98abd0ef 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 5bd66d9327..5d9316d759 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 1f73865595..9bfbbb7f77 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 3f6bd0d1d5..4864045f31 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 c6dd70ad73..a844988d59 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 944db9ccac..6806527dac 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 073d3fd93c..9367d55a54 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 0000000000..ef6d2ddad9 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 0000000000..121864df62 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 8965ce45e3..e8fdda0965 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 c35228fab3..524346beb5 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/CertificateProfilesTab/CertificateProfilesTab.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CertificateProfilesTab.tsx index 3218187c43..39bb3c4e9a 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CertificateProfilesTab.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CertificateProfilesTab.tsx @@ -1,7 +1,8 @@ +import { useState } from "react"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useState } from "react"; +import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { Button, DeleteActionModal } from "@app/components/v2"; import { useProjectPermission } from "@app/context"; @@ -9,13 +10,12 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; +import { usePopUp } from "@app/hooks"; import { TCertificateProfileWithDetails, useDeleteCertificateProfile } from "@app/hooks/api/certificateProfiles"; -import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; -import { usePopUp } from "@app/hooks"; import { CreateProfileModal } from "./CreateProfileModal"; import { ProfileList } from "./ProfileList"; import { RevealAcmeEabSecretModal } from "./RevealAcmeEabSecretModal"; diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx index 712455ce91..516b642883 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx @@ -1,8 +1,8 @@ +import { useEffect } from "react"; +import { Controller, useForm } from "react-hook-form"; import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useEffect } from "react"; -import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; 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 ec660b3396..33f92a1222 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 b929ce36e5..d55c1b5ef7 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 50b69a2e26..47fdcd5a2a 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 0590c4160f..137ca262c6 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 0000000000..9edad21ea9 --- /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 21e376c2e3..d786a7d758 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 170ea2eab8..dacd08e749 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 86aab570d0..87f2ed75ba 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 0000000000..10e4d9eddc --- /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 aab5999257..ab4a58a32f 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 a8e1693a57..d9c67d6107 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 1b2e785fe1..3193a1bf5b 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) => {
+