From abfc5736fd985f516b4d449716fec2ff5fd1c973 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 1 Jul 2025 02:05:53 +0400 Subject: [PATCH 01/16] docs(api-reference/organizations): document SSO configuration endpoints --- backend/src/ee/routes/v1/ldap-router.ts | 78 ++++++++---- backend/src/ee/routes/v1/oidc-router.ts | 100 ++++++++++----- backend/src/ee/routes/v1/saml-router.ts | 57 ++++++--- backend/src/lib/api-docs/constants.ts | 115 +++++++++++++++++- .../ldap-sso/create-ldap-config.mdx | 4 + .../ldap-sso/get-ldap-config.mdx | 4 + .../ldap-sso/update-ldap-config.mdx | 4 + .../oidc-sso/create-oidc-config.mdx | 4 + .../oidc-sso/get-oidc-config.mdx | 4 + .../oidc-sso/update-oidc-config.mdx | 4 + .../saml-sso/create-saml-config.mdx | 4 + .../saml-sso/get-saml-config.mdx | 4 + .../saml-sso/update-saml-config.mdx | 4 + docs/docs.json | 28 ++++- 14 files changed, 339 insertions(+), 75 deletions(-) create mode 100644 docs/api-reference/endpoints/organizations/ldap-sso/create-ldap-config.mdx create mode 100644 docs/api-reference/endpoints/organizations/ldap-sso/get-ldap-config.mdx create mode 100644 docs/api-reference/endpoints/organizations/ldap-sso/update-ldap-config.mdx create mode 100644 docs/api-reference/endpoints/organizations/oidc-sso/create-oidc-config.mdx create mode 100644 docs/api-reference/endpoints/organizations/oidc-sso/get-oidc-config.mdx create mode 100644 docs/api-reference/endpoints/organizations/oidc-sso/update-oidc-config.mdx create mode 100644 docs/api-reference/endpoints/organizations/saml-sso/create-saml-config.mdx create mode 100644 docs/api-reference/endpoints/organizations/saml-sso/get-saml-config.mdx create mode 100644 docs/api-reference/endpoints/organizations/saml-sso/update-saml-config.mdx diff --git a/backend/src/ee/routes/v1/ldap-router.ts b/backend/src/ee/routes/v1/ldap-router.ts index 57c5736df..7c520e2ab 100644 --- a/backend/src/ee/routes/v1/ldap-router.ts +++ b/backend/src/ee/routes/v1/ldap-router.ts @@ -17,6 +17,7 @@ import { z } from "zod"; import { LdapGroupMapsSchema } from "@app/db/schemas"; import { TLDAPConfig } from "@app/ee/services/ldap-config/ldap-config-types"; import { isValidLdapFilter, searchGroups } from "@app/ee/services/ldap-config/ldap-fns"; +import { ApiDocsTags, LdapSso } from "@app/lib/api-docs"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -132,10 +133,18 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { config: { rateLimit: readLimit }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + hide: false, + tags: [ApiDocsTags.LdapSso], + description: "Get LDAP config", + security: [ + { + bearerAuth: [] + } + ], querystring: z.object({ - organizationId: z.string().trim() + organizationId: z.string().trim().describe(LdapSso.GET_CONFIG.organizationId) }), response: { 200: z.object({ @@ -172,23 +181,32 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { config: { rateLimit: writeLimit }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + hide: false, + tags: [ApiDocsTags.LdapSso], + description: "Create LDAP config", + security: [ + { + bearerAuth: [] + } + ], body: z.object({ - organizationId: z.string().trim(), - isActive: z.boolean(), - url: z.string().trim(), - bindDN: z.string().trim(), - bindPass: z.string().trim(), - uniqueUserAttribute: z.string().trim().default("uidNumber"), - searchBase: z.string().trim(), - searchFilter: z.string().trim().default("(uid={{username}})"), - groupSearchBase: z.string().trim(), + organizationId: z.string().trim().describe(LdapSso.CREATE_CONFIG.organizationId), + isActive: z.boolean().describe(LdapSso.CREATE_CONFIG.isActive), + url: z.string().trim().describe(LdapSso.CREATE_CONFIG.url), + bindDN: z.string().trim().describe(LdapSso.CREATE_CONFIG.bindDN), + bindPass: z.string().trim().describe(LdapSso.CREATE_CONFIG.bindPass), + uniqueUserAttribute: z.string().trim().default("uidNumber").describe(LdapSso.CREATE_CONFIG.uniqueUserAttribute), + searchBase: z.string().trim().describe(LdapSso.CREATE_CONFIG.searchBase), + searchFilter: z.string().trim().default("(uid={{username}})").describe(LdapSso.CREATE_CONFIG.searchFilter), + groupSearchBase: z.string().trim().describe(LdapSso.CREATE_CONFIG.groupSearchBase), groupSearchFilter: z .string() .trim() - .default("(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))"), - caCert: z.string().trim().default("") + .default("(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))") + .describe(LdapSso.CREATE_CONFIG.groupSearchFilter), + caCert: z.string().trim().default("").describe(LdapSso.CREATE_CONFIG.caCert) }), response: { 200: SanitizedLdapConfigSchema @@ -214,23 +232,31 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { config: { rateLimit: writeLimit }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + hide: false, + tags: [ApiDocsTags.LdapSso], + description: "Update LDAP config", + security: [ + { + bearerAuth: [] + } + ], body: z .object({ - isActive: z.boolean(), - url: z.string().trim(), - bindDN: z.string().trim(), - bindPass: z.string().trim(), - uniqueUserAttribute: z.string().trim(), - searchBase: z.string().trim(), - searchFilter: z.string().trim(), - groupSearchBase: z.string().trim(), - groupSearchFilter: z.string().trim(), - caCert: z.string().trim() + isActive: z.boolean().describe(LdapSso.UPDATE_CONFIG.isActive), + url: z.string().trim().describe(LdapSso.UPDATE_CONFIG.url), + bindDN: z.string().trim().describe(LdapSso.UPDATE_CONFIG.bindDN), + bindPass: z.string().trim().describe(LdapSso.UPDATE_CONFIG.bindPass), + uniqueUserAttribute: z.string().trim().describe(LdapSso.UPDATE_CONFIG.uniqueUserAttribute), + searchBase: z.string().trim().describe(LdapSso.UPDATE_CONFIG.searchBase), + searchFilter: z.string().trim().describe(LdapSso.UPDATE_CONFIG.searchFilter), + groupSearchBase: z.string().trim().describe(LdapSso.UPDATE_CONFIG.groupSearchBase), + groupSearchFilter: z.string().trim().describe(LdapSso.UPDATE_CONFIG.groupSearchFilter), + caCert: z.string().trim().describe(LdapSso.UPDATE_CONFIG.caCert) }) .partial() - .merge(z.object({ organizationId: z.string() })), + .merge(z.object({ organizationId: z.string().trim().describe(LdapSso.UPDATE_CONFIG.organizationId) })), response: { 200: SanitizedLdapConfigSchema } diff --git a/backend/src/ee/routes/v1/oidc-router.ts b/backend/src/ee/routes/v1/oidc-router.ts index 1bfc4d696..520a94c52 100644 --- a/backend/src/ee/routes/v1/oidc-router.ts +++ b/backend/src/ee/routes/v1/oidc-router.ts @@ -13,6 +13,7 @@ import { z } from "zod"; import { OidcConfigsSchema } from "@app/db/schemas"; import { OIDCConfigurationType, OIDCJWTSignatureAlgorithm } from "@app/ee/services/oidc/oidc-config-types"; +import { ApiDocsTags, OidcSSo } from "@app/lib/api-docs"; import { getConfig } from "@app/lib/config/env"; import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; @@ -153,10 +154,18 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { config: { rateLimit: readLimit }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + hide: false, + tags: [ApiDocsTags.OidcSso], + description: "Get OIDC config", + security: [ + { + bearerAuth: [] + } + ], querystring: z.object({ - orgSlug: z.string().trim() + orgSlug: z.string().trim().describe(OidcSSo.GET_CONFIG.orgSlug) }), response: { 200: SanitizedOidcConfigSchema.pick({ @@ -200,8 +209,16 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { config: { rateLimit: writeLimit }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + hide: false, + tags: [ApiDocsTags.OidcSso], + description: "Update OIDC config", + security: [ + { + bearerAuth: [] + } + ], body: z .object({ allowedEmailDomains: z @@ -216,19 +233,23 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { .split(",") .map((id) => id.trim()) .join(", "); - }), - discoveryURL: z.string().trim(), - configurationType: z.nativeEnum(OIDCConfigurationType), - issuer: z.string().trim(), - authorizationEndpoint: z.string().trim(), - jwksUri: z.string().trim(), - tokenEndpoint: z.string().trim(), - userinfoEndpoint: z.string().trim(), - clientId: z.string().trim(), - clientSecret: z.string().trim(), - isActive: z.boolean(), - manageGroupMemberships: z.boolean().optional(), - jwtSignatureAlgorithm: z.nativeEnum(OIDCJWTSignatureAlgorithm).optional() + }) + .describe(OidcSSo.UPDATE_CONFIG.allowedEmailDomains), + discoveryURL: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.discoveryURL), + configurationType: z.nativeEnum(OIDCConfigurationType).describe(OidcSSo.UPDATE_CONFIG.configurationType), + issuer: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.issuer), + authorizationEndpoint: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.authorizationEndpoint), + jwksUri: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.jwksUri), + tokenEndpoint: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.tokenEndpoint), + userinfoEndpoint: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.userinfoEndpoint), + clientId: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.clientId), + clientSecret: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.clientSecret), + isActive: z.boolean().describe(OidcSSo.UPDATE_CONFIG.isActive), + manageGroupMemberships: z.boolean().optional().describe(OidcSSo.UPDATE_CONFIG.manageGroupMemberships), + jwtSignatureAlgorithm: z + .nativeEnum(OIDCJWTSignatureAlgorithm) + .optional() + .describe(OidcSSo.UPDATE_CONFIG.jwtSignatureAlgorithm) }) .partial() .merge(z.object({ orgSlug: z.string() })), @@ -267,8 +288,16 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { config: { rateLimit: writeLimit }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + hide: false, + tags: [ApiDocsTags.OidcSso], + description: "Create OIDC config", + security: [ + { + bearerAuth: [] + } + ], body: z .object({ allowedEmailDomains: z @@ -283,23 +312,34 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { .split(",") .map((id) => id.trim()) .join(", "); - }), - configurationType: z.nativeEnum(OIDCConfigurationType), - issuer: z.string().trim().optional().default(""), - discoveryURL: z.string().trim().optional().default(""), - authorizationEndpoint: z.string().trim().optional().default(""), - jwksUri: z.string().trim().optional().default(""), - tokenEndpoint: z.string().trim().optional().default(""), - userinfoEndpoint: z.string().trim().optional().default(""), - clientId: z.string().trim(), - clientSecret: z.string().trim(), - isActive: z.boolean(), - orgSlug: z.string().trim(), - manageGroupMemberships: z.boolean().optional().default(false), + }) + .describe(OidcSSo.CREATE_CONFIG.allowedEmailDomains), + configurationType: z.nativeEnum(OIDCConfigurationType).describe(OidcSSo.CREATE_CONFIG.configurationType), + issuer: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.issuer), + discoveryURL: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.discoveryURL), + authorizationEndpoint: z + .string() + .trim() + .optional() + .default("") + .describe(OidcSSo.CREATE_CONFIG.authorizationEndpoint), + jwksUri: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.jwksUri), + tokenEndpoint: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.tokenEndpoint), + userinfoEndpoint: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.userinfoEndpoint), + clientId: z.string().trim().describe(OidcSSo.CREATE_CONFIG.clientId), + clientSecret: z.string().trim().describe(OidcSSo.CREATE_CONFIG.clientSecret), + isActive: z.boolean().describe(OidcSSo.CREATE_CONFIG.isActive), + orgSlug: z.string().trim().describe(OidcSSo.CREATE_CONFIG.orgSlug), + manageGroupMemberships: z + .boolean() + .optional() + .default(false) + .describe(OidcSSo.CREATE_CONFIG.manageGroupMemberships), jwtSignatureAlgorithm: z .nativeEnum(OIDCJWTSignatureAlgorithm) .optional() .default(OIDCJWTSignatureAlgorithm.RS256) + .describe(OidcSSo.CREATE_CONFIG.jwtSignatureAlgorithm) }) .superRefine((data, ctx) => { if (data.configurationType === OIDCConfigurationType.CUSTOM) { diff --git a/backend/src/ee/routes/v1/saml-router.ts b/backend/src/ee/routes/v1/saml-router.ts index c8395d608..6f220c9cb 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -13,6 +13,7 @@ import { FastifyRequest } from "fastify"; import { z } from "zod"; import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml-config-types"; +import { ApiDocsTags, SamlSso } from "@app/lib/api-docs"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -262,10 +263,18 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { config: { rateLimit: readLimit }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + hide: false, + tags: [ApiDocsTags.SamlSso], + description: "Get SAML config", + security: [ + { + bearerAuth: [] + } + ], querystring: z.object({ - organizationId: z.string().trim() + organizationId: z.string().trim().describe(SamlSso.GET_CONFIG.organizationId) }), response: { 200: z @@ -302,15 +311,23 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { config: { rateLimit: writeLimit }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + hide: false, + tags: [ApiDocsTags.SamlSso], + description: "Create SAML config", + security: [ + { + bearerAuth: [] + } + ], body: z.object({ - organizationId: z.string(), - authProvider: z.nativeEnum(SamlProviders), - isActive: z.boolean(), - entryPoint: z.string(), - issuer: z.string(), - cert: z.string() + organizationId: z.string().trim().describe(SamlSso.CREATE_CONFIG.organizationId), + authProvider: z.nativeEnum(SamlProviders).describe(SamlSso.CREATE_CONFIG.authProvider), + isActive: z.boolean().describe(SamlSso.CREATE_CONFIG.isActive), + entryPoint: z.string().trim().describe(SamlSso.CREATE_CONFIG.entryPoint), + issuer: z.string().trim().describe(SamlSso.CREATE_CONFIG.issuer), + cert: z.string().trim().describe(SamlSso.CREATE_CONFIG.cert) }), response: { 200: SanitizedSamlConfigSchema @@ -341,18 +358,26 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { config: { rateLimit: writeLimit }, - onRequest: verifyAuth([AuthMode.JWT]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + hide: false, + tags: [ApiDocsTags.SamlSso], + description: "Update SAML config", + security: [ + { + bearerAuth: [] + } + ], body: z .object({ - authProvider: z.nativeEnum(SamlProviders), - isActive: z.boolean(), - entryPoint: z.string(), - issuer: z.string(), - cert: z.string() + authProvider: z.nativeEnum(SamlProviders).describe(SamlSso.UPDATE_CONFIG.authProvider), + isActive: z.boolean().describe(SamlSso.UPDATE_CONFIG.isActive), + entryPoint: z.string().trim().describe(SamlSso.UPDATE_CONFIG.entryPoint), + issuer: z.string().trim().describe(SamlSso.UPDATE_CONFIG.issuer), + cert: z.string().trim().describe(SamlSso.UPDATE_CONFIG.cert) }) .partial() - .merge(z.object({ organizationId: z.string() })), + .merge(z.object({ organizationId: z.string().trim().describe(SamlSso.UPDATE_CONFIG.organizationId) })), response: { 200: SanitizedSamlConfigSchema } diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 7c7a6f654..10e9e2718 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -66,7 +66,10 @@ export enum ApiDocsTags { KmsKeys = "KMS Keys", KmsEncryption = "KMS Encryption", KmsSigning = "KMS Signing", - SecretScanning = "Secret Scanning" + SecretScanning = "Secret Scanning", + OidcSso = "OIDC SSO", + SamlSso = "SAML SSO", + LdapSso = "LDAP SSO" } export const GROUPS = { @@ -2650,3 +2653,113 @@ export const SecretScanningConfigs = { content: "The contents of the Secret Scanning Configuration file." } }; + +export const OidcSSo = { + GET_CONFIG: { + orgSlug: "The slug of the organization to get the OIDC config for." + }, + UPDATE_CONFIG: { + orgSlug: "The slug of the organization to update the OIDC config for.", + allowedEmailDomains: + "A list of allowed email domains that users can use to authenticate with. This field is comma separated.", + discoveryURL: "The URL of the OIDC discovery endpoint.", + configurationType: "The configuration type to use for the OIDC configuration.", + issuer: + "The issuer for the OIDC configuration. This is only supported when the OIDC configuration type is set to 'custom'.", + authorizationEndpoint: + "The authorization endpoint to use for OIDC authorization. This is only supported when the OIDC configuration type is set to 'custom'.", + jwksUri: "The URL of the OIDC JWKS endpoint.", + tokenEndpoint: "The token endpoint to use for OIDC token exchange.", + userinfoEndpoint: "The userinfo endpoint to get user information from the OIDC provider.", + clientId: "The client ID to use for OIDC authentication.", + clientSecret: "The client secret to use for OIDC authentication.", + isActive: "Whether the OIDC configuration is active.", + manageGroupMemberships: + "Whether to manage group memberships for the OIDC configuration. If enabled, users will automatically be assigned groups when they sign in or are added to a group in the OIDC provider.", + jwtSignatureAlgorithm: "The algorithm to use for JWT signature verification." + }, + CREATE_CONFIG: { + orgSlug: "The slug of the organization to create the OIDC config for.", + allowedEmailDomains: + "A list of allowed email domains that users can use to authenticate with. This field is comma separated.", + discoveryURL: "The URL of the OIDC discovery endpoint.", + configurationType: "The configuration type to use for the OIDC configuration.", + issuer: + "The issuer for the OIDC configuration. This is only supported when the OIDC configuration type is set to 'custom'.", + authorizationEndpoint: + "The authorization endpoint to use for OIDC authorization. This is only supported when the OIDC configuration type is set to 'custom'.", + jwksUri: "The URL of the OIDC JWKS endpoint.", + tokenEndpoint: "The token endpoint to use for OIDC token exchange.", + userinfoEndpoint: "The userinfo endpoint to get user information from the OIDC provider.", + clientId: "The client ID to use for OIDC authentication.", + clientSecret: "The client secret to use for OIDC authentication.", + isActive: "Whether the OIDC configuration is active.", + manageGroupMemberships: + "Whether to manage group memberships for the OIDC configuration. If enabled, users will automatically be assigned groups when they sign in or are added to a group in the OIDC provider.", + jwtSignatureAlgorithm: "The algorithm to use for JWT signature verification." + } +}; + +export const SamlSso = { + GET_CONFIG: { + organizationId: "The ID of the organization to get the SAML config for." + }, + UPDATE_CONFIG: { + organizationId: "The ID of the organization to update the SAML config for.", + authProvider: "The authentication provider to use for SAML authentication.", + isActive: "Whether the SAML configuration is active.", + entryPoint: + "The entry point for the SAML authentication. This is the URL that the user will be redirected to after they have authenticated with the SAML provider.", + issuer: "The SAML provider issuer URL or entity ID.", + cert: "The certificate to use for SAML authentication." + }, + CREATE_CONFIG: { + organizationId: "The ID of the organization to create the SAML config for.", + authProvider: "The authentication provider to use for SAML authentication.", + isActive: "Whether the SAML configuration is active.", + entryPoint: + "The entry point for the SAML authentication. This is the URL that the user will be redirected to after they have authenticated with the SAML provider.", + issuer: "The SAML provider issuer URL or entity ID.", + cert: "The certificate to use for SAML authentication." + } +}; + +export const LdapSso = { + GET_CONFIG: { + organizationId: "The ID of the organization to get the LDAP config for." + }, + CREATE_CONFIG: { + organizationId: "The ID of the organization to create the LDAP config for.", + isActive: "Whether the LDAP configuration is active.", + url: "The LDAP server to connect to such as `ldap://ldap.your-org.com`, `ldaps://ldap.myorg.com:636` (for connection over SSL/TLS), etc.", + bindDN: + "The distinguished name of object to bind when performing the user search such as `cn=infisical,ou=Users,dc=acme,dc=com`", + bindPass: "The password to use along with Bind DN when performing the user search.", + searchBase: "The base DN to use for the user search such as `ou=Users,dc=acme,dc=com`", + uniqueUserAttribute: + "The attribute to use as the unique identifier of LDAP users such as `sAMAccountName`, `cn`, `uid`, `objectGUID`. If left blank, defaults to uidNumber", + searchFilter: + "Template used to construct the LDAP user search filter such as `(uid={{username}})` uses literal `{{username}}` to have the given username used in the search. The default is `(uid={{username}})` which is compatible with several common directory schemas.", + groupSearchBase: "LDAP search base to use for group membership search such as `ou=Groups,dc=acme,dc=com`", + groupSearchFilter: + " Template used when constructing the group membership query such as `(&(objectClass=posixGroup)(memberUid={{.Username}}))`. The template can access the following context variables: `[UserDN, UserName]`. The default is `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))` which is compatible with several common directory schemas.", + caCert: "The CA certificate to use when verifying the LDAP server certificate." + }, + UPDATE_CONFIG: { + organizationId: "The ID of the organization to update the LDAP config for.", + isActive: "Whether the LDAP configuration is active.", + url: "The LDAP server to connect to such as `ldap://ldap.your-org.com`, `ldaps://ldap.myorg.com:636` (for connection over SSL/TLS), etc.", + bindDN: + "The distinguished name of object to bind when performing the user search such as `cn=infisical,ou=Users,dc=acme,dc=com`", + bindPass: "The password to use along with Bind DN when performing the user search.", + uniqueUserAttribute: + "The attribute to use as the unique identifier of LDAP users such as `sAMAccountName`, `cn`, `uid`, `objectGUID`. If left blank, defaults to uidNumber", + searchFilter: + "Template used to construct the LDAP user search filter such as `(uid={{username}})` uses literal `{{username}}` to have the given username used in the search. The default is `(uid={{username}})` which is compatible with several common directory schemas.", + searchBase: "The base DN to use for the user search such as `ou=Users,dc=acme,dc=com`", + groupSearchBase: "LDAP search base to use for group membership search such as `ou=Groups,dc=acme,dc=com`", + groupSearchFilter: + " Template used when constructing the group membership query such as `(&(objectClass=posixGroup)(memberUid={{.Username}}))`. The template can access the following context variables: `[UserDN, UserName]`. The default is `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))` which is compatible with several common directory schemas.", + caCert: "The CA certificate to use when verifying the LDAP server certificate." + } +}; diff --git a/docs/api-reference/endpoints/organizations/ldap-sso/create-ldap-config.mdx b/docs/api-reference/endpoints/organizations/ldap-sso/create-ldap-config.mdx new file mode 100644 index 000000000..3edabd366 --- /dev/null +++ b/docs/api-reference/endpoints/organizations/ldap-sso/create-ldap-config.mdx @@ -0,0 +1,4 @@ +--- +title: "Create LDAP SSO Config" +openapi: "POST /api/v1/ldap/config" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/organizations/ldap-sso/get-ldap-config.mdx b/docs/api-reference/endpoints/organizations/ldap-sso/get-ldap-config.mdx new file mode 100644 index 000000000..a41669384 --- /dev/null +++ b/docs/api-reference/endpoints/organizations/ldap-sso/get-ldap-config.mdx @@ -0,0 +1,4 @@ +--- +title: "Get LDAP SSO Config" +openapi: "GET /api/v1/ldap/config" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/organizations/ldap-sso/update-ldap-config.mdx b/docs/api-reference/endpoints/organizations/ldap-sso/update-ldap-config.mdx new file mode 100644 index 000000000..ab613728a --- /dev/null +++ b/docs/api-reference/endpoints/organizations/ldap-sso/update-ldap-config.mdx @@ -0,0 +1,4 @@ +--- +title: "Update LDAP SSO Config" +openapi: "PATCH /api/v1/ldap/config" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/organizations/oidc-sso/create-oidc-config.mdx b/docs/api-reference/endpoints/organizations/oidc-sso/create-oidc-config.mdx new file mode 100644 index 000000000..6a86d970c --- /dev/null +++ b/docs/api-reference/endpoints/organizations/oidc-sso/create-oidc-config.mdx @@ -0,0 +1,4 @@ +--- +title: "Create OIDC Config" +openapi: "POST /api/v1/sso/oidc/config" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/organizations/oidc-sso/get-oidc-config.mdx b/docs/api-reference/endpoints/organizations/oidc-sso/get-oidc-config.mdx new file mode 100644 index 000000000..af62c7d0a --- /dev/null +++ b/docs/api-reference/endpoints/organizations/oidc-sso/get-oidc-config.mdx @@ -0,0 +1,4 @@ +--- +title: "Get OIDC Config" +openapi: "GET /api/v1/sso/oidc/config" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/organizations/oidc-sso/update-oidc-config.mdx b/docs/api-reference/endpoints/organizations/oidc-sso/update-oidc-config.mdx new file mode 100644 index 000000000..fdafeaf99 --- /dev/null +++ b/docs/api-reference/endpoints/organizations/oidc-sso/update-oidc-config.mdx @@ -0,0 +1,4 @@ +--- +title: "Update OIDC Config" +openapi: "PATCH /api/v1/sso/oidc/config" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/organizations/saml-sso/create-saml-config.mdx b/docs/api-reference/endpoints/organizations/saml-sso/create-saml-config.mdx new file mode 100644 index 000000000..0a01fb742 --- /dev/null +++ b/docs/api-reference/endpoints/organizations/saml-sso/create-saml-config.mdx @@ -0,0 +1,4 @@ +--- +title: "Create SAML SSO Config" +openapi: "POST /api/v1/sso/config" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/organizations/saml-sso/get-saml-config.mdx b/docs/api-reference/endpoints/organizations/saml-sso/get-saml-config.mdx new file mode 100644 index 000000000..71c00fb24 --- /dev/null +++ b/docs/api-reference/endpoints/organizations/saml-sso/get-saml-config.mdx @@ -0,0 +1,4 @@ +--- +title: "Get SAML SSO Config" +openapi: "GET /api/v1/sso/config" +--- \ No newline at end of file diff --git a/docs/api-reference/endpoints/organizations/saml-sso/update-saml-config.mdx b/docs/api-reference/endpoints/organizations/saml-sso/update-saml-config.mdx new file mode 100644 index 000000000..57067dbd1 --- /dev/null +++ b/docs/api-reference/endpoints/organizations/saml-sso/update-saml-config.mdx @@ -0,0 +1,4 @@ +--- +title: "Update SAML SSO Config" +openapi: "PATCH /api/v1/sso/config" +--- \ No newline at end of file diff --git a/docs/docs.json b/docs/docs.json index ac9d62a7c..404ae79a4 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -849,6 +849,30 @@ { "group": "Organizations", "pages": [ + { + "group": "OIDC SSO", + "pages": [ + "api-reference/endpoints/organizations/oidc-sso/get-oidc-config", + "api-reference/endpoints/organizations/oidc-sso/update-oidc-config", + "api-reference/endpoints/organizations/oidc-sso/create-oidc-config" + ] + }, + { + "group": "LDAP SSO", + "pages": [ + "api-reference/endpoints/organizations/ldap-sso/get-ldap-config", + "api-reference/endpoints/organizations/ldap-sso/update-ldap-config", + "api-reference/endpoints/organizations/ldap-sso/create-ldap-config" + ] + }, + { + "group": "SAML SSO", + "pages": [ + "api-reference/endpoints/organizations/saml-sso/get-saml-config", + "api-reference/endpoints/organizations/saml-sso/update-saml-config", + "api-reference/endpoints/organizations/saml-sso/create-saml-config" + ] + }, "api-reference/endpoints/organizations/memberships", "api-reference/endpoints/organizations/update-membership", "api-reference/endpoints/organizations/delete-membership", @@ -2089,9 +2113,9 @@ "href": "https://infisical.com" }, "api": { - "openapi": "https://app.infisical.com/api/docs/json", + "openapi": "https://db056ef4a0a9.ngrok.app/api/docs/json", "mdx": { - "server": ["https://app.infisical.com", "http://localhost:8080"] + "server": ["http://localhost:8080"] } }, "appearance": { From 13d2cbd8b0b2d463ffa2aa00fac081ceac4afb12 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 1 Jul 2025 02:09:14 +0400 Subject: [PATCH 02/16] Update docs.json --- docs/docs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index 404ae79a4..8d90a1a58 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -2113,9 +2113,9 @@ "href": "https://infisical.com" }, "api": { - "openapi": "https://db056ef4a0a9.ngrok.app/api/docs/json", + "openapi": "https://app.infisical.com/api/docs/json", "mdx": { - "server": ["http://localhost:8080"] + "server": ["https://app.infisical.com", "http://localhost:8080"] } }, "appearance": { From abfe185a5bd48278637025a84165edecce0a1f70 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 2 Jul 2025 22:13:37 +0530 Subject: [PATCH 03/16] feat: added autoplay to loading lottie and fixed tooltip in project select --- frontend/src/components/v2/ContentLoader/ContentLoader.tsx | 2 +- .../ProjectLayout/components/ProjectSelect/ProjectSelect.tsx | 2 +- frontend/src/main.tsx | 2 +- frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx | 2 +- .../components/SecretDropzone/SecretDropzone.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/v2/ContentLoader/ContentLoader.tsx b/frontend/src/components/v2/ContentLoader/ContentLoader.tsx index 3254dd9ff..f60668aec 100644 --- a/frontend/src/components/v2/ContentLoader/ContentLoader.tsx +++ b/frontend/src/components/v2/ContentLoader/ContentLoader.tsx @@ -33,7 +33,7 @@ export const ContentLoader = ({ text, frequency = 2000, className }: Props) => { className )} > - + {text && isTextArray && ( {
- +
{currentWorkspace?.name}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index c2c54be08..e99210352 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -63,7 +63,7 @@ const router = createRouter({ context: { serverConfig: null, queryClient }, defaultPendingComponent: () => (
- +
), defaultNotFoundComponent: NotFoundPage, diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 78582db2d..2af7cada0 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -864,7 +864,7 @@ export const OverviewPage = () => { if (isProjectV3 && visibleEnvs.length > 0 && isOverviewLoading) { return (
- +
); } diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretDropzone/SecretDropzone.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretDropzone/SecretDropzone.tsx index 56d075445..017604fc4 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretDropzone/SecretDropzone.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretDropzone/SecretDropzone.tsx @@ -253,7 +253,7 @@ export const SecretDropzone = ({ > {isLoading ? (
- +
) : (
From 3a17281e3733cea43715899695b0d5dc4d6aa07f Mon Sep 17 00:00:00 2001 From: = Date: Thu, 3 Jul 2025 00:41:47 +0530 Subject: [PATCH 04/16] feat: resolved tooltip overflow --- .../src/components/v2/Tooltip/Tooltip.tsx | 36 ++++++++++--------- .../ProjectSelect/ProjectSelect.tsx | 2 +- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/v2/Tooltip/Tooltip.tsx b/frontend/src/components/v2/Tooltip/Tooltip.tsx index 2eb443b4e..12d4397d9 100644 --- a/frontend/src/components/v2/Tooltip/Tooltip.tsx +++ b/frontend/src/components/v2/Tooltip/Tooltip.tsx @@ -43,23 +43,25 @@ export const Tooltip = ({ onOpenChange={onOpenChange} > {children} - - {content} - - + + + {content} + + + ) : ( // eslint-disable-next-line react/jsx-no-useless-fragment diff --git a/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx b/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx index 6797b8e38..1c2b7f437 100644 --- a/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx +++ b/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx @@ -176,7 +176,7 @@ export const ProjectSelect = () => { >
- +
{workspace.name}
From 7ab67db84d36dd8af41931b9b519d263965f71b1 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 3 Jul 2025 01:18:52 +0530 Subject: [PATCH 05/16] feat: fixed black color in tooltip --- .../ProjectLayout/components/ProjectSelect/ProjectSelect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx b/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx index 1c2b7f437..d4c7a3f56 100644 --- a/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx +++ b/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx @@ -176,7 +176,7 @@ export const ProjectSelect = () => { >
- +
{workspace.name}
From 37d490ede34281741d8cc7066a66f5389bb3a867 Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 3 Jul 2025 00:09:28 -0400 Subject: [PATCH 06/16] Add BitBucket platform to secret scanning --- cli/detect/cmd/scm/scm.go | 4 ++++ cli/detect/git.go | 2 ++ cli/detect/utils.go | 9 +++++++++ 3 files changed, 15 insertions(+) diff --git a/cli/detect/cmd/scm/scm.go b/cli/detect/cmd/scm/scm.go index 66868aadc..dddeffdf5 100644 --- a/cli/detect/cmd/scm/scm.go +++ b/cli/detect/cmd/scm/scm.go @@ -35,6 +35,7 @@ const ( GitHubPlatform GitLabPlatform AzureDevOpsPlatform + BitBucketPlatform // TODO: Add others. ) @@ -45,6 +46,7 @@ func (p Platform) String() string { "github", "gitlab", "azuredevops", + "bitbucket", }[p] } @@ -60,6 +62,8 @@ func PlatformFromString(s string) (Platform, error) { return GitLabPlatform, nil case "azuredevops": return AzureDevOpsPlatform, nil + case "bitbucket": + return BitBucketPlatform, nil default: return UnknownPlatform, fmt.Errorf("invalid scm platform value: %s", s) } diff --git a/cli/detect/git.go b/cli/detect/git.go index ddde0757d..83ed8a853 100644 --- a/cli/detect/git.go +++ b/cli/detect/git.go @@ -208,6 +208,8 @@ func platformFromHost(u *url.URL) scm.Platform { return scm.GitLabPlatform case "dev.azure.com", "visualstudio.com": return scm.AzureDevOpsPlatform + case "bitbucket.org": + return scm.BitBucketPlatform default: return scm.UnknownPlatform } diff --git a/cli/detect/utils.go b/cli/detect/utils.go index 255d01fbe..0ab755319 100644 --- a/cli/detect/utils.go +++ b/cli/detect/utils.go @@ -112,6 +112,15 @@ func createScmLink(scmPlatform scm.Platform, remoteUrl string, finding report.Fi // This is a bit dirty, but Azure DevOps does not highlight the line when the lineStartColumn and lineEndColumn are not provided link += "&lineStartColumn=1&lineEndColumn=10000000&type=2&lineStyle=plain&_a=files" return link + case scm.BitBucketPlatform: + link := fmt.Sprintf("%s/src/%s/%s", remoteUrl, finding.Commit, filePath) + if finding.StartLine != 0 { + link += fmt.Sprintf("#lines-%d", finding.StartLine) + if finding.EndLine != finding.StartLine { + link += fmt.Sprintf(":%d", finding.EndLine) + } + } + return link default: // This should never happen. return "" From 23b20ebdab0da830404e91e02305dfe6698336c4 Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 3 Jul 2025 00:49:31 -0400 Subject: [PATCH 07/16] Fix CLI always defaulting to github --- cli/packages/cmd/scan.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cli/packages/cmd/scan.go b/cli/packages/cmd/scan.go index 42ff0f1e1..4a721d2c5 100644 --- a/cli/packages/cmd/scan.go +++ b/cli/packages/cmd/scan.go @@ -337,9 +337,7 @@ var scanCmd = &cobra.Command{ if gitCmd, err = sources.NewGitLogCmd(source, logOpts); err != nil { logging.Fatal().Err(err).Msg("could not create Git cmd") } - if scmPlatform, err = scm.PlatformFromString("github"); err != nil { - logging.Fatal().Err(err).Send() - } + scmPlatform = scm.UnknownPlatform remote = detect.NewRemoteInfo(scmPlatform, source) if findings, err = detector.DetectGit(gitCmd, remote); err != nil { From 42648a134c07a5a9987e20b2aa12c4fc2612a38a Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 3 Jul 2025 12:47:25 -0400 Subject: [PATCH 08/16] Update utils.go to look more like Gitleaks version --- cli/detect/utils.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/detect/utils.go b/cli/detect/utils.go index 0ab755319..84b1017fc 100644 --- a/cli/detect/utils.go +++ b/cli/detect/utils.go @@ -116,9 +116,9 @@ func createScmLink(scmPlatform scm.Platform, remoteUrl string, finding report.Fi link := fmt.Sprintf("%s/src/%s/%s", remoteUrl, finding.Commit, filePath) if finding.StartLine != 0 { link += fmt.Sprintf("#lines-%d", finding.StartLine) - if finding.EndLine != finding.StartLine { - link += fmt.Sprintf(":%d", finding.EndLine) - } + } + if finding.EndLine != finding.StartLine { + link += fmt.Sprintf(":%d", finding.EndLine) } return link default: From 9cbef2c07b9abb11ebba3c609d1daf51e4b8b212 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Thu, 3 Jul 2025 12:37:28 -0700 Subject: [PATCH 09/16] fix: pass audit log info from import/delete secrets for sync endpoint --- .../routes/v1/secret-sync-routers/secret-sync-endpoints.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts index 6ab9b5939..9db3a2013 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-endpoints.ts @@ -382,7 +382,8 @@ export const registerSyncSecretsEndpoints = Date: Fri, 4 Jul 2025 04:24:15 +0800 Subject: [PATCH 10/16] misc: allow users with create permission to add identities with no access --- .../identity-project-service.ts | 36 +++++++++--------- .../src/services/identity/identity-service.ts | 37 ++++++++++--------- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/backend/src/services/identity-project/identity-project-service.ts b/backend/src/services/identity-project/identity-project-service.ts index f354477cf..7ee051d88 100644 --- a/backend/src/services/identity-project/identity-project-service.ts +++ b/backend/src/services/identity-project/identity-project-service.ts @@ -93,23 +93,25 @@ export const identityProjectServiceFactory = ({ projectId ); - const permissionBoundary = validatePrivilegeChangeOperation( - membership.shouldUseNewPrivilegeSystem, - ProjectPermissionIdentityActions.GrantPrivileges, - ProjectPermissionSub.Identity, - permission, - rolePermission - ); - if (!permissionBoundary.isValid) - throw new PermissionBoundaryError({ - message: constructPermissionErrorMessage( - "Failed to assign to role", - membership.shouldUseNewPrivilegeSystem, - ProjectPermissionIdentityActions.GrantPrivileges, - ProjectPermissionSub.Identity - ), - details: { missingPermissions: permissionBoundary.missingPermissions } - }); + if (requestedRoleChange !== ProjectMembershipRole.NoAccess) { + const permissionBoundary = validatePrivilegeChangeOperation( + membership.shouldUseNewPrivilegeSystem, + ProjectPermissionIdentityActions.GrantPrivileges, + ProjectPermissionSub.Identity, + permission, + rolePermission + ); + if (!permissionBoundary.isValid) + throw new PermissionBoundaryError({ + message: constructPermissionErrorMessage( + "Failed to assign to role", + membership.shouldUseNewPrivilegeSystem, + ProjectPermissionIdentityActions.GrantPrivileges, + ProjectPermissionSub.Identity + ), + details: { missingPermissions: permissionBoundary.missingPermissions } + }); + } } // validate custom roles input diff --git a/backend/src/services/identity/identity-service.ts b/backend/src/services/identity/identity-service.ts index 4ea382f9e..7c76520b3 100644 --- a/backend/src/services/identity/identity-service.ts +++ b/backend/src/services/identity/identity-service.ts @@ -69,23 +69,25 @@ export const identityServiceFactory = ({ orgId ); const isCustomRole = Boolean(customRole); - const permissionBoundary = validatePrivilegeChangeOperation( - membership.shouldUseNewPrivilegeSystem, - OrgPermissionIdentityActions.GrantPrivileges, - OrgPermissionSubjects.Identity, - permission, - rolePermission - ); - if (!permissionBoundary.isValid) - throw new PermissionBoundaryError({ - message: constructPermissionErrorMessage( - "Failed to create identity", - membership.shouldUseNewPrivilegeSystem, - OrgPermissionIdentityActions.GrantPrivileges, - OrgPermissionSubjects.Identity - ), - details: { missingPermissions: permissionBoundary.missingPermissions } - }); + if (role !== OrgMembershipRole.NoAccess) { + const permissionBoundary = validatePrivilegeChangeOperation( + membership.shouldUseNewPrivilegeSystem, + OrgPermissionIdentityActions.GrantPrivileges, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ); + if (!permissionBoundary.isValid) + throw new PermissionBoundaryError({ + message: constructPermissionErrorMessage( + "Failed to create identity", + membership.shouldUseNewPrivilegeSystem, + OrgPermissionIdentityActions.GrantPrivileges, + OrgPermissionSubjects.Identity + ), + details: { missingPermissions: permissionBoundary.missingPermissions } + }); + } const plan = await licenseService.getPlan(orgId); @@ -187,6 +189,7 @@ export const identityServiceFactory = ({ ), details: { missingPermissions: appliedRolePermissionBoundary.missingPermissions } }); + if (isCustomRole) customRole = customOrgRole; } From cda8579ca4238fea5447d5d40d52d0d24e29e497 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 4 Jul 2025 04:51:14 +0400 Subject: [PATCH 11/16] fix: requested changes --- backend/src/ee/routes/v1/oidc-router.ts | 9 ++--- .../ee/services/oidc/oidc-config-service.ts | 40 +++++++------------ .../src/ee/services/oidc/oidc-config-types.ts | 8 ++-- backend/src/lib/api-docs/constants.ts | 36 ++++++++--------- .../src/hooks/api/oidcConfig/mutations.tsx | 16 ++++---- frontend/src/hooks/api/oidcConfig/queries.tsx | 8 ++-- .../components/OrgSsoTab/OIDCModal.tsx | 8 ++-- .../components/OrgSsoTab/OrgOIDCSection.tsx | 6 +-- .../components/OrgSsoTab/OrgSsoTab.tsx | 2 +- 9 files changed, 60 insertions(+), 73 deletions(-) diff --git a/backend/src/ee/routes/v1/oidc-router.ts b/backend/src/ee/routes/v1/oidc-router.ts index 520a94c52..59c05272d 100644 --- a/backend/src/ee/routes/v1/oidc-router.ts +++ b/backend/src/ee/routes/v1/oidc-router.ts @@ -165,7 +165,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { } ], querystring: z.object({ - orgSlug: z.string().trim().describe(OidcSSo.GET_CONFIG.orgSlug) + organizationId: z.string().trim().describe(OidcSSo.GET_CONFIG.organizationId) }), response: { 200: SanitizedOidcConfigSchema.pick({ @@ -189,9 +189,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { - const { orgSlug } = req.query; const oidc = await server.services.oidc.getOidc({ - orgSlug, + organizationId: req.query.organizationId, type: "external", actor: req.permission.type, actorId: req.permission.id, @@ -252,7 +251,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { .describe(OidcSSo.UPDATE_CONFIG.jwtSignatureAlgorithm) }) .partial() - .merge(z.object({ orgSlug: z.string() })), + .merge(z.object({ organizationId: z.string().describe(OidcSSo.UPDATE_CONFIG.organizationId) })), response: { 200: SanitizedOidcConfigSchema.pick({ id: true, @@ -329,7 +328,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { clientId: z.string().trim().describe(OidcSSo.CREATE_CONFIG.clientId), clientSecret: z.string().trim().describe(OidcSSo.CREATE_CONFIG.clientSecret), isActive: z.boolean().describe(OidcSSo.CREATE_CONFIG.isActive), - orgSlug: z.string().trim().describe(OidcSSo.CREATE_CONFIG.orgSlug), + organizationId: z.string().trim().describe(OidcSSo.CREATE_CONFIG.organizationId), manageGroupMemberships: z .boolean() .optional() diff --git a/backend/src/ee/services/oidc/oidc-config-service.ts b/backend/src/ee/services/oidc/oidc-config-service.ts index a5088bde5..1a3374035 100644 --- a/backend/src/ee/services/oidc/oidc-config-service.ts +++ b/backend/src/ee/services/oidc/oidc-config-service.ts @@ -107,34 +107,26 @@ export const oidcConfigServiceFactory = ({ kmsService }: TOidcConfigServiceFactoryDep) => { const getOidc = async (dto: TGetOidcCfgDTO) => { - const org = await orgDAL.findOne({ slug: dto.orgSlug }); - if (!org) { + const oidcCfg = await oidcConfigDAL.findOne({ + orgId: dto.organizationId + }); + if (!oidcCfg) { throw new NotFoundError({ - message: `Organization with slug '${dto.orgSlug}' not found`, - name: "OrgNotFound" + message: `OIDC configuration for organization with ID '${dto.organizationId}' not found` }); } + if (dto.type === "external") { const { permission } = await permissionService.getOrgPermission( dto.actor, dto.actorId, - org.id, + dto.organizationId, dto.actorAuthMethod, dto.actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso); } - const oidcCfg = await oidcConfigDAL.findOne({ - orgId: org.id - }); - - if (!oidcCfg) { - throw new NotFoundError({ - message: `OIDC configuration for organization with slug '${dto.orgSlug}' not found` - }); - } - const { decryptor } = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.Organization, orgId: oidcCfg.orgId @@ -465,7 +457,7 @@ export const oidcConfigServiceFactory = ({ }; const updateOidcCfg = async ({ - orgSlug, + organizationId, allowedEmailDomains, configurationType, discoveryURL, @@ -484,13 +476,11 @@ export const oidcConfigServiceFactory = ({ manageGroupMemberships, jwtSignatureAlgorithm }: TUpdateOidcCfgDTO) => { - const org = await orgDAL.findOne({ - slug: orgSlug - }); + const org = await orgDAL.findOne({ id: organizationId }); if (!org) { throw new NotFoundError({ - message: `Organization with slug '${orgSlug}' not found` + message: `Organization with ID '${organizationId}' not found` }); } @@ -555,7 +545,7 @@ export const oidcConfigServiceFactory = ({ }; const createOidcCfg = async ({ - orgSlug, + organizationId, allowedEmailDomains, configurationType, discoveryURL, @@ -574,12 +564,10 @@ export const oidcConfigServiceFactory = ({ manageGroupMemberships, jwtSignatureAlgorithm }: TCreateOidcCfgDTO) => { - const org = await orgDAL.findOne({ - slug: orgSlug - }); + const org = await orgDAL.findOne({ id: organizationId }); if (!org) { throw new NotFoundError({ - message: `Organization with slug '${orgSlug}' not found` + message: `Organization with ID '${organizationId}' not found` }); } @@ -639,7 +627,7 @@ export const oidcConfigServiceFactory = ({ const oidcCfg = await getOidc({ type: "internal", - orgSlug + organizationId: org.id }); if (!oidcCfg || !oidcCfg.isActive) { diff --git a/backend/src/ee/services/oidc/oidc-config-types.ts b/backend/src/ee/services/oidc/oidc-config-types.ts index c56427e63..f38f2172f 100644 --- a/backend/src/ee/services/oidc/oidc-config-types.ts +++ b/backend/src/ee/services/oidc/oidc-config-types.ts @@ -26,11 +26,11 @@ export type TOidcLoginDTO = { export type TGetOidcCfgDTO = | ({ type: "external"; - orgSlug: string; + organizationId: string; } & TGenericPermission) | { type: "internal"; - orgSlug: string; + organizationId: string; }; export type TCreateOidcCfgDTO = { @@ -45,7 +45,7 @@ export type TCreateOidcCfgDTO = { clientId: string; clientSecret: string; isActive: boolean; - orgSlug: string; + organizationId: string; manageGroupMemberships: boolean; jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm; } & TGenericPermission; @@ -62,7 +62,7 @@ export type TUpdateOidcCfgDTO = Partial<{ clientId: string; clientSecret: string; isActive: boolean; - orgSlug: string; + organizationId: string; manageGroupMemberships: boolean; jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm; }> & diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 10e9e2718..848b0d095 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2656,30 +2656,30 @@ export const SecretScanningConfigs = { export const OidcSSo = { GET_CONFIG: { - orgSlug: "The slug of the organization to get the OIDC config for." + organizationId: "The ID of the organization to get the OIDC config for." }, UPDATE_CONFIG: { - orgSlug: "The slug of the organization to update the OIDC config for.", + organizationId: "The ID of the organization to update the OIDC config for.", allowedEmailDomains: - "A list of allowed email domains that users can use to authenticate with. This field is comma separated.", + "A list of allowed email domains that users can use to authenticate with. This field is comma separated. Example: 'example.com,acme.com'", discoveryURL: "The URL of the OIDC discovery endpoint.", configurationType: "The configuration type to use for the OIDC configuration.", issuer: "The issuer for the OIDC configuration. This is only supported when the OIDC configuration type is set to 'custom'.", authorizationEndpoint: - "The authorization endpoint to use for OIDC authorization. This is only supported when the OIDC configuration type is set to 'custom'.", + "The endpoint to use for OIDC authorization. This is only supported when the OIDC configuration type is set to 'custom'.", jwksUri: "The URL of the OIDC JWKS endpoint.", tokenEndpoint: "The token endpoint to use for OIDC token exchange.", userinfoEndpoint: "The userinfo endpoint to get user information from the OIDC provider.", clientId: "The client ID to use for OIDC authentication.", clientSecret: "The client secret to use for OIDC authentication.", - isActive: "Whether the OIDC configuration is active.", + isActive: "Whether to enable or disable this OIDC configuration.", manageGroupMemberships: - "Whether to manage group memberships for the OIDC configuration. If enabled, users will automatically be assigned groups when they sign in or are added to a group in the OIDC provider.", + "Whether to manage group memberships for the OIDC configuration. If enabled, users will automatically be assigned groups when they sign in, based on which groups they are a member of in the OIDC provider.", jwtSignatureAlgorithm: "The algorithm to use for JWT signature verification." }, CREATE_CONFIG: { - orgSlug: "The slug of the organization to create the OIDC config for.", + organizationId: "The ID of the organization to create the OIDC config for.", allowedEmailDomains: "A list of allowed email domains that users can use to authenticate with. This field is comma separated.", discoveryURL: "The URL of the OIDC discovery endpoint.", @@ -2693,9 +2693,9 @@ export const OidcSSo = { userinfoEndpoint: "The userinfo endpoint to get user information from the OIDC provider.", clientId: "The client ID to use for OIDC authentication.", clientSecret: "The client secret to use for OIDC authentication.", - isActive: "Whether the OIDC configuration is active.", + isActive: "Whether to enable or disable this OIDC configuration.", manageGroupMemberships: - "Whether to manage group memberships for the OIDC configuration. If enabled, users will automatically be assigned groups when they sign in or are added to a group in the OIDC provider.", + "Whether to manage group memberships for the OIDC configuration. If enabled, users will automatically be assigned groups when they sign in, based on which groups they are a member of in the OIDC provider.", jwtSignatureAlgorithm: "The algorithm to use for JWT signature verification." } }; @@ -2707,7 +2707,7 @@ export const SamlSso = { UPDATE_CONFIG: { organizationId: "The ID of the organization to update the SAML config for.", authProvider: "The authentication provider to use for SAML authentication.", - isActive: "Whether the SAML configuration is active.", + isActive: "Whether to enable or disable this SAML configuration.", entryPoint: "The entry point for the SAML authentication. This is the URL that the user will be redirected to after they have authenticated with the SAML provider.", issuer: "The SAML provider issuer URL or entity ID.", @@ -2716,7 +2716,7 @@ export const SamlSso = { CREATE_CONFIG: { organizationId: "The ID of the organization to create the SAML config for.", authProvider: "The authentication provider to use for SAML authentication.", - isActive: "Whether the SAML configuration is active.", + isActive: "Whether to enable or disable this SAML configuration.", entryPoint: "The entry point for the SAML authentication. This is the URL that the user will be redirected to after they have authenticated with the SAML provider.", issuer: "The SAML provider issuer URL or entity ID.", @@ -2730,24 +2730,24 @@ export const LdapSso = { }, CREATE_CONFIG: { organizationId: "The ID of the organization to create the LDAP config for.", - isActive: "Whether the LDAP configuration is active.", + isActive: "Whether to enable or disable this LDAP configuration.", url: "The LDAP server to connect to such as `ldap://ldap.your-org.com`, `ldaps://ldap.myorg.com:636` (for connection over SSL/TLS), etc.", bindDN: - "The distinguished name of object to bind when performing the user search such as `cn=infisical,ou=Users,dc=acme,dc=com`", + "The distinguished name of the object to bind when performing the user search such as `cn=infisical,ou=Users,dc=acme,dc=com`", bindPass: "The password to use along with Bind DN when performing the user search.", searchBase: "The base DN to use for the user search such as `ou=Users,dc=acme,dc=com`", uniqueUserAttribute: "The attribute to use as the unique identifier of LDAP users such as `sAMAccountName`, `cn`, `uid`, `objectGUID`. If left blank, defaults to uidNumber", searchFilter: - "Template used to construct the LDAP user search filter such as `(uid={{username}})` uses literal `{{username}}` to have the given username used in the search. The default is `(uid={{username}})` which is compatible with several common directory schemas.", + "The template used to construct the LDAP user search filter such as `(uid={{username}})` uses literal `{{username}}` to have the given username used in the search. The default is `(uid={{username}})` which is compatible with several common directory schemas.", groupSearchBase: "LDAP search base to use for group membership search such as `ou=Groups,dc=acme,dc=com`", groupSearchFilter: - " Template used when constructing the group membership query such as `(&(objectClass=posixGroup)(memberUid={{.Username}}))`. The template can access the following context variables: `[UserDN, UserName]`. The default is `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))` which is compatible with several common directory schemas.", + "The template used when constructing the group membership query such as `(&(objectClass=posixGroup)(memberUid={{.Username}}))`. The template can access the following context variables: `[UserDN, UserName]`. The default is `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))` which is compatible with several common directory schemas.", caCert: "The CA certificate to use when verifying the LDAP server certificate." }, UPDATE_CONFIG: { organizationId: "The ID of the organization to update the LDAP config for.", - isActive: "Whether the LDAP configuration is active.", + isActive: "Whether to enable or disable this LDAP configuration.", url: "The LDAP server to connect to such as `ldap://ldap.your-org.com`, `ldaps://ldap.myorg.com:636` (for connection over SSL/TLS), etc.", bindDN: "The distinguished name of object to bind when performing the user search such as `cn=infisical,ou=Users,dc=acme,dc=com`", @@ -2755,11 +2755,11 @@ export const LdapSso = { uniqueUserAttribute: "The attribute to use as the unique identifier of LDAP users such as `sAMAccountName`, `cn`, `uid`, `objectGUID`. If left blank, defaults to uidNumber", searchFilter: - "Template used to construct the LDAP user search filter such as `(uid={{username}})` uses literal `{{username}}` to have the given username used in the search. The default is `(uid={{username}})` which is compatible with several common directory schemas.", + "The template used to construct the LDAP user search filter such as `(uid={{username}})` uses literal `{{username}}` to have the given username used in the search. The default is `(uid={{username}})` which is compatible with several common directory schemas.", searchBase: "The base DN to use for the user search such as `ou=Users,dc=acme,dc=com`", groupSearchBase: "LDAP search base to use for group membership search such as `ou=Groups,dc=acme,dc=com`", groupSearchFilter: - " Template used when constructing the group membership query such as `(&(objectClass=posixGroup)(memberUid={{.Username}}))`. The template can access the following context variables: `[UserDN, UserName]`. The default is `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))` which is compatible with several common directory schemas.", + "The template used when constructing the group membership query such as `(&(objectClass=posixGroup)(memberUid={{.Username}}))`. The template can access the following context variables: `[UserDN, UserName]`. The default is `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))` which is compatible with several common directory schemas.", caCert: "The CA certificate to use when verifying the LDAP server certificate." } }; diff --git a/frontend/src/hooks/api/oidcConfig/mutations.tsx b/frontend/src/hooks/api/oidcConfig/mutations.tsx index 4cf4ede93..2150a17bc 100644 --- a/frontend/src/hooks/api/oidcConfig/mutations.tsx +++ b/frontend/src/hooks/api/oidcConfig/mutations.tsx @@ -21,7 +21,7 @@ export const useUpdateOIDCConfig = () => { clientId, clientSecret, isActive, - orgSlug, + organizationId, manageGroupMemberships, jwtSignatureAlgorithm }: { @@ -36,7 +36,7 @@ export const useUpdateOIDCConfig = () => { clientSecret?: string; isActive?: boolean; configurationType?: string; - orgSlug: string; + organizationId: string; manageGroupMemberships?: boolean; jwtSignatureAlgorithm?: OIDCJWTSignatureAlgorithm; }) => { @@ -50,7 +50,7 @@ export const useUpdateOIDCConfig = () => { tokenEndpoint, userinfoEndpoint, clientId, - orgSlug, + organizationId, clientSecret, isActive, manageGroupMemberships, @@ -60,7 +60,7 @@ export const useUpdateOIDCConfig = () => { return data; }, onSuccess(_, dto) { - queryClient.invalidateQueries({ queryKey: oidcConfigKeys.getOIDCConfig(dto.orgSlug) }); + queryClient.invalidateQueries({ queryKey: oidcConfigKeys.getOIDCConfig(dto.organizationId) }); queryClient.invalidateQueries({ queryKey: organizationKeys.getUserOrganizations }); } }); @@ -81,7 +81,7 @@ export const useCreateOIDCConfig = () => { clientId, clientSecret, isActive, - orgSlug, + organizationId, manageGroupMemberships, jwtSignatureAlgorithm }: { @@ -95,7 +95,7 @@ export const useCreateOIDCConfig = () => { clientId: string; clientSecret: string; isActive: boolean; - orgSlug: string; + organizationId: string; allowedEmailDomains?: string; manageGroupMemberships?: boolean; jwtSignatureAlgorithm?: OIDCJWTSignatureAlgorithm; @@ -112,7 +112,7 @@ export const useCreateOIDCConfig = () => { clientId, clientSecret, isActive, - orgSlug, + organizationId, manageGroupMemberships, jwtSignatureAlgorithm }); @@ -120,7 +120,7 @@ export const useCreateOIDCConfig = () => { return data; }, onSuccess(_, dto) { - queryClient.invalidateQueries({ queryKey: oidcConfigKeys.getOIDCConfig(dto.orgSlug) }); + queryClient.invalidateQueries({ queryKey: oidcConfigKeys.getOIDCConfig(dto.organizationId) }); } }); }; diff --git a/frontend/src/hooks/api/oidcConfig/queries.tsx b/frontend/src/hooks/api/oidcConfig/queries.tsx index 38c939520..b9ee943f7 100644 --- a/frontend/src/hooks/api/oidcConfig/queries.tsx +++ b/frontend/src/hooks/api/oidcConfig/queries.tsx @@ -5,18 +5,18 @@ import { apiRequest } from "@app/config/request"; import { OIDCConfigData } from "./types"; export const oidcConfigKeys = { - getOIDCConfig: (orgSlug: string) => [{ orgSlug }, "organization-oidc"] as const, + getOIDCConfig: (orgId: string) => [{ orgId }, "organization-oidc"] as const, getOIDCManageGroupMembershipsEnabled: (orgId: string) => ["oidc-manage-group-memberships", orgId] as const }; -export const useGetOIDCConfig = (orgSlug: string) => { +export const useGetOIDCConfig = (orgId: string) => { return useQuery({ - queryKey: oidcConfigKeys.getOIDCConfig(orgSlug), + queryKey: oidcConfigKeys.getOIDCConfig(orgId), queryFn: async () => { try { const { data } = await apiRequest.get( - `/api/v1/sso/oidc/config?orgSlug=${orgSlug}` + `/api/v1/sso/oidc/config?organizationId=${orgId}` ); return data; diff --git a/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OIDCModal.tsx b/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OIDCModal.tsx index 52df20285..03f946ace 100644 --- a/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OIDCModal.tsx +++ b/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OIDCModal.tsx @@ -105,7 +105,7 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele const { mutateAsync: updateMutateAsync, isPending: updateIsLoading } = useUpdateOIDCConfig(); const [isDeletePopupOpen, setIsDeletePopupOpen] = useToggle(false); - const { data } = useGetOIDCConfig(currentOrg?.slug ?? ""); + const { data } = useGetOIDCConfig(currentOrg?.id ?? ""); const { control, handleSubmit, reset, setValue, watch } = useForm({ resolver: zodResolver(schema), @@ -134,7 +134,7 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele clientId: "", clientSecret: "", isActive: false, - orgSlug: currentOrg.slug + organizationId: currentOrg.id }); createNotification({ @@ -196,7 +196,7 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele clientId, clientSecret, isActive: true, - orgSlug: currentOrg.slug, + organizationId: currentOrg.id, jwtSignatureAlgorithm }); } else { @@ -212,7 +212,7 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele clientId, clientSecret, isActive: true, - orgSlug: currentOrg.slug, + organizationId: currentOrg.id, jwtSignatureAlgorithm }); } diff --git a/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgOIDCSection.tsx b/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgOIDCSection.tsx index 8d5021d53..3956ba894 100644 --- a/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgOIDCSection.tsx +++ b/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgOIDCSection.tsx @@ -21,7 +21,7 @@ export const OrgOIDCSection = (): JSX.Element => { const { currentOrg } = useOrganization(); const { subscription } = useSubscription(); - const { data, isPending } = useGetOIDCConfig(currentOrg?.slug ?? ""); + const { data, isPending } = useGetOIDCConfig(currentOrg?.id ?? ""); const { mutateAsync } = useUpdateOIDCConfig(); const { mutateAsync: updateOrg } = useUpdateOrg(); @@ -41,7 +41,7 @@ export const OrgOIDCSection = (): JSX.Element => { } await mutateAsync({ - orgSlug: currentOrg?.slug, + organizationId: currentOrg?.id, isActive: value }); @@ -114,7 +114,7 @@ export const OrgOIDCSection = (): JSX.Element => { } await mutateAsync({ - orgSlug: currentOrg?.slug, + organizationId: currentOrg?.id, manageGroupMemberships: value }); diff --git a/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgSsoTab.tsx b/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgSsoTab.tsx index ca27b7517..9b11c1302 100644 --- a/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgSsoTab.tsx +++ b/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgSsoTab.tsx @@ -38,7 +38,7 @@ export const OrgSsoTab = withPermission( const { subscription } = useSubscription(); const { data: oidcConfig, isPending: isLoadingOidcConfig } = useGetOIDCConfig( - currentOrg?.slug ?? "" + currentOrg?.id ?? "" ); const { data: samlConfig, isPending: isLoadingSamlConfig } = useGetSSOConfig( currentOrg?.id ?? "" From 65b1354ef15643972969151a003f2913c4d722f5 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 4 Jul 2025 05:07:54 +0400 Subject: [PATCH 12/16] fix: remove undefined return type from get saml endpoint --- backend/src/ee/routes/v1/saml-router.ts | 24 ++++++++---------- .../saml-config/saml-config-service.ts | 12 +++++++-- .../services/saml-config/saml-config-types.ts | 25 ++++++++----------- backend/src/lib/api-docs/constants.ts | 4 +-- 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/backend/src/ee/routes/v1/saml-router.ts b/backend/src/ee/routes/v1/saml-router.ts index 6f220c9cb..461e6a489 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -277,19 +277,17 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { organizationId: z.string().trim().describe(SamlSso.GET_CONFIG.organizationId) }), response: { - 200: z - .object({ - id: z.string(), - organization: z.string(), - orgId: z.string(), - authProvider: z.string(), - isActive: z.boolean(), - entryPoint: z.string(), - issuer: z.string(), - cert: z.string(), - lastUsed: z.date().nullable().optional() - }) - .optional() + 200: z.object({ + id: z.string(), + organization: z.string(), + orgId: z.string(), + authProvider: z.string(), + isActive: z.boolean(), + entryPoint: z.string(), + issuer: z.string(), + cert: z.string(), + lastUsed: z.date().nullable().optional() + }) } }, handler: async (req) => { diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index c81fd518b..2dee8afca 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -148,10 +148,18 @@ export const samlConfigServiceFactory = ({ let samlConfig: TSamlConfigs | undefined; if (dto.type === "org") { samlConfig = await samlConfigDAL.findOne({ orgId: dto.orgId }); - if (!samlConfig) return; + if (!samlConfig) { + throw new NotFoundError({ + message: `Organization with ID '${dto.orgId}' not found` + }); + } } else if (dto.type === "orgSlug") { const org = await orgDAL.findOne({ slug: dto.orgSlug }); - if (!org) return; + if (!org) { + throw new NotFoundError({ + message: `Organization with slug '${dto.orgSlug}' not found` + }); + } samlConfig = await samlConfigDAL.findOne({ orgId: org.id }); } else if (dto.type === "ssoId") { // TODO: diff --git a/backend/src/ee/services/saml-config/saml-config-types.ts b/backend/src/ee/services/saml-config/saml-config-types.ts index a9bd8f485..f4ede04fa 100644 --- a/backend/src/ee/services/saml-config/saml-config-types.ts +++ b/backend/src/ee/services/saml-config/saml-config-types.ts @@ -61,20 +61,17 @@ export type TSamlLoginDTO = { export type TSamlConfigServiceFactory = { createSamlCfg: (arg: TCreateSamlCfgDTO) => Promise; updateSamlCfg: (arg: TUpdateSamlCfgDTO) => Promise; - getSaml: (arg: TGetSamlCfgDTO) => Promise< - | { - id: string; - organization: string; - orgId: string; - authProvider: string; - isActive: boolean; - entryPoint: string; - issuer: string; - cert: string; - lastUsed: Date | null | undefined; - } - | undefined - >; + getSaml: (arg: TGetSamlCfgDTO) => Promise<{ + id: string; + organization: string; + orgId: string; + authProvider: string; + isActive: boolean; + entryPoint: string; + issuer: string; + cert: string; + lastUsed: Date | null | undefined; + }>; samlLogin: (arg: TSamlLoginDTO) => Promise<{ isUserCompleted: boolean; providerAuthToken: string; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 848b0d095..d22f26624 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2706,7 +2706,7 @@ export const SamlSso = { }, UPDATE_CONFIG: { organizationId: "The ID of the organization to update the SAML config for.", - authProvider: "The authentication provider to use for SAML authentication.", + authProvider: "Authentication provider to use for SAML authentication.", isActive: "Whether to enable or disable this SAML configuration.", entryPoint: "The entry point for the SAML authentication. This is the URL that the user will be redirected to after they have authenticated with the SAML provider.", @@ -2715,7 +2715,7 @@ export const SamlSso = { }, CREATE_CONFIG: { organizationId: "The ID of the organization to create the SAML config for.", - authProvider: "The authentication provider to use for SAML authentication.", + authProvider: "Authentication provider to use for SAML authentication.", isActive: "Whether to enable or disable this SAML configuration.", entryPoint: "The entry point for the SAML authentication. This is the URL that the user will be redirected to after they have authenticated with the SAML provider.", From c6f8915d3faa82f631d0030e09099bedb2148f7b Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 4 Jul 2025 05:21:54 +0400 Subject: [PATCH 13/16] Update saml-config-service.ts --- backend/src/ee/services/saml-config/saml-config-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index 2dee8afca..5430b0afc 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -150,7 +150,7 @@ export const samlConfigServiceFactory = ({ samlConfig = await samlConfigDAL.findOne({ orgId: dto.orgId }); if (!samlConfig) { throw new NotFoundError({ - message: `Organization with ID '${dto.orgId}' not found` + message: `SAML configuration for organization with ID '${dto.orgId}' not found` }); } } else if (dto.type === "orgSlug") { From f903e5b3d4fc8dfe5ab6034f69be4e8ab5dee3e9 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 4 Jul 2025 05:23:05 +0400 Subject: [PATCH 14/16] Update saml-router.ts --- backend/src/ee/routes/v1/saml-router.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/ee/routes/v1/saml-router.ts b/backend/src/ee/routes/v1/saml-router.ts index 461e6a489..f8e371d01 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -150,8 +150,8 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { firstName, lastName: lastName as string, relayState: (req.body as { RelayState?: string }).RelayState, - authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string, - orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string, + authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider, + orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId, metadata: userMetadata }); cb(null, { isUserCompleted, providerAuthToken }); From 83dd38db498c6a1a550c1bb01a1b0e245c57f276 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Mon, 7 Jul 2025 08:36:15 -0300 Subject: [PATCH 15/16] feat(telemetry): reduce TELEMETRY_AGGREGATED_KEY_EXP to 10 mins and avoid sending org identitfy events for batch events on sendPostHogEvents --- .../src/services/telemetry/telemetry-service.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/services/telemetry/telemetry-service.ts b/backend/src/services/telemetry/telemetry-service.ts index eaab5bec6..6dbd12ff5 100644 --- a/backend/src/services/telemetry/telemetry-service.ts +++ b/backend/src/services/telemetry/telemetry-service.ts @@ -14,7 +14,7 @@ export const TELEMETRY_SECRET_PROCESSED_KEY = "telemetry-secret-processed"; export const TELEMETRY_SECRET_OPERATIONS_KEY = "telemetry-secret-operations"; export const POSTHOG_AGGREGATED_EVENTS = [PostHogEventTypes.SecretPulled]; -const TELEMETRY_AGGREGATED_KEY_EXP = 900; // 15mins +const TELEMETRY_AGGREGATED_KEY_EXP = 600; // 10mins // Bucket configuration const TELEMETRY_BUCKET_COUNT = 30; @@ -102,13 +102,6 @@ To opt into telemetry, you can set "TELEMETRY_ENABLED=true" within the environme const instanceType = licenseService.getInstanceType(); // capture posthog only when its cloud or signup event happens in self-hosted if (instanceType === InstanceType.Cloud || event.event === PostHogEventTypes.UserSignedUp) { - if (event.organizationId) { - try { - postHog.groupIdentify({ groupType: "organization", groupKey: event.organizationId }); - } catch (error) { - logger.error(error, "Failed to identify PostHog organization"); - } - } if (POSTHOG_AGGREGATED_EVENTS.includes(event.event)) { const eventKey = createTelemetryEventKey(event.event, event.distinctId); await keyStore.setItemWithExpiry( @@ -122,6 +115,13 @@ To opt into telemetry, you can set "TELEMETRY_ENABLED=true" within the environme }) ); } else { + if (event.organizationId) { + try { + postHog.groupIdentify({ groupType: "organization", groupKey: event.organizationId }); + } catch (error) { + logger.error(error, "Failed to identify PostHog organization"); + } + } postHog.capture({ event: event.event, distinctId: event.distinctId, From a678ebb4acf0ce07d70796604e408aa11fc0df2c Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Mon, 7 Jul 2025 10:10:30 -0300 Subject: [PATCH 16/16] Fix Cloud telemetry queue initialization --- backend/src/server/routes/index.ts | 1 + .../src/services/telemetry/telemetry-queue.ts | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 4704449fc..8500e6518 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1911,6 +1911,7 @@ export const registerRoutes = async ( await hsmService.startService(); await telemetryQueue.startTelemetryCheck(); + await telemetryQueue.startAggregatedEventsJob(); await dailyResourceCleanUp.startCleanUp(); await dailyExpiringPkiItemAlert.startSendingAlerts(); await pkiSubscriberQueue.startDailyAutoRenewalJob(); diff --git a/backend/src/services/telemetry/telemetry-queue.ts b/backend/src/services/telemetry/telemetry-queue.ts index 994ebbd38..cc386aebb 100644 --- a/backend/src/services/telemetry/telemetry-queue.ts +++ b/backend/src/services/telemetry/telemetry-queue.ts @@ -71,6 +71,15 @@ export const telemetryQueueServiceFactory = ({ QueueName.TelemetryInstanceStats // just a job id ); + if (postHog) { + await queueService.queue(QueueName.TelemetryInstanceStats, QueueJobs.TelemetryInstanceStats, undefined, { + jobId: QueueName.TelemetryInstanceStats, + repeat: { pattern: "0 0 * * *", utc: true } + }); + } + }; + + const startAggregatedEventsJob = async () => { // clear previous aggregated events job await queueService.stopRepeatableJob( QueueName.TelemetryAggregatedEvents, @@ -80,11 +89,6 @@ export const telemetryQueueServiceFactory = ({ ); if (postHog) { - await queueService.queue(QueueName.TelemetryInstanceStats, QueueJobs.TelemetryInstanceStats, undefined, { - jobId: QueueName.TelemetryInstanceStats, - repeat: { pattern: "0 0 * * *", utc: true } - }); - // Start aggregated events job (runs every five minutes) await queueService.queue(QueueName.TelemetryAggregatedEvents, QueueJobs.TelemetryAggregatedEvents, undefined, { jobId: QueueName.TelemetryAggregatedEvents, @@ -102,6 +106,7 @@ export const telemetryQueueServiceFactory = ({ }); return { - startTelemetryCheck + startTelemetryCheck, + startAggregatedEventsJob }; };