From 8d9775c42a67c563e080402b492d2c570c8ae146 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Wed, 12 Nov 2025 21:50:21 +0530 Subject: [PATCH 01/53] feat: adds GET endpoint for single auth token by ID --- .../ee/services/audit-log/audit-log-types.ts | 10 +++ backend/src/lib/api-docs/constants.ts | 4 ++ .../routes/v1/identity-token-auth-router.ts | 61 +++++++++++++++++-- .../identity-token-auth-service.ts | 47 ++++++++++++++ .../identity-token-auth-types.ts | 6 ++ frontend/src/hooks/api/identities/types.ts | 4 +- 6 files changed, 123 insertions(+), 9 deletions(-) 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 bcc2a07703..97dfdb9fad 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -173,6 +173,7 @@ export enum EventType { CREATE_TOKEN_IDENTITY_TOKEN_AUTH = "create-token-identity-token-auth", UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth", GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth", + GET_TOKEN_IDENTITY_TOKEN_AUTH = "get-token-identity-token-auth", CREATE_SUB_ORGANIZATION = "create-sub-organization", UPDATE_SUB_ORGANIZATION = "update-sub-organization", @@ -1013,6 +1014,14 @@ interface GetTokensIdentityTokenAuthEvent { }; } +interface GetTokenIdentityTokenAuthEvent { + type: EventType.GET_TOKEN_IDENTITY_TOKEN_AUTH; + metadata: { + identityId: string; + tokenId: string; + }; +} + interface AddIdentityTokenAuthEvent { type: EventType.ADD_IDENTITY_TOKEN_AUTH; metadata: { @@ -4128,6 +4137,7 @@ export type Event = | CreateTokenIdentityTokenAuthEvent | UpdateTokenIdentityTokenAuthEvent | GetTokensIdentityTokenAuthEvent + | GetTokenIdentityTokenAuthEvent | AddIdentityTokenAuthEvent | UpdateIdentityTokenAuthEvent | GetIdentityTokenAuthEvent diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 927694ef40..09adf2b8b4 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -577,6 +577,10 @@ export const TOKEN_AUTH = { offset: "The offset to start from. If you enter 10, it will start from the 10th token.", limit: "The number of tokens to return." }, + GET_TOKEN: { + identityId: "The ID of the machine identity to get the token for.", + tokenId: "The ID of the token to get metadata for." + }, CREATE_TOKEN: { identityId: "The ID of the machine identity to create the token for.", name: "The name of the token to create." diff --git a/backend/src/server/routes/v1/identity-token-auth-router.ts b/backend/src/server/routes/v1/identity-token-auth-router.ts index aafffdfdb1..9906b6b5d5 100644 --- a/backend/src/server/routes/v1/identity-token-auth-router.ts +++ b/backend/src/server/routes/v1/identity-token-auth-router.ts @@ -312,9 +312,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider response: { 200: z.object({ accessToken: z.string(), - expiresIn: z.coerce.number(), - accessTokenMaxTTL: z.coerce.number(), - tokenType: z.literal("Bearer") + tokenData: IdentityAccessTokensSchema }) } }, @@ -344,9 +342,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider return { accessToken, - tokenType: "Bearer" as const, - expiresIn: identityTokenAuth.accessTokenTTL, - accessTokenMaxTTL: identityTokenAuth.accessTokenMaxTTL + tokenData: identityAccessToken }; } }); @@ -406,6 +402,59 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider } }); + server.route({ + method: "GET", + url: "/token-auth/identities/:identityId/tokens/:tokenId", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.TokenAuth], + description: "Get token for machine identity with Token Auth", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + identityId: z.string().describe(TOKEN_AUTH.GET_TOKEN.identityId), + tokenId: z.string().describe(TOKEN_AUTH.GET_TOKEN.tokenId) + }), + response: { + 200: z.object({ + token: IdentityAccessTokensSchema + }) + } + }, + handler: async (req) => { + const { token, identityMembershipOrg } = await server.services.identityTokenAuth.getTokenAuthTokenById({ + identityId: req.params.identityId, + tokenId: req.params.tokenId, + actor: req.permission.type, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + isActorSuperAdmin: isSuperAdmin(req.auth) + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: identityMembershipOrg.scopeOrgId, + event: { + type: EventType.GET_TOKEN_IDENTITY_TOKEN_AUTH, + metadata: { + identityId: token.identityId, + tokenId: token.id + } + } + }); + + return { token }; + } + }); + server.route({ method: "PATCH", url: "/token-auth/tokens/:tokenId", diff --git a/backend/src/services/identity-token-auth/identity-token-auth-service.ts b/backend/src/services/identity-token-auth/identity-token-auth-service.ts index e6969da611..8c2c90711c 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-service.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-service.ts @@ -31,6 +31,7 @@ import { TAttachTokenAuthDTO, TCreateTokenAuthTokenDTO, TGetTokenAuthDTO, + TGetTokenAuthTokenByIdDTO, TGetTokenAuthTokensDTO, TRevokeTokenAuthDTO, TRevokeTokenAuthTokenDTO, @@ -499,6 +500,51 @@ export const identityTokenAuthServiceFactory = ({ return { tokens, identityMembershipOrg }; }; + const getTokenAuthTokenById = async ({ + tokenId, + identityId, + isActorSuperAdmin, + actorId, + actor, + actorAuthMethod, + actorOrgId + }: TGetTokenAuthTokenByIdDTO) => { + await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin); + + const identityMembershipOrg = await membershipIdentityDAL.getIdentityById({ + scopeData: { + scope: AccessScope.Organization, + orgId: actorOrgId + }, + identityId + }); + if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { + throw new BadRequestError({ + message: "The identity does not have Token Auth" + }); + } + const { permission } = await permissionService.getOrgPermission({ + scope: OrganizationActionScope.Any, + actor, + actorId, + orgId: identityMembershipOrg.scopeOrgId, + actorAuthMethod, + actorOrgId + }); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); + + const token = await identityAccessTokenDAL.findOne({ + [`${TableName.IdentityAccessToken}.id` as "id"]: tokenId, + [`${TableName.IdentityAccessToken}.authMethod` as "authMethod"]: IdentityAuthMethod.TOKEN_AUTH + }); + + if (!token) throw new NotFoundError({ message: `Token with ID ${tokenId} not found` }); + + return { token, identityMembershipOrg }; + }; + const updateTokenAuthToken = async ({ tokenId, name, @@ -642,6 +688,7 @@ export const identityTokenAuthServiceFactory = ({ revokeIdentityTokenAuth, createTokenAuthToken, getTokenAuthTokens, + getTokenAuthTokenById, updateTokenAuthToken, revokeTokenAuthToken }; diff --git a/backend/src/services/identity-token-auth/identity-token-auth-types.ts b/backend/src/services/identity-token-auth/identity-token-auth-types.ts index 16cd60db77..fdecc6d4c9 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-types.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-types.ts @@ -40,6 +40,12 @@ export type TGetTokenAuthTokensDTO = { isActorSuperAdmin?: boolean; } & Omit; +export type TGetTokenAuthTokenByIdDTO = { + tokenId: string; + identityId: string; + isActorSuperAdmin?: boolean; +} & Omit; + export type TUpdateTokenAuthTokenDTO = { tokenId: string; name?: string; diff --git a/frontend/src/hooks/api/identities/types.ts b/frontend/src/hooks/api/identities/types.ts index a0eb828e8a..b7be75e1c1 100644 --- a/frontend/src/hooks/api/identities/types.ts +++ b/frontend/src/hooks/api/identities/types.ts @@ -765,9 +765,7 @@ export type CreateTokenIdentityTokenAuthDTO = { export type CreateTokenIdentityTokenAuthRes = { accessToken: string; - tokenType: string; - expiresIn: number; - accessTokenMaxTTL: number; + tokenData: IdentityAccessToken; }; export type UpdateTokenIdentityTokenAuthDTO = { From 4091dc53a570faa5f6a5cf8eff53c55bb367fa79 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Wed, 12 Nov 2025 23:00:58 +0530 Subject: [PATCH 02/53] refactor: simplify token retrieval by using findById method --- .../identity-token-auth/identity-token-auth-service.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/backend/src/services/identity-token-auth/identity-token-auth-service.ts b/backend/src/services/identity-token-auth/identity-token-auth-service.ts index 8c2c90711c..1ece312ea6 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-service.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-service.ts @@ -535,10 +535,7 @@ export const identityTokenAuthServiceFactory = ({ }); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); - const token = await identityAccessTokenDAL.findOne({ - [`${TableName.IdentityAccessToken}.id` as "id"]: tokenId, - [`${TableName.IdentityAccessToken}.authMethod` as "authMethod"]: IdentityAuthMethod.TOKEN_AUTH - }); + const token = await identityAccessTokenDAL.findById(tokenId); if (!token) throw new NotFoundError({ message: `Token with ID ${tokenId} not found` }); From a76e323b6cf4528aea3b5769624e2ddbe36620ba Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Wed, 12 Nov 2025 23:27:19 +0530 Subject: [PATCH 03/53] fix: identityAccessTokenDALFactory.findOne dal function --- .../identity-access-token/identity-access-token-dal.ts | 1 - .../identity-token-auth/identity-token-auth-service.ts | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/services/identity-access-token/identity-access-token-dal.ts b/backend/src/services/identity-access-token/identity-access-token-dal.ts index ffdb786459..74b624a7e1 100644 --- a/backend/src/services/identity-access-token/identity-access-token-dal.ts +++ b/backend/src/services/identity-access-token/identity-access-token-dal.ts @@ -18,7 +18,6 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => { .where(filter) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`) .select(selectAllTableCols(TableName.IdentityAccessToken)) - .select(db.ref("name").withSchema(TableName.Identity)) .select(db.ref("orgId").withSchema(TableName.Identity).as("identityScopeOrgId")) .first(); diff --git a/backend/src/services/identity-token-auth/identity-token-auth-service.ts b/backend/src/services/identity-token-auth/identity-token-auth-service.ts index 1ece312ea6..97717c4f31 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-service.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-service.ts @@ -535,7 +535,11 @@ export const identityTokenAuthServiceFactory = ({ }); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); - const token = await identityAccessTokenDAL.findById(tokenId); + const token = await identityAccessTokenDAL.findOne({ + [`${TableName.IdentityAccessToken}.id` as "id"]: tokenId, + [`${TableName.IdentityAccessToken}.authMethod` as "authMethod"]: IdentityAuthMethod.TOKEN_AUTH, + [`${TableName.IdentityAccessToken}.identityId` as "identityId"]: identityId + }); if (!token) throw new NotFoundError({ message: `Token with ID ${tokenId} not found` }); From 10a2d7e9ae830a4900b1d6477cd5fc425f54a815 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Fri, 14 Nov 2025 19:34:33 +0530 Subject: [PATCH 04/53] chore: unify license key env variables --- .../src/ee/services/license/license-fns.ts | 38 ++++++++++++++++++- .../ee/services/license/license-service.ts | 16 +++++--- .../src/ee/services/license/license-types.ts | 10 +++++ backend/src/server/routes/v1/admin-router.ts | 6 ++- .../offline-usage-report-service.ts | 10 +++-- docs/self-hosting/ee.mdx | 15 ++++---- 6 files changed, 76 insertions(+), 19 deletions(-) diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index 14b7bcfbd8..fca2455b0b 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -7,7 +7,43 @@ import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { UserAliasType } from "@app/services/user-alias/user-alias-types"; -import { TFeatureSet } from "./license-types"; +import { TFeatureSet, TLicenseKeyConfig, TOfflineLicenseContents } from "./license-types"; + +const getOfflineLicenseContents = (licenseKey: string): TOfflineLicenseContents => { + return JSON.parse(Buffer.from(licenseKey, "base64").toString("utf8")) as TOfflineLicenseContents; +}; + +export const isOfflineLicenseKey = (licenseKey: string): boolean => { + const contents = getOfflineLicenseContents(licenseKey); + return "signature" in contents && "license" in contents; +}; + +export const getLicenseKeyConfig = (): TLicenseKeyConfig => { + const cfg = getConfig(); + + const licenseKey = cfg.LICENSE_KEY; + + if (licenseKey) { + if (isOfflineLicenseKey(licenseKey)) { + return { isValid: true, licenseKey, type: "offline" }; + } + + return { isValid: true, licenseKey, type: "online" }; + } + + const offlineLicenseKey = cfg.LICENSE_KEY_OFFLINE; + + // backwards compatibility + if (offlineLicenseKey) { + if (isOfflineLicenseKey(offlineLicenseKey)) { + return { isValid: true, licenseKey: offlineLicenseKey, type: "offline" }; + } + + return { isValid: false }; + } + + return { isValid: false }; +}; export const getDefaultOnPremFeatures = (): TFeatureSet => ({ _id: null, diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index bbd6147ed3..04e77f2594 100644 --- a/backend/src/ee/services/license/license-service.ts +++ b/backend/src/ee/services/license/license-service.ts @@ -22,7 +22,7 @@ import { OrgPermissionBillingActions, OrgPermissionSubjects } from "../permissio import { TPermissionServiceFactory } from "../permission/permission-service-types"; import { BillingPlanRows, BillingPlanTableHead } from "./licence-enums"; import { TLicenseDALFactory } from "./license-dal"; -import { getDefaultOnPremFeatures, setupLicenseRequestWithStore } from "./license-fns"; +import { getDefaultOnPremFeatures, getLicenseKeyConfig, setupLicenseRequestWithStore } from "./license-fns"; import { InstanceType, TAddOrgPmtMethodDTO, @@ -77,6 +77,7 @@ export const licenseServiceFactory = ({ let instanceType = InstanceType.OnPrem; let onPremFeatures: TFeatureSet = getDefaultOnPremFeatures(); let selfHostedLicense: TOfflineLicense | null = null; + const licenseKeyConfig = getLicenseKeyConfig(); const licenseServerCloudApi = setupLicenseRequestWithStore( envConfig.LICENSE_SERVER_URL || "", @@ -85,10 +86,13 @@ export const licenseServiceFactory = ({ envConfig.INTERNAL_REGION ); + const onlineLicenseKey = + licenseKeyConfig.isValid && licenseKeyConfig.type === "online" ? licenseKeyConfig.licenseKey : ""; + const licenseServerOnPremApi = setupLicenseRequestWithStore( envConfig.LICENSE_SERVER_URL || "", LICENSE_SERVER_ON_PREM_LOGIN, - envConfig.LICENSE_KEY || "", + onlineLicenseKey, envConfig.INTERNAL_REGION ); @@ -131,7 +135,7 @@ export const licenseServiceFactory = ({ return; } - if (envConfig.LICENSE_KEY) { + if (licenseKeyConfig.isValid && licenseKeyConfig.type === "online") { const token = await licenseServerOnPremApi.refreshLicense(); if (token) { await syncLicenseKeyOnPremFeatures(true); @@ -142,10 +146,10 @@ export const licenseServiceFactory = ({ return; } - if (envConfig.LICENSE_KEY_OFFLINE) { + if (licenseKeyConfig.isValid && licenseKeyConfig.type === "offline") { let isValidOfflineLicense = true; const contents: TOfflineLicenseContents = JSON.parse( - Buffer.from(envConfig.LICENSE_KEY_OFFLINE, "base64").toString("utf8") + Buffer.from(licenseKeyConfig.licenseKey, "base64").toString("utf8") ); const isVerified = await verifyOfflineLicense(JSON.stringify(contents.license), contents.signature); @@ -184,7 +188,7 @@ export const licenseServiceFactory = ({ }; const initializeBackgroundSync = async () => { - if (envConfig.LICENSE_KEY) { + if (licenseKeyConfig?.isValid && licenseKeyConfig?.type === "online") { logger.info("Setting up background sync process for refresh onPremFeatures"); const job = new CronJob("*/10 * * * *", syncLicenseKeyOnPremFeatures); job.start(); diff --git a/backend/src/ee/services/license/license-types.ts b/backend/src/ee/services/license/license-types.ts index 5157b0730d..1fd11fabf4 100644 --- a/backend/src/ee/services/license/license-types.ts +++ b/backend/src/ee/services/license/license-types.ts @@ -136,3 +136,13 @@ export type TDelOrgTaxIdDTO = TOrgPermission & { taxId: string }; export type TOrgInvoiceDTO = TOrgPermission; export type TOrgLicensesDTO = TOrgPermission; + +export type TLicenseKeyConfig = + | { + isValid: false; + } + | { + isValid: true; + licenseKey: string; + type: "offline" | "online"; + }; diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index ddb3f23264..8586336429 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -9,6 +9,7 @@ import { SuperAdminSchema, UsersSchema } from "@app/db/schemas"; +import { getLicenseKeyConfig } from "@app/ee/services/license/license-fns"; import { getConfig, overridableKeys } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError } from "@app/lib/errors"; @@ -65,6 +66,9 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { const config = await getServerCfg(); const serverEnvs = getConfig(); + const licenseKeyConfig = getLicenseKeyConfig(); + const hasOfflineLicense = licenseKeyConfig.isValid && licenseKeyConfig.type === "offline"; + return { config: { ...config, @@ -73,7 +77,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { isSecretScanningDisabled: serverEnvs.DISABLE_SECRET_SCANNING, kubernetesAutoFetchServiceAccountToken: serverEnvs.KUBERNETES_AUTO_FETCH_SERVICE_ACCOUNT_TOKEN, paramsFolderSecretDetectionEnabled: serverEnvs.PARAMS_FOLDER_SECRET_DETECTION_ENABLED, - isOfflineUsageReportsEnabled: !!serverEnvs.LICENSE_KEY_OFFLINE + isOfflineUsageReportsEnabled: hasOfflineLicense } }; } diff --git a/backend/src/services/offline-usage-report/offline-usage-report-service.ts b/backend/src/services/offline-usage-report/offline-usage-report-service.ts index 179232aa4a..92360ca52d 100644 --- a/backend/src/services/offline-usage-report/offline-usage-report-service.ts +++ b/backend/src/services/offline-usage-report/offline-usage-report-service.ts @@ -1,7 +1,7 @@ import crypto from "crypto"; +import { getLicenseKeyConfig } from "@app/ee/services/license/license-fns"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; import { TOfflineUsageReportDALFactory } from "./offline-usage-report-dal"; @@ -30,10 +30,12 @@ export const offlineUsageReportServiceFactory = ({ }; const generateUsageReportCSV = async () => { - const cfg = getConfig(); - if (!cfg.LICENSE_KEY_OFFLINE) { + const licenseKeyConfig = getLicenseKeyConfig(); + const hasOfflineLicense = licenseKeyConfig.isValid && licenseKeyConfig.type === "offline"; + + if (!hasOfflineLicense) { throw new BadRequestError({ - message: "Offline usage reports are not enabled. LICENSE_KEY_OFFLINE must be configured." + message: "Offline usage reports are not enabled. An offline license must be configured in LICENSE_KEY." }); } diff --git a/docs/self-hosting/ee.mdx b/docs/self-hosting/ee.mdx index 83b2772e49..ea32c416ce 100644 --- a/docs/self-hosting/ee.mdx +++ b/docs/self-hosting/ee.mdx @@ -14,14 +14,13 @@ This guide walks through how you can use these paid features on a self-hosted in Once purchased, you will be issued a license key. - Depending on whether or not the environment where Infisical is deployed has internet access, you may be issued a regular license or an offline license. + Assign the issued license key to the `LICENSE_KEY` environment variable in your Infisical instance. The system will automatically detect whether the license is online or offline. - - Assign the issued license key to the `LICENSE_KEY` environment variable in your Infisical instance. - - Your Infisical instance will need to communicate with the Infisical license server to validate the license key. + - Your Infisical instance will need to communicate with the Infisical license server to validate the license key. If you want to limit outgoing connections only to the Infisical license server, you can use the following IP addresses: `13.248.249.247` and `35.71.190.59` @@ -29,16 +28,18 @@ This guide walks through how you can use these paid features on a self-hosted in - - Assign the issued license key to the `LICENSE_KEY_OFFLINE` environment variable in your Infisical instance. + - Assign the issued offline license key to the `LICENSE_KEY` environment variable in your Infisical instance. + + - The system will automatically detect that it's an offline license based on the key format. - How you set the environment variable will depend on the deployment method you used. Please refer to the documentation of your deployment method for specific instructions. + Backwards Compatibility: The `LICENSE_KEY_OFFLINE` environment variable is still supported for backwards compatibility, but we recommend using `LICENSE_KEY` for all license types going forward. - Once your instance starts up, the license key will be validated and you’ll be able to use the paid features. + Once your instance starts up, the license key will be validated and you'll be able to use the paid features. However, when the license expires, Infisical will continue to run, but EE features will be disabled until the license is renewed or a new one is purchased. - + From c2bdd12dd56890dbab41d0d32a56c2fe99965ab9 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Fri, 14 Nov 2025 20:37:44 +0530 Subject: [PATCH 05/53] refactor: enhance license key handling and error management --- .../src/ee/services/license/license-fns.ts | 25 ++++++++++++------- .../ee/services/license/license-service.ts | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index fca2455b0b..4231ba3c23 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -1,7 +1,7 @@ import axios, { AxiosError } from "axios"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { getConfig } from "@app/lib/config/env"; +import { getConfig, TEnvConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -9,17 +9,24 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types"; import { TFeatureSet, TLicenseKeyConfig, TOfflineLicenseContents } from "./license-types"; -const getOfflineLicenseContents = (licenseKey: string): TOfflineLicenseContents => { - return JSON.parse(Buffer.from(licenseKey, "base64").toString("utf8")) as TOfflineLicenseContents; -}; - export const isOfflineLicenseKey = (licenseKey: string): boolean => { - const contents = getOfflineLicenseContents(licenseKey); - return "signature" in contents && "license" in contents; + try { + const contents = JSON.parse(Buffer.from(licenseKey, "base64").toString("utf8")) as TOfflineLicenseContents; + + return "signature" in contents && "license" in contents; + } catch (error) { + return false; + } }; -export const getLicenseKeyConfig = (): TLicenseKeyConfig => { - const cfg = getConfig(); +export const getLicenseKeyConfig = ( + config?: Pick +): TLicenseKeyConfig => { + const cfg = config || getConfig(); + + if (!cfg) { + return { isValid: false }; + } const licenseKey = cfg.LICENSE_KEY; diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index 04e77f2594..080556ca57 100644 --- a/backend/src/ee/services/license/license-service.ts +++ b/backend/src/ee/services/license/license-service.ts @@ -77,7 +77,7 @@ export const licenseServiceFactory = ({ let instanceType = InstanceType.OnPrem; let onPremFeatures: TFeatureSet = getDefaultOnPremFeatures(); let selfHostedLicense: TOfflineLicense | null = null; - const licenseKeyConfig = getLicenseKeyConfig(); + const licenseKeyConfig = getLicenseKeyConfig(envConfig); const licenseServerCloudApi = setupLicenseRequestWithStore( envConfig.LICENSE_SERVER_URL || "", From 7d6f52d2aed7a74918556f2c18c1468896e181f6 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Mon, 17 Nov 2025 20:48:57 +0530 Subject: [PATCH 06/53] feat: add identityName to audit log event and enhance identity token response structure --- backend/src/ee/services/audit-log/audit-log-types.ts | 1 + backend/src/server/routes/v1/identity-token-auth-router.ts | 7 +++++++ 2 files changed, 8 insertions(+) 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 021da107fa..06fdeebcff 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -1034,6 +1034,7 @@ interface GetTokenIdentityTokenAuthEvent { type: EventType.GET_TOKEN_IDENTITY_TOKEN_AUTH; metadata: { identityId: string; + identityName: string; tokenId: string; }; } diff --git a/backend/src/server/routes/v1/identity-token-auth-router.ts b/backend/src/server/routes/v1/identity-token-auth-router.ts index 9906b6b5d5..d7cd86330b 100644 --- a/backend/src/server/routes/v1/identity-token-auth-router.ts +++ b/backend/src/server/routes/v1/identity-token-auth-router.ts @@ -312,6 +312,9 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider response: { 200: z.object({ accessToken: z.string(), + expiresIn: z.coerce.number(), + accessTokenMaxTTL: z.coerce.number(), + tokenType: z.literal("Bearer"), tokenData: IdentityAccessTokensSchema }) } @@ -342,6 +345,9 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider return { accessToken, + tokenType: "Bearer" as const, + expiresIn: identityTokenAuth.accessTokenTTL, + accessTokenMaxTTL: identityTokenAuth.accessTokenMaxTTL, tokenData: identityAccessToken }; } @@ -446,6 +452,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider type: EventType.GET_TOKEN_IDENTITY_TOKEN_AUTH, metadata: { identityId: token.identityId, + identityName: identityMembershipOrg.identity.name, tokenId: token.id } } From d951c40aba3d351988132994b4c85b2d2d76ecf8 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Mon, 17 Nov 2025 21:05:25 +0530 Subject: [PATCH 07/53] fix: review suggestions --- backend/src/ee/services/license/license-fns.ts | 8 ++++---- backend/src/ee/services/license/license-service.ts | 9 +++++---- backend/src/ee/services/license/license-types.ts | 7 ++++++- backend/src/server/routes/v1/admin-router.ts | 3 ++- .../offline-usage-report/offline-usage-report-service.ts | 3 ++- docs/self-hosting/ee.mdx | 4 ++-- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index 4231ba3c23..09ff9e1081 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -7,7 +7,7 @@ import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { UserAliasType } from "@app/services/user-alias/user-alias-types"; -import { TFeatureSet, TLicenseKeyConfig, TOfflineLicenseContents } from "./license-types"; +import { LicenseType, TFeatureSet, TLicenseKeyConfig, TOfflineLicenseContents } from "./license-types"; export const isOfflineLicenseKey = (licenseKey: string): boolean => { try { @@ -32,10 +32,10 @@ export const getLicenseKeyConfig = ( if (licenseKey) { if (isOfflineLicenseKey(licenseKey)) { - return { isValid: true, licenseKey, type: "offline" }; + return { isValid: true, licenseKey, type: LicenseType.Offline }; } - return { isValid: true, licenseKey, type: "online" }; + return { isValid: true, licenseKey, type: LicenseType.Online }; } const offlineLicenseKey = cfg.LICENSE_KEY_OFFLINE; @@ -43,7 +43,7 @@ export const getLicenseKeyConfig = ( // backwards compatibility if (offlineLicenseKey) { if (isOfflineLicenseKey(offlineLicenseKey)) { - return { isValid: true, licenseKey: offlineLicenseKey, type: "offline" }; + return { isValid: true, licenseKey: offlineLicenseKey, type: LicenseType.Offline }; } return { isValid: false }; diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index 080556ca57..3bbd58831a 100644 --- a/backend/src/ee/services/license/license-service.ts +++ b/backend/src/ee/services/license/license-service.ts @@ -25,6 +25,7 @@ import { TLicenseDALFactory } from "./license-dal"; import { getDefaultOnPremFeatures, getLicenseKeyConfig, setupLicenseRequestWithStore } from "./license-fns"; import { InstanceType, + LicenseType, TAddOrgPmtMethodDTO, TAddOrgTaxIdDTO, TCreateOrgPortalSession, @@ -87,7 +88,7 @@ export const licenseServiceFactory = ({ ); const onlineLicenseKey = - licenseKeyConfig.isValid && licenseKeyConfig.type === "online" ? licenseKeyConfig.licenseKey : ""; + licenseKeyConfig.isValid && licenseKeyConfig.type === LicenseType.Online ? licenseKeyConfig.licenseKey : ""; const licenseServerOnPremApi = setupLicenseRequestWithStore( envConfig.LICENSE_SERVER_URL || "", @@ -135,7 +136,7 @@ export const licenseServiceFactory = ({ return; } - if (licenseKeyConfig.isValid && licenseKeyConfig.type === "online") { + if (licenseKeyConfig.isValid && licenseKeyConfig.type === LicenseType.Online) { const token = await licenseServerOnPremApi.refreshLicense(); if (token) { await syncLicenseKeyOnPremFeatures(true); @@ -146,7 +147,7 @@ export const licenseServiceFactory = ({ return; } - if (licenseKeyConfig.isValid && licenseKeyConfig.type === "offline") { + if (licenseKeyConfig.isValid && licenseKeyConfig.type === LicenseType.Offline) { let isValidOfflineLicense = true; const contents: TOfflineLicenseContents = JSON.parse( Buffer.from(licenseKeyConfig.licenseKey, "base64").toString("utf8") @@ -188,7 +189,7 @@ export const licenseServiceFactory = ({ }; const initializeBackgroundSync = async () => { - if (licenseKeyConfig?.isValid && licenseKeyConfig?.type === "online") { + if (licenseKeyConfig?.isValid && licenseKeyConfig?.type === LicenseType.Online) { logger.info("Setting up background sync process for refresh onPremFeatures"); const job = new CronJob("*/10 * * * *", syncLicenseKeyOnPremFeatures); job.start(); diff --git a/backend/src/ee/services/license/license-types.ts b/backend/src/ee/services/license/license-types.ts index 1fd11fabf4..8897eaabcf 100644 --- a/backend/src/ee/services/license/license-types.ts +++ b/backend/src/ee/services/license/license-types.ts @@ -137,6 +137,11 @@ export type TOrgInvoiceDTO = TOrgPermission; export type TOrgLicensesDTO = TOrgPermission; +export enum LicenseType { + Offline = "offline", + Online = "online" +} + export type TLicenseKeyConfig = | { isValid: false; @@ -144,5 +149,5 @@ export type TLicenseKeyConfig = | { isValid: true; licenseKey: string; - type: "offline" | "online"; + type: LicenseType; }; diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index 8586336429..f6ec36f6f0 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -10,6 +10,7 @@ import { UsersSchema } from "@app/db/schemas"; import { getLicenseKeyConfig } from "@app/ee/services/license/license-fns"; +import { LicenseType } from "@app/ee/services/license/license-types"; import { getConfig, overridableKeys } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError } from "@app/lib/errors"; @@ -67,7 +68,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { const serverEnvs = getConfig(); const licenseKeyConfig = getLicenseKeyConfig(); - const hasOfflineLicense = licenseKeyConfig.isValid && licenseKeyConfig.type === "offline"; + const hasOfflineLicense = licenseKeyConfig.isValid && licenseKeyConfig.type === LicenseType.Offline; return { config: { diff --git a/backend/src/services/offline-usage-report/offline-usage-report-service.ts b/backend/src/services/offline-usage-report/offline-usage-report-service.ts index 92360ca52d..f05c734c15 100644 --- a/backend/src/services/offline-usage-report/offline-usage-report-service.ts +++ b/backend/src/services/offline-usage-report/offline-usage-report-service.ts @@ -35,7 +35,8 @@ export const offlineUsageReportServiceFactory = ({ if (!hasOfflineLicense) { throw new BadRequestError({ - message: "Offline usage reports are not enabled. An offline license must be configured in LICENSE_KEY." + message: + "Offline usage reports are not enabled. Usage reports are only available for self-hosted offline instances" }); } diff --git a/docs/self-hosting/ee.mdx b/docs/self-hosting/ee.mdx index ea32c416ce..9c3e7d644c 100644 --- a/docs/self-hosting/ee.mdx +++ b/docs/self-hosting/ee.mdx @@ -14,7 +14,7 @@ This guide walks through how you can use these paid features on a self-hosted in Once purchased, you will be issued a license key. - Assign the issued license key to the `LICENSE_KEY` environment variable in your Infisical instance. The system will automatically detect whether the license is online or offline. + Set your license key as the value of the **LICENSE_KEY** environment variable within your Infisical instance. @@ -33,7 +33,7 @@ This guide walks through how you can use these paid features on a self-hosted in - The system will automatically detect that it's an offline license based on the key format. - Backwards Compatibility: The `LICENSE_KEY_OFFLINE` environment variable is still supported for backwards compatibility, but we recommend using `LICENSE_KEY` for all license types going forward. + While the LICENSE_KEY_OFFLINE environment variable continues to be supported for compatibility with existing configurations, we recommend transitioning to LICENSE_KEY for all license types going forward. From 4a56c7ea4601958565af26b906901c2dc294f3ae Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Tue, 18 Nov 2025 02:11:07 +0530 Subject: [PATCH 08/53] fix: e2e tests --- .../services/license/__mocks__/license-fns.ts | 6 ++++ backend/src/server/routes/index.ts | 33 ++++++++++--------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/backend/src/ee/services/license/__mocks__/license-fns.ts b/backend/src/ee/services/license/__mocks__/license-fns.ts index d303859bb3..2f29e4812a 100644 --- a/backend/src/ee/services/license/__mocks__/license-fns.ts +++ b/backend/src/ee/services/license/__mocks__/license-fns.ts @@ -39,3 +39,9 @@ export const getDefaultOnPremFeatures = () => { }; export const setupLicenseRequestWithStore = () => {}; + +export const getLicenseKeyConfig = () => { + return { + isValid: false + }; +}; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 5dd7a1c22a..e0508a76a7 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -2431,22 +2431,25 @@ export const registerRoutes = async ( } } - await telemetryQueue.startTelemetryCheck(); - await telemetryQueue.startAggregatedEventsJob(); - await dailyResourceCleanUp.init(); - await healthAlert.init(); - await pkiSyncCleanup.init(); - await pamAccountRotation.init(); - await dailyReminderQueueService.startDailyRemindersJob(); - await dailyReminderQueueService.startSecretReminderMigrationJob(); - await dailyExpiringPkiItemAlert.startSendingAlerts(); - await pkiSubscriberQueue.startDailyAutoRenewalJob(); - await pkiAlertV2Queue.init(); - await certificateV3Queue.init(); + if (!appCfg.isTestMode) { + await telemetryQueue.startTelemetryCheck(); + await telemetryQueue.startAggregatedEventsJob(); + await dailyResourceCleanUp.init(); + await healthAlert.init(); + await pkiSyncCleanup.init(); + await pamAccountRotation.init(); + await dailyReminderQueueService.startDailyRemindersJob(); + await dailyReminderQueueService.startSecretReminderMigrationJob(); + await dailyExpiringPkiItemAlert.startSendingAlerts(); + await pkiSubscriberQueue.startDailyAutoRenewalJob(); + await pkiAlertV2Queue.init(); + await certificateV3Queue.init(); + await microsoftTeamsService.start(); + await dynamicSecretQueueService.init(); + await eventBusService.init(); + } + await kmsService.startService(hsmStatus); - await microsoftTeamsService.start(); - await dynamicSecretQueueService.init(); - await eventBusService.init(); // inject all services server.decorate("services", { From ea74b6051e6deaff9373b9fd42c3ce7320ec750d Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Tue, 18 Nov 2025 11:34:06 +0530 Subject: [PATCH 09/53] chore: refactor --- .../offline-usage-report/offline-usage-report-service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/services/offline-usage-report/offline-usage-report-service.ts b/backend/src/services/offline-usage-report/offline-usage-report-service.ts index f05c734c15..1c34425a22 100644 --- a/backend/src/services/offline-usage-report/offline-usage-report-service.ts +++ b/backend/src/services/offline-usage-report/offline-usage-report-service.ts @@ -2,6 +2,7 @@ import crypto from "crypto"; import { getLicenseKeyConfig } from "@app/ee/services/license/license-fns"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; +import { LicenseType } from "@app/ee/services/license/license-types"; import { BadRequestError } from "@app/lib/errors"; import { TOfflineUsageReportDALFactory } from "./offline-usage-report-dal"; @@ -31,7 +32,7 @@ export const offlineUsageReportServiceFactory = ({ const generateUsageReportCSV = async () => { const licenseKeyConfig = getLicenseKeyConfig(); - const hasOfflineLicense = licenseKeyConfig.isValid && licenseKeyConfig.type === "offline"; + const hasOfflineLicense = licenseKeyConfig.isValid && licenseKeyConfig.type === LicenseType.Offline; if (!hasOfflineLicense) { throw new BadRequestError({ From b242ec407d86aad118802047717ef1f226c04a77 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Tue, 18 Nov 2025 18:50:19 +0530 Subject: [PATCH 10/53] fix e2e tests --- backend/src/server/routes/index.ts | 33 ++++++++++++++---------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index e0508a76a7..c29fc1b1a9 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -2431,25 +2431,22 @@ export const registerRoutes = async ( } } - if (!appCfg.isTestMode) { - await telemetryQueue.startTelemetryCheck(); - await telemetryQueue.startAggregatedEventsJob(); - await dailyResourceCleanUp.init(); - await healthAlert.init(); - await pkiSyncCleanup.init(); - await pamAccountRotation.init(); - await dailyReminderQueueService.startDailyRemindersJob(); - await dailyReminderQueueService.startSecretReminderMigrationJob(); - await dailyExpiringPkiItemAlert.startSendingAlerts(); - await pkiSubscriberQueue.startDailyAutoRenewalJob(); - await pkiAlertV2Queue.init(); - await certificateV3Queue.init(); - await microsoftTeamsService.start(); - await dynamicSecretQueueService.init(); - await eventBusService.init(); - } - await kmsService.startService(hsmStatus); + await telemetryQueue.startTelemetryCheck(); + await telemetryQueue.startAggregatedEventsJob(); + await dailyResourceCleanUp.init(); + await healthAlert.init(); + await pkiSyncCleanup.init(); + await pamAccountRotation.init(); + await dailyReminderQueueService.startDailyRemindersJob(); + await dailyReminderQueueService.startSecretReminderMigrationJob(); + await dailyExpiringPkiItemAlert.startSendingAlerts(); + await pkiSubscriberQueue.startDailyAutoRenewalJob(); + await pkiAlertV2Queue.init(); + await certificateV3Queue.init(); + await microsoftTeamsService.start(); + await dynamicSecretQueueService.init(); + await eventBusService.init(); // inject all services server.decorate("services", { From aae6d1752d3bffa906c34b45f000081d24b8a10f Mon Sep 17 00:00:00 2001 From: Victor Santos Date: Wed, 19 Nov 2025 10:12:32 -0300 Subject: [PATCH 11/53] docs: add LDAP authentication example and parameters to .NET SDK documentation --- docs/sdks/languages/dotnet.mdx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/sdks/languages/dotnet.mdx b/docs/sdks/languages/dotnet.mdx index 7b3d128853..b02aa9d752 100644 --- a/docs/sdks/languages/dotnet.mdx +++ b/docs/sdks/languages/dotnet.mdx @@ -118,6 +118,22 @@ var _ = await sdk.Auth().UniversalAuth().LoginAsync( - `clientId` (string): The client ID of your Machine Identity. - `clientSecret` (string): The client secret of your Machine Identity. +### LDAP Auth + +#### Authenticating +```cs +var _ = await sdk.Auth().LdapAuth().LoginAsync( + "IDENTITY_ID", + "USERNAME", + "PASSWORD" +); +``` + +**Parameters:** +- `identityId` (string): The ID of your Machine Identity . +- `username` (string): The LDAP username for authentication. +- `password` (string): The LDAP password for authentication. + ### `Secrets()` The `Secrets()` sub-class handles operations related to the Infisical secrets management product. From 39aa338ce51e60dd14976509e69d5982ffd4f81a Mon Sep 17 00:00:00 2001 From: x032205 Date: Wed, 19 Nov 2025 11:11:35 -0500 Subject: [PATCH 12/53] pam: allow account credentials to be fetched more than once --- .../services/pam-account/pam-account-service.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/backend/src/ee/services/pam-account/pam-account-service.ts b/backend/src/ee/services/pam-account/pam-account-service.ts index 8c93fbfaf2..21389f6c0a 100644 --- a/backend/src/ee/services/pam-account/pam-account-service.ts +++ b/backend/src/ee/services/pam-account/pam-account-service.ts @@ -668,11 +668,6 @@ export const pamAccountServiceFactory = ({ throw new BadRequestError({ message: "Session has ended or expired" }); } - // Verify that the session has not already had credentials fetched - if (session.status !== PamSessionStatus.Starting) { - throw new BadRequestError({ message: "Session has already been started" }); - } - const account = await pamAccountDAL.findById(session.accountId); if (!account) throw new NotFoundError({ message: `Account with ID '${session.accountId}' not found` }); @@ -690,10 +685,12 @@ export const pamAccountServiceFactory = ({ const decryptedResource = await decryptResource(resource, session.projectId, kmsService); // Mark session as started - await pamSessionDAL.updateById(sessionId, { - status: PamSessionStatus.Active, - startedAt: new Date() - }); + if (session.status === PamSessionStatus.Starting) { + await pamSessionDAL.updateById(sessionId, { + status: PamSessionStatus.Active, + startedAt: new Date() + }); + } return { credentials: { From bfb49172b95c9fe6f1dccb7154bede061bb5275c Mon Sep 17 00:00:00 2001 From: x032205 Date: Wed, 19 Nov 2025 16:08:38 -0500 Subject: [PATCH 13/53] split audit logs into two: - get session credentials - start session --- .../src/ee/routes/v1/pam-session-router.ts | 23 +++++++++++++++---- .../ee/services/audit-log/audit-log-types.ts | 10 ++++++++ .../pam-account/pam-account-service.ts | 6 ++++- .../src/hooks/api/auditLogs/constants.tsx | 2 ++ frontend/src/hooks/api/auditLogs/enums.tsx | 1 + 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/backend/src/ee/routes/v1/pam-session-router.ts b/backend/src/ee/routes/v1/pam-session-router.ts index cc8969c924..3c39a9516e 100644 --- a/backend/src/ee/routes/v1/pam-session-router.ts +++ b/backend/src/ee/routes/v1/pam-session-router.ts @@ -41,17 +41,15 @@ export const registerPamSessionRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const { credentials, projectId, account } = await server.services.pamAccount.getSessionCredentials( - req.params.sessionId, - req.permission - ); + const { credentials, projectId, account, sessionStarted } = + await server.services.pamAccount.getSessionCredentials(req.params.sessionId, req.permission); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, projectId, event: { - type: EventType.PAM_SESSION_START, + type: EventType.PAM_SESSION_CREDENTIALS_GET, metadata: { sessionId: req.params.sessionId, accountName: account.name @@ -59,6 +57,21 @@ export const registerPamSessionRouter = async (server: FastifyZodProvider) => { } }); + if (sessionStarted) { + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId, + event: { + type: EventType.PAM_SESSION_START, + metadata: { + sessionId: req.params.sessionId, + accountName: account.name + } + } + }); + } + return { credentials: credentials as z.infer }; } }); 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 de3ce9af6a..eb4bd16ef0 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -535,6 +535,7 @@ export enum EventType { DASHBOARD_GET_SECRET_VALUE = "dashboard-get-secret-value", DASHBOARD_GET_SECRET_VERSION_VALUE = "dashboard-get-secret-version-value", + PAM_SESSION_CREDENTIALS_GET = "pam-session-credentials-get", PAM_SESSION_START = "pam-session-start", PAM_SESSION_LOGS_UPDATE = "pam-session-logs-update", PAM_SESSION_END = "pam-session-end", @@ -3978,6 +3979,14 @@ interface OrgRoleDeleteEvent { }; } +interface PamSessionCredentialsGetEvent { + type: EventType.PAM_SESSION_CREDENTIALS_GET; + metadata: { + sessionId: string; + accountName: string; + }; +} + interface PamSessionStartEvent { type: EventType.PAM_SESSION_START; metadata: { @@ -4531,6 +4540,7 @@ export type Event = | OrgRoleCreateEvent | OrgRoleUpdateEvent | OrgRoleDeleteEvent + | PamSessionCredentialsGetEvent | PamSessionStartEvent | PamSessionLogsUpdateEvent | PamSessionEndEvent diff --git a/backend/src/ee/services/pam-account/pam-account-service.ts b/backend/src/ee/services/pam-account/pam-account-service.ts index 21389f6c0a..1eae8df15c 100644 --- a/backend/src/ee/services/pam-account/pam-account-service.ts +++ b/backend/src/ee/services/pam-account/pam-account-service.ts @@ -684,12 +684,15 @@ export const pamAccountServiceFactory = ({ const decryptedResource = await decryptResource(resource, session.projectId, kmsService); + let sessionStarted = false; + // Mark session as started if (session.status === PamSessionStatus.Starting) { await pamSessionDAL.updateById(sessionId, { status: PamSessionStatus.Active, startedAt: new Date() }); + sessionStarted = true; } return { @@ -698,7 +701,8 @@ export const pamAccountServiceFactory = ({ ...decryptedAccount.credentials }, projectId: project.id, - account + account, + sessionStarted }; }; diff --git a/frontend/src/hooks/api/auditLogs/constants.tsx b/frontend/src/hooks/api/auditLogs/constants.tsx index 796ab7b780..54e96941e1 100644 --- a/frontend/src/hooks/api/auditLogs/constants.tsx +++ b/frontend/src/hooks/api/auditLogs/constants.tsx @@ -262,6 +262,7 @@ export const eventToNameMap: { [K in EventType]: string } = { [EventType.UPDATE_IDENTITY_PROJECT_MEMBERSHIP]: "Update Identity Project Membership", [EventType.DELETE_IDENTITY_PROJECT_MEMBERSHIP]: "Delete Identity Project Membership", + [EventType.PAM_SESSION_CREDENTIALS_GET]: "PAM Session Credentials Get", [EventType.PAM_SESSION_START]: "PAM Session Start", [EventType.PAM_SESSION_LOGS_UPDATE]: "PAM Session Logs Update", [EventType.PAM_SESSION_END]: "PAM Session End", @@ -314,6 +315,7 @@ const sharedProjectEvents = [ export const projectToEventsMap: Partial> = { [ProjectType.PAM]: [ ...sharedProjectEvents, + EventType.PAM_SESSION_CREDENTIALS_GET, EventType.PAM_SESSION_START, EventType.PAM_SESSION_LOGS_UPDATE, EventType.PAM_SESSION_END, diff --git a/frontend/src/hooks/api/auditLogs/enums.tsx b/frontend/src/hooks/api/auditLogs/enums.tsx index cf104dfae3..bde306450c 100644 --- a/frontend/src/hooks/api/auditLogs/enums.tsx +++ b/frontend/src/hooks/api/auditLogs/enums.tsx @@ -254,6 +254,7 @@ export enum EventType { UPDATE_IDENTITY_PROJECT_MEMBERSHIP = "update-identity-project-membership", DELETE_IDENTITY_PROJECT_MEMBERSHIP = "delete-identity-project-membership", + PAM_SESSION_CREDENTIALS_GET = "pam-session-credentials-get", PAM_SESSION_START = "pam-session-start", PAM_SESSION_LOGS_UPDATE = "pam-session-logs-update", PAM_SESSION_END = "pam-session-end", From 8a8d2b52a6027def1111118ba2b67c20ddb0f012 Mon Sep 17 00:00:00 2001 From: Victor Santos Date: Wed, 19 Nov 2025 18:18:16 -0300 Subject: [PATCH 14/53] docs: add JWT, LDAP, and OCI authentication examples to Go SDK documentation --- docs/sdks/languages/go.mdx | 108 +++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/docs/sdks/languages/go.mdx b/docs/sdks/languages/go.mdx index 26f8a36d3d..88dc6cf3b1 100644 --- a/docs/sdks/languages/go.mdx +++ b/docs/sdks/languages/go.mdx @@ -284,6 +284,114 @@ if err != nil { } ``` +#### JWT Auth + + + Please note that this authentication method requires a valid JWT token from your JWT issuer. Please [read + more](/documentation/platform/identities/jwt-auth) about this authentication + method. + + +**Using the SDK** + +```go +credential, err := client.Auth().JwtAuthLogin("MACHINE_IDENTITY_ID", "JWT_TOKEN") + +if err != nil { + fmt.Println(err) + os.Exit(1) +} +``` + +#### LDAP Auth + + + Please note that this authentication method requires LDAP credentials. Please [read + more](/documentation/platform/identities/ldap-auth/general) about this authentication + method. + + +**Using environment variables** + +You can set the `INFISICAL_LDAP_AUTH_IDENTITY_ID` environment variable and pass empty string for the identity ID: + +```go +credential, err := client.Auth().LdapAuthLogin("", "LDAP_USERNAME", "LDAP_PASSWORD") + +if err != nil { + fmt.Println(err) + os.Exit(1) +} +``` + +**Using the SDK directly** + +```go +credential, err := client.Auth().LdapAuthLogin("MACHINE_IDENTITY_ID", "LDAP_USERNAME", "LDAP_PASSWORD") + +if err != nil { + fmt.Println(err) + os.Exit(1) +} +``` + +#### OCI Auth + + + Please note that this authentication method will only work if you're running + your application on Oracle Cloud Infrastructure. Please [read + more](/documentation/platform/identities/oci-auth) about this authentication + method. + + +**Using environment variables** + +You can set the `INFISICAL_OCI_AUTH_IDENTITY_ID` environment variable and omit the `IdentityID` field: + +```go +credential, err := client.Auth().OciAuthLogin(infisical.OciAuthLoginOptions{ + UserID: "USER_OCID", + TenancyID: "TENANCY_OCID", + Fingerprint: "FINGERPRINT", + PrivateKey: "PRIVATE_KEY", + Region: "REGION", +}) + +if err != nil { + fmt.Println(err) + os.Exit(1) +} +``` + +**Using the SDK directly** + +```go +credential, err := client.Auth().OciAuthLogin(infisical.OciAuthLoginOptions{ + IdentityID: "MACHINE_IDENTITY_ID", + UserID: "USER_OCID", + TenancyID: "TENANCY_OCID", + Fingerprint: "FINGERPRINT", + PrivateKey: "PRIVATE_KEY", + Region: "REGION", + Passphrase: nil, // Optional: pointer to string if your private key has a passphrase +}) + +if err != nil { + fmt.Println(err) + os.Exit(1) +} +``` + +**OciAuthLoginOptions fields:** + +- `IdentityID` (string) - Your Infisical Machine Identity ID. Can be set via `INFISICAL_OCI_AUTH_IDENTITY_ID` environment variable. +- `UserID` (string) - Your OCI user OCID. +- `TenancyID` (string) - Your OCI tenancy OCID. +- `Fingerprint` (string) - Your OCI API key fingerprint. +- `PrivateKey` (string) - Your OCI private key (PEM format). +- `Region` (string) - Your OCI region (e.g., `us-ashburn-1`). +- `Passphrase` (*string) - Optional: pointer to passphrase string if your private key is encrypted. + ## Secrets ### List Secrets From a6d53c549892c43719545d4c331ae7008a7909e4 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Thu, 20 Nov 2025 02:52:28 +0530 Subject: [PATCH 15/53] docs: vercel secret sync info --- docs/integrations/secret-syncs/vercel.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/integrations/secret-syncs/vercel.mdx b/docs/integrations/secret-syncs/vercel.mdx index 74cffbc11b..6b2333089f 100644 --- a/docs/integrations/secret-syncs/vercel.mdx +++ b/docs/integrations/secret-syncs/vercel.mdx @@ -43,6 +43,9 @@ description: "Learn how to configure a Vercel Sync for Infisical." - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Vercel when keys conflict. - **Import Secrets (Prioritize Vercel)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Vercel over Infisical when keys conflict. + + If you're using [sensitive environment variables](https://vercel.com/docs/environment-variables/sensitive-environment-variables) in Vercel, you'll need to manually reset their values in Infisical after the initial sync to ensure both platforms remain in sync as their values cannot be retrieved from Vercel. + - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and `{{environment}}` for the environment. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. @@ -149,4 +152,5 @@ description: "Learn how to configure a Vercel Sync for Infisical." } ``` + From b8bdc231c2b8f47d15deff6d3d251b74fcf40481 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Thu, 20 Nov 2025 03:01:10 +0530 Subject: [PATCH 16/53] fix: review comments --- docs/integrations/secret-syncs/vercel.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/secret-syncs/vercel.mdx b/docs/integrations/secret-syncs/vercel.mdx index 6b2333089f..de83a20681 100644 --- a/docs/integrations/secret-syncs/vercel.mdx +++ b/docs/integrations/secret-syncs/vercel.mdx @@ -44,7 +44,7 @@ description: "Learn how to configure a Vercel Sync for Infisical." - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Vercel when keys conflict. - **Import Secrets (Prioritize Vercel)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Vercel over Infisical when keys conflict. - If you're using [sensitive environment variables](https://vercel.com/docs/environment-variables/sensitive-environment-variables) in Vercel, you'll need to manually reset their values in Infisical after the initial sync to ensure both platforms remain in sync as their values cannot be retrieved from Vercel. + Vercel does not expose the values of [sensitive environment variables](https://vercel.com/docs/environment-variables/sensitive-environment-variables), so Infisical cannot import them during the initial sync. As a result, these secrets are created in Infisical with empty values. After the first sync, you'll need to manually re-enter their values in Infisical to ensure both platforms stay aligned. - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and `{{environment}}` for the environment. From be7641601fbefac945f55f9621fbcbae881ed57c Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 20 Nov 2025 11:13:10 -0500 Subject: [PATCH 17/53] update audit log wording for PAM --- .../src/hooks/api/auditLogs/constants.tsx | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/frontend/src/hooks/api/auditLogs/constants.tsx b/frontend/src/hooks/api/auditLogs/constants.tsx index 54e96941e1..74a1ade310 100644 --- a/frontend/src/hooks/api/auditLogs/constants.tsx +++ b/frontend/src/hooks/api/auditLogs/constants.tsx @@ -262,25 +262,25 @@ export const eventToNameMap: { [K in EventType]: string } = { [EventType.UPDATE_IDENTITY_PROJECT_MEMBERSHIP]: "Update Identity Project Membership", [EventType.DELETE_IDENTITY_PROJECT_MEMBERSHIP]: "Delete Identity Project Membership", - [EventType.PAM_SESSION_CREDENTIALS_GET]: "PAM Session Credentials Get", - [EventType.PAM_SESSION_START]: "PAM Session Start", - [EventType.PAM_SESSION_LOGS_UPDATE]: "PAM Session Logs Update", - [EventType.PAM_SESSION_END]: "PAM Session End", - [EventType.PAM_SESSION_GET]: "PAM Session Get", - [EventType.PAM_SESSION_LIST]: "PAM Session List", - [EventType.PAM_FOLDER_CREATE]: "PAM Folder Create", - [EventType.PAM_FOLDER_UPDATE]: "PAM Folder Update", - [EventType.PAM_FOLDER_DELETE]: "PAM Folder Delete", - [EventType.PAM_ACCOUNT_LIST]: "PAM Account List", - [EventType.PAM_ACCOUNT_ACCESS]: "PAM Account Access", - [EventType.PAM_ACCOUNT_CREATE]: "PAM Account Create", - [EventType.PAM_ACCOUNT_UPDATE]: "PAM Account Update", - [EventType.PAM_ACCOUNT_DELETE]: "PAM Account Delete", - [EventType.PAM_RESOURCE_LIST]: "PAM Resource List", - [EventType.PAM_RESOURCE_GET]: "PAM Resource Get", - [EventType.PAM_RESOURCE_CREATE]: "PAM Resource Create", - [EventType.PAM_RESOURCE_UPDATE]: "PAM Resource Update", - [EventType.PAM_RESOURCE_DELETE]: "PAM Resource Delete", + [EventType.PAM_SESSION_CREDENTIALS_GET]: "Get PAM Session Credentials", + [EventType.PAM_SESSION_START]: "Start PAM Session", + [EventType.PAM_SESSION_LOGS_UPDATE]: "Update PAM Session Logs", + [EventType.PAM_SESSION_END]: "End PAM Session", + [EventType.PAM_SESSION_GET]: "Get PAM Session", + [EventType.PAM_SESSION_LIST]: "List PAM Sessions", + [EventType.PAM_FOLDER_CREATE]: "Create PAM Folder", + [EventType.PAM_FOLDER_UPDATE]: "Update PAM Folder", + [EventType.PAM_FOLDER_DELETE]: "Delete PAM Folder", + [EventType.PAM_ACCOUNT_LIST]: "List PAM Accounts", + [EventType.PAM_ACCOUNT_ACCESS]: "Access PAM Account", + [EventType.PAM_ACCOUNT_CREATE]: "Create PAM Account", + [EventType.PAM_ACCOUNT_UPDATE]: "Update PAM Account", + [EventType.PAM_ACCOUNT_DELETE]: "Delete PAM Account", + [EventType.PAM_RESOURCE_LIST]: "List PAM Resources", + [EventType.PAM_RESOURCE_GET]: "Get PAM Resource", + [EventType.PAM_RESOURCE_CREATE]: "Create PAM Resource", + [EventType.PAM_RESOURCE_UPDATE]: "Update PAM Resource", + [EventType.PAM_RESOURCE_DELETE]: "Delete PAM Resource", [EventType.CREATE_CERTIFICATE_PROFILE]: "Create Certificate Profile", [EventType.UPDATE_CERTIFICATE_PROFILE]: "Update Certificate Profile", From 8c3699b4376608339faa713de6888afe07a8493f Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Fri, 14 Nov 2025 21:00:28 -0800 Subject: [PATCH 18/53] Move nock to dev deps and load it lazily # Conflicts: # backend/src/server/routes/v1/bdd-nock-router.ts --- .../src/server/routes/v1/bdd-nock-router.ts | 125 ++++++++++-------- 1 file changed, 69 insertions(+), 56 deletions(-) diff --git a/backend/src/server/routes/v1/bdd-nock-router.ts b/backend/src/server/routes/v1/bdd-nock-router.ts index 6a32cac20a..15981ddd32 100644 --- a/backend/src/server/routes/v1/bdd-nock-router.ts +++ b/backend/src/server/routes/v1/bdd-nock-router.ts @@ -1,20 +1,30 @@ -// import { z } from "zod"; +import type { Definition } from "nock"; +import { z } from "zod"; -// import { getConfig } from "@app/lib/config/env"; -// import { ForbiddenRequestError } from "@app/lib/errors"; -// import { logger } from "@app/lib/logger"; -// import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -// import { AuthMode } from "@app/services/auth/auth-type"; +import { getConfig } from "@app/lib/config/env"; +import { ForbiddenRequestError, InternalServerError } 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 importNock = async () => { + // Notice: it seems like importing nock somehow increase memory usage a lots, let's import it lazily. + const nock = await import("nock"); + if (!nock) { + throw new InternalServerError({ message: "Failed to import nock" }); + } + return nock; + }; + + 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", @@ -42,46 +52,49 @@ // } 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" }; -// } -// }); + const nock = await importNock(); + 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"); + const nock = await importNock(); + 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"); + const nock = await importNock(); + nock.restore(); + return { status: "ok" }; + } + }); +}; From 889fb8b7e09d7c7f5cbad51b00b9912144789fe4 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Fri, 14 Nov 2025 21:08:08 -0800 Subject: [PATCH 19/53] Fix rebase --- .../src/server/routes/v1/bdd-nock-router.ts | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/backend/src/server/routes/v1/bdd-nock-router.ts b/backend/src/server/routes/v1/bdd-nock-router.ts index 15981ddd32..43c5b6494f 100644 --- a/backend/src/server/routes/v1/bdd-nock-router.ts +++ b/backend/src/server/routes/v1/bdd-nock-router.ts @@ -26,31 +26,31 @@ export const registerBddNockRouter = async (server: FastifyZodProvider) => { } }; -// 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; + }); const nock = await importNock(); nock.define(processedDefinitions); From c5f5e329e8383bc9418b34b206c91b2eb2965ba7 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Fri, 14 Nov 2025 21:15:15 -0800 Subject: [PATCH 20/53] TS linter issue --- backend/src/server/routes/v1/bdd-nock-router.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/server/routes/v1/bdd-nock-router.ts b/backend/src/server/routes/v1/bdd-nock-router.ts index 43c5b6494f..a2c2def2c1 100644 --- a/backend/src/server/routes/v1/bdd-nock-router.ts +++ b/backend/src/server/routes/v1/bdd-nock-router.ts @@ -10,6 +10,7 @@ import { AuthMode } from "@app/services/auth/auth-type"; export const registerBddNockRouter = async (server: FastifyZodProvider) => { const importNock = async () => { // Notice: it seems like importing nock somehow increase memory usage a lots, let's import it lazily. + // eslint-disable-next-line import/no-extraneous-dependencies const nock = await import("nock"); if (!nock) { throw new InternalServerError({ message: "Failed to import nock" }); From f5936cad7ee6df5cc79157fb689ae52237a6939c Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Fri, 14 Nov 2025 21:25:55 -0800 Subject: [PATCH 21/53] Bring back nock route --- backend/src/server/routes/v1/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/server/routes/v1/index.ts b/backend/src/server/routes/v1/index.ts index 68099e50e4..36f091a8bd 100644 --- a/backend/src/server/routes/v1/index.ts +++ b/backend/src/server/routes/v1/index.ts @@ -8,7 +8,7 @@ import { registerSecretSyncRouter, SECRET_SYNC_REGISTER_ROUTER_MAP } from "@app/ import { registerAdminRouter } from "./admin-router"; import { registerAuthRoutes } from "./auth-router"; -// import { registerBddNockRouter } from "./bdd-nock-router"; +import { registerBddNockRouter } from "./bdd-nock-router"; import { registerProjectBotRouter } from "./bot-router"; import { registerCaRouter } from "./certificate-authority-router"; import { CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP } from "./certificate-authority-routers"; @@ -71,6 +71,7 @@ import { registerUserEngagementRouter } from "./user-engagement-router"; import { registerUserRouter } from "./user-router"; import { registerWebhookRouter } from "./webhook-router"; import { registerWorkflowIntegrationRouter } from "./workflow-integration-router"; +import { getConfig } from "@app/lib/config/env"; export const registerV1Routes = async (server: FastifyZodProvider) => { await server.register(registerSsoRouter, { prefix: "/sso" }); @@ -241,7 +242,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { // Note: This is a special route for BDD tests. It's only available in development mode and only for BDD tests. // This route should NEVER BE ENABLED IN PRODUCTION! - // if (getConfig().isBddNockApiEnabled) { - // await server.register(registerBddNockRouter, { prefix: "/bdd-nock" }); - // } + if (getConfig().isBddNockApiEnabled) { + await server.register(registerBddNockRouter, { prefix: "/bdd-nock" }); + } }; From fbaf8d37a37f850718f55186fe5e2818e722038d Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Fri, 14 Nov 2025 21:28:43 -0800 Subject: [PATCH 22/53] Let the import throw error instead --- backend/src/server/routes/v1/bdd-nock-router.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/backend/src/server/routes/v1/bdd-nock-router.ts b/backend/src/server/routes/v1/bdd-nock-router.ts index a2c2def2c1..79d099d669 100644 --- a/backend/src/server/routes/v1/bdd-nock-router.ts +++ b/backend/src/server/routes/v1/bdd-nock-router.ts @@ -8,14 +8,10 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerBddNockRouter = async (server: FastifyZodProvider) => { - const importNock = async () => { + const importNock = () => { // Notice: it seems like importing nock somehow increase memory usage a lots, let's import it lazily. // eslint-disable-next-line import/no-extraneous-dependencies - const nock = await import("nock"); - if (!nock) { - throw new InternalServerError({ message: "Failed to import nock" }); - } - return nock; + return import("nock"); }; const checkIfBddNockApiEnabled = () => { From 7da6929c0c9cb2288192b887552b57121f1b74e5 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Fri, 14 Nov 2025 21:36:25 -0800 Subject: [PATCH 23/53] Import --- backend/src/server/routes/v1/bdd-nock-router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/routes/v1/bdd-nock-router.ts b/backend/src/server/routes/v1/bdd-nock-router.ts index 79d099d669..c7409f148a 100644 --- a/backend/src/server/routes/v1/bdd-nock-router.ts +++ b/backend/src/server/routes/v1/bdd-nock-router.ts @@ -2,7 +2,7 @@ import type { Definition } from "nock"; import { z } from "zod"; import { getConfig } from "@app/lib/config/env"; -import { ForbiddenRequestError, InternalServerError } from "@app/lib/errors"; +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"; From 2ac739a6aaa1f6bb48a627852a69e0785fb98c40 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 11:52:29 -0800 Subject: [PATCH 24/53] Use a different build for prod and dev --- backend/nodemon.json | 8 +- backend/package.json | 4 +- .../bdd-nock-router.bdd.ts} | 9 +- .../src/server/routes/bdd/bdd-nock-router.ts | 3 + backend/src/server/routes/index.ts | 123 +++++++++--------- backend/src/server/routes/v1/index.ts | 8 -- backend/tsconfig.dev.json | 8 ++ backend/tsconfig.json | 3 +- 8 files changed, 93 insertions(+), 73 deletions(-) rename backend/src/server/routes/{v1/bdd-nock-router.ts => bdd/bdd-nock-router.bdd.ts} (86%) create mode 100644 backend/src/server/routes/bdd/bdd-nock-router.ts create mode 100644 backend/tsconfig.dev.json diff --git a/backend/nodemon.json b/backend/nodemon.json index 856f9ee51c..95a7f23dfd 100644 --- a/backend/nodemon.json +++ b/backend/nodemon.json @@ -1,6 +1,8 @@ { - "watch": ["src"], + "watch": [ + "src" + ], "ext": ".ts,.js", "ignore": [], - "exec": "tsx ./src/main.ts | pino-pretty --colorize --colorizeObjects --singleLine" -} + "exec": "tsx ./src/main.ts --config tsconfig.dev.json | pino-pretty --colorize --colorizeObjects --singleLine" +} \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 9a0d9772db..0e17bb2b73 100644 --- a/backend/package.json +++ b/backend/package.json @@ -32,7 +32,7 @@ "binary:clean": "rm -rf ./dist && rm -rf ./binary", "binary:rename-imports": "ts-node ./scripts/rename-mjs.ts", "test": "echo \"Error: no test specified\" && exit 1", - "dev": "tsx watch --clear-screen=false ./src/main.ts | pino-pretty --colorize --colorizeObjects --singleLine", + "dev": "tsx watch --clear-screen=false ./src/main.ts --config tsconfig.dev.json | pino-pretty --colorize --colorizeObjects --singleLine", "dev:docker": "nodemon", "build": "tsup --sourcemap", "build:frontend": "npm run build --prefix ../frontend", @@ -266,4 +266,4 @@ "zod": "^3.22.4", "zod-to-json-schema": "^3.24.5" } -} +} \ No newline at end of file diff --git a/backend/src/server/routes/v1/bdd-nock-router.ts b/backend/src/server/routes/bdd/bdd-nock-router.bdd.ts similarity index 86% rename from backend/src/server/routes/v1/bdd-nock-router.ts rename to backend/src/server/routes/bdd/bdd-nock-router.bdd.ts index c7409f148a..17db89788c 100644 --- a/backend/src/server/routes/v1/bdd-nock-router.ts +++ b/backend/src/server/routes/bdd/bdd-nock-router.bdd.ts @@ -7,7 +7,15 @@ import { logger } from "@app/lib/logger"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; +// When running in production, we don't want to even import nock, because it's not needed and it increases memory usage a lots. +// It once caused an outage in the production environment. +// This is why we would rather to crash the app if it's not in development mode (in that case, Kubernetes should stop it from rolling out). +if (process.env.NODE_ENV !== "development") { + throw new Error("BDD Nock API is not enabled"); +} + export const registerBddNockRouter = async (server: FastifyZodProvider) => { + const appCfg = getConfig(); const importNock = () => { // Notice: it seems like importing nock somehow increase memory usage a lots, let's import it lazily. // eslint-disable-next-line import/no-extraneous-dependencies @@ -15,7 +23,6 @@ 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) { diff --git a/backend/src/server/routes/bdd/bdd-nock-router.ts b/backend/src/server/routes/bdd/bdd-nock-router.ts new file mode 100644 index 0000000000..6373207d7a --- /dev/null +++ b/backend/src/server/routes/bdd/bdd-nock-router.ts @@ -0,0 +1,3 @@ +export const registerBddNockRouter = async (server: FastifyZodProvider) => { + throw new Error("BDD Nock should not be enabled in production"); +}; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 5dd7a1c22a..625f549940 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -17,30 +17,30 @@ import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approva import { accessApprovalRequestReviewerDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-reviewer-dal"; import { accessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service"; import { assumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-service"; +import { auditLogStreamDALFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-dal"; +import { auditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service"; import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal"; import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue"; import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; -import { auditLogStreamDALFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-dal"; -import { auditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service"; import { certificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal"; import { certificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-service"; import { certificateEstServiceFactory } from "@app/ee/services/certificate-est/certificate-est-service"; -import { dynamicSecretDALFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-dal"; -import { dynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service"; -import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/providers"; import { dynamicSecretLeaseDALFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal"; import { dynamicSecretLeaseQueueServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue"; import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service"; +import { dynamicSecretDALFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-dal"; +import { dynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service"; +import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/providers"; import { eventBusFactory } from "@app/ee/services/event/event-bus-service"; import { sseServiceFactory } from "@app/ee/services/event/event-sse-service"; import { externalKmsDALFactory } from "@app/ee/services/external-kms/external-kms-dal"; import { externalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service"; -import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal"; -import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; -import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal"; import { gatewayV2DalFactory } from "@app/ee/services/gateway-v2/gateway-v2-dal"; import { gatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; import { orgGatewayConfigV2DalFactory } from "@app/ee/services/gateway-v2/org-gateway-config-v2-dal"; +import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal"; +import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; +import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal"; import { githubOrgSyncDALFactory } from "@app/ee/services/github-org-sync/github-org-sync-dal"; import { githubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service"; import { groupDALFactory } from "@app/ee/services/group/group-dal"; @@ -105,39 +105,39 @@ import { secretApprovalRequestReviewerDALFactory } from "@app/ee/services/secret import { secretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal"; import { secretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service"; import { secretReplicationServiceFactory } from "@app/ee/services/secret-replication/secret-replication-service"; -import { secretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal"; -import { secretRotationQueueFactory } from "@app/ee/services/secret-rotation/secret-rotation-queue"; -import { secretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service"; import { secretRotationV2DALFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-dal"; import { secretRotationV2QueueServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-queue"; import { secretRotationV2ServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-service"; +import { secretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal"; +import { secretRotationQueueFactory } from "@app/ee/services/secret-rotation/secret-rotation-queue"; +import { secretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service"; +import { secretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal"; +import { secretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue"; +import { secretScanningV2ServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-service"; import { gitAppDALFactory } from "@app/ee/services/secret-scanning/git-app-dal"; import { gitAppInstallSessionDALFactory } from "@app/ee/services/secret-scanning/git-app-install-session-dal"; import { secretScanningDALFactory } from "@app/ee/services/secret-scanning/secret-scanning-dal"; import { secretScanningQueueFactory } from "@app/ee/services/secret-scanning/secret-scanning-queue"; import { secretScanningServiceFactory } from "@app/ee/services/secret-scanning/secret-scanning-service"; -import { secretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal"; -import { secretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue"; -import { secretScanningV2ServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-service"; import { secretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service"; import { snapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal"; import { snapshotFolderDALFactory } from "@app/ee/services/secret-snapshot/snapshot-folder-dal"; import { snapshotSecretDALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-dal"; import { snapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal"; -import { sshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; -import { sshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal"; -import { sshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh-certificate-authority-service"; -import { sshCertificateBodyDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-body-dal"; -import { sshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal"; import { sshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal"; import { sshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service"; +import { sshCertificateBodyDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-body-dal"; +import { sshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal"; +import { sshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal"; +import { sshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal"; +import { sshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service"; import { sshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal"; import { sshHostLoginUserMappingDALFactory } from "@app/ee/services/ssh-host/ssh-host-login-user-mapping-dal"; import { sshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service"; import { sshHostLoginUserDALFactory } from "@app/ee/services/ssh-host/ssh-login-user-dal"; -import { sshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal"; -import { sshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal"; -import { sshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service"; +import { sshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; +import { sshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal"; +import { sshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh-certificate-authority-service"; import { subOrgServiceFactory } from "@app/ee/services/sub-org/sub-org-service"; import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal"; import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service"; @@ -157,16 +157,12 @@ import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal"; import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service"; import { appConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; import { appConnectionServiceFactory } from "@app/services/app-connection/app-connection-service"; +import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal"; +import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { authDALFactory } from "@app/services/auth/auth-dal"; import { authLoginServiceFactory } from "@app/services/auth/auth-login-service"; import { authPaswordServiceFactory } from "@app/services/auth/auth-password-service"; import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service"; -import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal"; -import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service"; -import { certificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; -import { certificateDALFactory } from "@app/services/certificate/certificate-dal"; -import { certificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; -import { certificateServiceFactory } from "@app/services/certificate/certificate-service"; import { certificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal"; import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue"; @@ -180,13 +176,17 @@ import { certificateEstV3ServiceFactory } from "@app/services/certificate-est-v3 import { certificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; import { certificateProfileServiceFactory } from "@app/services/certificate-profile/certificate-profile-service"; import { certificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal"; +import { certificateTemplateV2DALFactory } from "@app/services/certificate-template-v2/certificate-template-v2-dal"; +import { certificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal"; import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal"; import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service"; -import { certificateTemplateV2DALFactory } from "@app/services/certificate-template-v2/certificate-template-v2-dal"; -import { certificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; import { certificateV3QueueServiceFactory } from "@app/services/certificate-v3/certificate-v3-queue"; import { certificateV3ServiceFactory } from "@app/services/certificate-v3/certificate-v3-service"; +import { certificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; +import { certificateDALFactory } from "@app/services/certificate/certificate-dal"; +import { certificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; +import { certificateServiceFactory } from "@app/services/certificate/certificate-service"; import { cmekServiceFactory } from "@app/services/cmek/cmek-service"; import { convertorServiceFactory } from "@app/services/convertor/convertor-service"; import { acmeEnrollmentConfigDALFactory } from "@app/services/enrollment-config/acme-enrollment-config-dal"; @@ -197,21 +197,17 @@ import { externalGroupOrgRoleMappingServiceFactory } from "@app/services/externa import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue"; import { externalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service"; import { vaultExternalMigrationConfigDALFactory } from "@app/services/external-migration/vault-external-migration-config-dal"; -import { folderCheckpointDALFactory } from "@app/services/folder-checkpoint/folder-checkpoint-dal"; import { folderCheckpointResourcesDALFactory } from "@app/services/folder-checkpoint-resources/folder-checkpoint-resources-dal"; +import { folderCheckpointDALFactory } from "@app/services/folder-checkpoint/folder-checkpoint-dal"; +import { folderCommitChangesDALFactory } from "@app/services/folder-commit-changes/folder-commit-changes-dal"; import { folderCommitDALFactory } from "@app/services/folder-commit/folder-commit-dal"; import { folderCommitQueueServiceFactory } from "@app/services/folder-commit/folder-commit-queue"; import { folderCommitServiceFactory } from "@app/services/folder-commit/folder-commit-service"; -import { folderCommitChangesDALFactory } from "@app/services/folder-commit-changes/folder-commit-changes-dal"; -import { folderTreeCheckpointDALFactory } from "@app/services/folder-tree-checkpoint/folder-tree-checkpoint-dal"; import { folderTreeCheckpointResourcesDALFactory } from "@app/services/folder-tree-checkpoint-resources/folder-tree-checkpoint-resources-dal"; +import { folderTreeCheckpointDALFactory } from "@app/services/folder-tree-checkpoint/folder-tree-checkpoint-dal"; import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal"; import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service"; import { healthAlertServiceFactory } from "@app/services/health-alert/health-alert-queue"; -import { identityDALFactory } from "@app/services/identity/identity-dal"; -import { identityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal"; -import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal"; -import { identityServiceFactory } from "@app/services/identity/identity-service"; import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal"; import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service"; import { identityAliCloudAuthDALFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-dal"; @@ -243,23 +239,27 @@ import { identityUaDALFactory } from "@app/services/identity-ua/identity-ua-dal" import { identityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service"; import { identityV2DALFactory } from "@app/services/identity-v2/identity-dal"; import { identityV2ServiceFactory } from "@app/services/identity-v2/identity-service"; -import { integrationDALFactory } from "@app/services/integration/integration-dal"; -import { integrationServiceFactory } from "@app/services/integration/integration-service"; +import { identityDALFactory } from "@app/services/identity/identity-dal"; +import { identityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal"; +import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal"; +import { identityServiceFactory } from "@app/services/identity/identity-service"; import { integrationAuthDALFactory } from "@app/services/integration-auth/integration-auth-dal"; import { integrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service"; +import { integrationDALFactory } from "@app/services/integration/integration-dal"; +import { integrationServiceFactory } from "@app/services/integration/integration-service"; import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal"; import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal"; import { TKmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal"; import { kmsServiceFactory } from "@app/services/kms/kms-service"; import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types"; -import { membershipDALFactory } from "@app/services/membership/membership-dal"; -import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal"; import { membershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal"; import { membershipGroupServiceFactory } from "@app/services/membership-group/membership-group-service"; import { membershipIdentityDALFactory } from "@app/services/membership-identity/membership-identity-dal"; import { membershipIdentityServiceFactory } from "@app/services/membership-identity/membership-identity-service"; import { membershipUserDALFactory } from "@app/services/membership-user/membership-user-dal"; import { membershipUserServiceFactory } from "@app/services/membership-user/membership-user-service"; +import { membershipDALFactory } from "@app/services/membership/membership-dal"; +import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal"; import { microsoftTeamsIntegrationDALFactory } from "@app/services/microsoft-teams/microsoft-teams-integration-dal"; import { microsoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service"; import { projectMicrosoftTeamsConfigDALFactory } from "@app/services/microsoft-teams/project-microsoft-teams-config-dal"; @@ -268,20 +268,20 @@ import { notificationServiceFactory } from "@app/services/notification/notificat import { userNotificationDALFactory } from "@app/services/notification/user-notification-dal"; import { offlineUsageReportDALFactory } from "@app/services/offline-usage-report/offline-usage-report-dal"; import { offlineUsageReportServiceFactory } from "@app/services/offline-usage-report/offline-usage-report-service"; +import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service"; +import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { incidentContactDALFactory } from "@app/services/org/incident-contacts-dal"; import { orgDALFactory } from "@app/services/org/org-dal"; import { orgServiceFactory } from "@app/services/org/org-service"; -import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service"; -import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { pamAccountRotationServiceFactory } from "@app/services/pam-account-rotation/pam-account-rotation-queue"; -import { dailyExpiringPkiItemAlertQueueServiceFactory } from "@app/services/pki-alert/expiring-pki-item-alert-queue"; -import { pkiAlertDALFactory } from "@app/services/pki-alert/pki-alert-dal"; -import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service"; import { pkiAlertChannelDALFactory } from "@app/services/pki-alert-v2/pki-alert-channel-dal"; import { pkiAlertHistoryDALFactory } from "@app/services/pki-alert-v2/pki-alert-history-dal"; import { pkiAlertV2DALFactory } from "@app/services/pki-alert-v2/pki-alert-v2-dal"; import { pkiAlertV2QueueServiceFactory } from "@app/services/pki-alert-v2/pki-alert-v2-queue"; import { pkiAlertV2ServiceFactory } from "@app/services/pki-alert-v2/pki-alert-v2-service"; +import { dailyExpiringPkiItemAlertQueueServiceFactory } from "@app/services/pki-alert/expiring-pki-item-alert-queue"; +import { pkiAlertDALFactory } from "@app/services/pki-alert/pki-alert-dal"; +import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service"; import { pkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal"; import { pkiCollectionItemDALFactory } from "@app/services/pki-collection/pki-collection-item-dal"; import { pkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service"; @@ -294,10 +294,6 @@ import { pkiSyncQueueFactory } from "@app/services/pki-sync/pki-sync-queue"; import { pkiSyncServiceFactory } from "@app/services/pki-sync/pki-sync-service"; import { pkiTemplatesDALFactory } from "@app/services/pki-templates/pki-templates-dal"; import { pkiTemplatesServiceFactory } from "@app/services/pki-templates/pki-templates-service"; -import { projectDALFactory } from "@app/services/project/project-dal"; -import { projectQueueFactory } from "@app/services/project/project-queue"; -import { projectServiceFactory } from "@app/services/project/project-service"; -import { projectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal"; import { projectBotDALFactory } from "@app/services/project-bot/project-bot-dal"; import { projectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { projectEnvDALFactory } from "@app/services/project-env/project-env-dal"; @@ -306,19 +302,18 @@ import { projectKeyDALFactory } from "@app/services/project-key/project-key-dal" import { projectKeyServiceFactory } from "@app/services/project-key/project-key-service"; import { projectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { projectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service"; +import { projectDALFactory } from "@app/services/project/project-dal"; +import { projectQueueFactory } from "@app/services/project/project-queue"; +import { projectServiceFactory } from "@app/services/project/project-service"; +import { projectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal"; +import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-recipient-dal"; import { reminderDALFactory } from "@app/services/reminder/reminder-dal"; import { dailyReminderQueueServiceFactory } from "@app/services/reminder/reminder-queue"; import { reminderServiceFactory } from "@app/services/reminder/reminder-service"; -import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-recipient-dal"; import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue"; import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal"; import { roleDALFactory } from "@app/services/role/role-dal"; import { roleServiceFactory } from "@app/services/role/role-service"; -import { secretDALFactory } from "@app/services/secret/secret-dal"; -import { secretQueueFactory } from "@app/services/secret/secret-queue"; -import { secretServiceFactory } from "@app/services/secret/secret-service"; -import { secretVersionDALFactory } from "@app/services/secret/secret-version-dal"; -import { secretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal"; import { secretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal"; import { secretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service"; import { secretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; @@ -338,6 +333,11 @@ import { secretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret- import { secretV2BridgeServiceFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-service"; import { secretVersionV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-version-dal"; import { secretVersionV2TagBridgeDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal"; +import { secretDALFactory } from "@app/services/secret/secret-dal"; +import { secretQueueFactory } from "@app/services/secret/secret-queue"; +import { secretServiceFactory } from "@app/services/secret/secret-service"; +import { secretVersionDALFactory } from "@app/services/secret/secret-version-dal"; +import { secretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal"; import { serviceTokenDALFactory } from "@app/services/service-token/service-token-dal"; import { serviceTokenServiceFactory } from "@app/services/service-token/service-token-service"; import { projectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal"; @@ -353,14 +353,15 @@ import { telemetryServiceFactory } from "@app/services/telemetry/telemetry-servi import { totpConfigDALFactory } from "@app/services/totp/totp-config-dal"; import { totpServiceFactory } from "@app/services/totp/totp-service"; import { upgradePathServiceFactory } from "@app/services/upgrade-path/upgrade-path-service"; -import { userDALFactory } from "@app/services/user/user-dal"; -import { userServiceFactory } from "@app/services/user/user-service"; import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; import { userEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service"; +import { userDALFactory } from "@app/services/user/user-dal"; +import { userServiceFactory } from "@app/services/user/user-service"; import { webhookDALFactory } from "@app/services/webhook/webhook-dal"; import { webhookServiceFactory } from "@app/services/webhook/webhook-service"; import { workflowIntegrationDALFactory } from "@app/services/workflow-integration/workflow-integration-dal"; import { workflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service"; +import { registerBddNockRouter } from "@bdd_routes/bdd-nock-router"; import { injectAuditLogInfo } from "../plugins/audit-log"; import { injectAssumePrivilege } from "../plugins/auth/inject-assume-privilege"; @@ -2698,6 +2699,12 @@ export const registerRoutes = async ( await server.register(registerV3Routes, { prefix: "/api/v3" }); await server.register(registerV4Routes, { prefix: "/api/v4" }); + // 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" }); + } + server.addHook("onClose", async () => { cronJobs.forEach((job) => job.stop()); await telemetryService.flushAll(); diff --git a/backend/src/server/routes/v1/index.ts b/backend/src/server/routes/v1/index.ts index 36f091a8bd..b480a5144a 100644 --- a/backend/src/server/routes/v1/index.ts +++ b/backend/src/server/routes/v1/index.ts @@ -8,7 +8,6 @@ import { registerSecretSyncRouter, SECRET_SYNC_REGISTER_ROUTER_MAP } from "@app/ import { registerAdminRouter } from "./admin-router"; import { registerAuthRoutes } from "./auth-router"; -import { registerBddNockRouter } from "./bdd-nock-router"; import { registerProjectBotRouter } from "./bot-router"; import { registerCaRouter } from "./certificate-authority-router"; import { CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP } from "./certificate-authority-routers"; @@ -71,7 +70,6 @@ import { registerUserEngagementRouter } from "./user-engagement-router"; import { registerUserRouter } from "./user-router"; import { registerWebhookRouter } from "./webhook-router"; import { registerWorkflowIntegrationRouter } from "./workflow-integration-router"; -import { getConfig } from "@app/lib/config/env"; export const registerV1Routes = async (server: FastifyZodProvider) => { await server.register(registerSsoRouter, { prefix: "/sso" }); @@ -239,10 +237,4 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await server.register(registerEventRouter, { prefix: "/events" }); await server.register(registerUpgradePathRouter, { prefix: "/upgrade-path" }); - - // 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" }); - } }; diff --git a/backend/tsconfig.dev.json b/backend/tsconfig.dev.json new file mode 100644 index 0000000000..2fcc634d33 --- /dev/null +++ b/backend/tsconfig.dev.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "paths": { + "@bdd_routes/bdd-nock-router": ["./src/server/routes/bdd/bdd-nock-router.bdd.ts"] + } + } +} diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 523e6de5be..72fc638afb 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -24,7 +24,8 @@ "skipLibCheck": true, "baseUrl": ".", "paths": { - "@app/*": ["./src/*"] + "@app/*": ["./src/*"], + "@bdd_routes/bdd-nock-router": ["./src/server/routes/bdd/bdd-nock-router.ts"] }, "jsx": "react-jsx" }, From 9a36b55f5fcd312ccafd9344d7ae656f8d902486 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 11:52:52 -0800 Subject: [PATCH 25/53] Rename --- .../bdd/{bdd-nock-router.bdd.ts => bdd-nock-router.dev.ts} | 0 backend/tsconfig.dev.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename backend/src/server/routes/bdd/{bdd-nock-router.bdd.ts => bdd-nock-router.dev.ts} (100%) diff --git a/backend/src/server/routes/bdd/bdd-nock-router.bdd.ts b/backend/src/server/routes/bdd/bdd-nock-router.dev.ts similarity index 100% rename from backend/src/server/routes/bdd/bdd-nock-router.bdd.ts rename to backend/src/server/routes/bdd/bdd-nock-router.dev.ts diff --git a/backend/tsconfig.dev.json b/backend/tsconfig.dev.json index 2fcc634d33..0ec33991d8 100644 --- a/backend/tsconfig.dev.json +++ b/backend/tsconfig.dev.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "paths": { - "@bdd_routes/bdd-nock-router": ["./src/server/routes/bdd/bdd-nock-router.bdd.ts"] + "@bdd_routes/bdd-nock-router": ["./src/server/routes/bdd/bdd-nock-router.dev.ts"] } } } From da41eeb2be5064615ac3c599a161f7ca2d6874fc Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 11:55:45 -0800 Subject: [PATCH 26/53] Add debugger as well --- backend/nodemon.json | 4 ++-- docker-compose.dev.yml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/nodemon.json b/backend/nodemon.json index 95a7f23dfd..23c2dc5dc6 100644 --- a/backend/nodemon.json +++ b/backend/nodemon.json @@ -4,5 +4,5 @@ ], "ext": ".ts,.js", "ignore": [], - "exec": "tsx ./src/main.ts --config tsconfig.dev.json | pino-pretty --colorize --colorizeObjects --singleLine" -} \ No newline at end of file + "exec": "tsx --inspect=0.0.0.0:9229 --config tsconfig.dev.json ./src/main.ts | pino-pretty --colorize --colorizeObjects --singleLine" +} diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index e60ef1ba56..b75b6df221 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -71,6 +71,7 @@ services: ports: - 4000:4000 - 9464:9464 # for OTEL collection of Prometheus metrics + - 9229:9229 # For debugger access environment: - NODE_ENV=development - DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable From ea8de6e2fc7bab582c274a333d95f284e8275bd7 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 12:16:35 -0800 Subject: [PATCH 27/53] Fix configs --- .gitignore | 1 + backend/nodemon.json | 4 ++-- backend/tsconfig.dev.json | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b4e9a07c2f..0ad950da30 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,4 @@ cli/test/infisical-merge backend/bdd/.bdd-infisical-bootstrap-result.json /npm/bin +__pycache__ diff --git a/backend/nodemon.json b/backend/nodemon.json index 23c2dc5dc6..2542bca4dc 100644 --- a/backend/nodemon.json +++ b/backend/nodemon.json @@ -4,5 +4,5 @@ ], "ext": ".ts,.js", "ignore": [], - "exec": "tsx --inspect=0.0.0.0:9229 --config tsconfig.dev.json ./src/main.ts | pino-pretty --colorize --colorizeObjects --singleLine" -} + "exec": "tsx --tsconfig=./tsconfig.dev.json --inspect=0.0.0.0:9229 ./src/main.ts | pino-pretty --colorize --colorizeObjects --singleLine" +} \ No newline at end of file diff --git a/backend/tsconfig.dev.json b/backend/tsconfig.dev.json index 0ec33991d8..4bcbcd5e1c 100644 --- a/backend/tsconfig.dev.json +++ b/backend/tsconfig.dev.json @@ -2,6 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "paths": { + "@app/*": ["./src/*"], "@bdd_routes/bdd-nock-router": ["./src/server/routes/bdd/bdd-nock-router.dev.ts"] } } From f23884ab12f434b6445626d34fb0ffe4b2be2f30 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 12:57:20 -0800 Subject: [PATCH 28/53] It needs to have api prefix otherwise nginx won't even route it to the backend --- backend/bdd/features/steps/utils.py | 7 ++++--- backend/src/server/routes/index.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/bdd/features/steps/utils.py b/backend/bdd/features/steps/utils.py index 4ee7c8921d..de02f094de 100644 --- a/backend/bdd/features/steps/utils.py +++ b/backend/bdd/features/steps/utils.py @@ -15,6 +15,7 @@ from josepy import JSONObjectWithFields ACC_KEY_BITS = 2048 ACC_KEY_PUBLIC_EXPONENT = 65537 +NOCK_API_PREFIX = "/api/__bdd_nock__" logger = logging.getLogger(__name__) faker = Faker() @@ -265,7 +266,7 @@ def x509_cert_to_dict(cert: x509.Certificate) -> dict: def define_nock(context: Context, definitions: list[dict]): jwt_token = context.vars["AUTH_TOKEN"] response = context.http_client.post( - "/api/v1/bdd-nock/define", + f"/{NOCK_API_PREFIX}/define", headers=dict(authorization="Bearer {}".format(jwt_token)), json=dict(definitions=definitions), ) @@ -275,7 +276,7 @@ def define_nock(context: Context, definitions: list[dict]): def restore_nock(context: Context): jwt_token = context.vars["AUTH_TOKEN"] response = context.http_client.post( - "/api/v1/bdd-nock/restore", + f"{NOCK_API_PREFIX}/restore", headers=dict(authorization="Bearer {}".format(jwt_token)), json=dict(), ) @@ -285,7 +286,7 @@ def restore_nock(context: Context): def clean_all_nock(context: Context): jwt_token = context.vars["AUTH_TOKEN"] response = context.http_client.post( - "/api/v1/bdd-nock/clean-all", + f"{NOCK_API_PREFIX}/clean-all", headers=dict(authorization="Bearer {}".format(jwt_token)), json=dict(), ) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 625f549940..e617cedeb6 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -2702,7 +2702,7 @@ export const registerRoutes = async ( // 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" }); + await server.register(registerBddNockRouter, { prefix: "/api/__bdd_nock__" }); } server.addHook("onClose", async () => { From 5623c666b893e35e3a01cd7e5ec19bd75310ac6b Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 13:49:37 -0800 Subject: [PATCH 29/53] Fix nock import --- backend/src/server/routes/bdd/bdd-nock-router.dev.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/src/server/routes/bdd/bdd-nock-router.dev.ts b/backend/src/server/routes/bdd/bdd-nock-router.dev.ts index 17db89788c..ee5617686f 100644 --- a/backend/src/server/routes/bdd/bdd-nock-router.dev.ts +++ b/backend/src/server/routes/bdd/bdd-nock-router.dev.ts @@ -16,10 +16,9 @@ if (process.env.NODE_ENV !== "development") { export const registerBddNockRouter = async (server: FastifyZodProvider) => { const appCfg = getConfig(); - const importNock = () => { - // Notice: it seems like importing nock somehow increase memory usage a lots, let's import it lazily. - // eslint-disable-next-line import/no-extraneous-dependencies - return import("nock"); + const importNock = async () => { + const { default: nock } = await import("nock"); + return nock; }; const checkIfBddNockApiEnabled = () => { From 8e1a04206f93531885e3aeb74cd1e4a883eac41f Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 15:18:11 -0800 Subject: [PATCH 30/53] Tryt to fix tests --- backend/vitest.e2e.config.mts | 3 ++- backend/vitest.unit.config.mts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/vitest.e2e.config.mts b/backend/vitest.e2e.config.mts index 83554b818c..a37ca9518e 100644 --- a/backend/vitest.e2e.config.mts +++ b/backend/vitest.e2e.config.mts @@ -28,7 +28,8 @@ export default defineConfig({ }, resolve: { alias: { - "@app": path.resolve(__dirname, "./src") + "@app": path.resolve(__dirname, "./src"), + "@bdd_routes/bdd-nock-router": path.resolve(__dirname, "./src/server/routes/bdd/bdd-nock-router.dev.ts") } } }); diff --git a/backend/vitest.unit.config.mts b/backend/vitest.unit.config.mts index 97862d2884..aa56063a9c 100644 --- a/backend/vitest.unit.config.mts +++ b/backend/vitest.unit.config.mts @@ -11,7 +11,8 @@ export default defineConfig({ }, resolve: { alias: { - "@app": path.resolve(__dirname, "./src") + "@app": path.resolve(__dirname, "./src"), + "@bdd_routes/bdd-nock-router": path.resolve(__dirname, "./src/server/routes/bdd/bdd-nock-router.dev.ts") } } }); From 877d7780f68fd2407ac2441b0fc699f6e5849076 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 15:20:31 -0800 Subject: [PATCH 31/53] Better assert msg --- backend/bdd/features/steps/pki_acme.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/bdd/features/steps/pki_acme.py b/backend/bdd/features/steps/pki_acme.py index 46b10c13e0..5043b0e7df 100644 --- a/backend/bdd/features/steps/pki_acme.py +++ b/backend/bdd/features/steps/pki_acme.py @@ -615,13 +615,14 @@ def step_impl(context: Context, var_path: str, jq_query: str, expected: str): @then('the value {var_path} with jq "{jq_query}" should match pattern {regex}') def step_impl(context: Context, var_path: str, jq_query: str, regex: str): + actual_regex = replace_vars(regex, context.vars) value, result = apply_value_with_jq( context=context, var_path=var_path, jq_query=jq_query, ) - assert re.match(replace_vars(regex, context.vars), result), ( - f"{json.dumps(value)!r} with jq {jq_query!r}, the result {json.dumps(result)!r} does not match {regex!r}" + assert re.match(actual_regex, result), ( + f"{json.dumps(value)!r} with jq {jq_query!r}, the result {json.dumps(result)!r} does not match {actual_regex!r}" ) From 142316db80ac106a426a1cc0ee1d9c62e92a02c0 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 15:26:44 -0800 Subject: [PATCH 32/53] Do not enable in production --- backend/src/lib/config/env.ts | 2 +- backend/src/server/routes/bdd/bdd-nock-router.dev.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 96107306f7..11de576670 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -400,7 +400,7 @@ const envSchema = z isAcmeDevelopmentMode: data.NODE_ENV === "development" && data.ACME_DEVELOPMENT_MODE, isProductionMode: data.NODE_ENV === "production" || IS_PACKAGED, isRedisSentinelMode: Boolean(data.REDIS_SENTINEL_HOSTS), - isBddNockApiEnabled: data.NODE_ENV === "development" && data.BDD_NOCK_API_ENABLED, + isBddNockApiEnabled: data.NODE_ENV !== "production" && data.BDD_NOCK_API_ENABLED, REDIS_SENTINEL_HOSTS: data.REDIS_SENTINEL_HOSTS?.trim() ?.split(",") .map((el) => { diff --git a/backend/src/server/routes/bdd/bdd-nock-router.dev.ts b/backend/src/server/routes/bdd/bdd-nock-router.dev.ts index ee5617686f..2c119d42b7 100644 --- a/backend/src/server/routes/bdd/bdd-nock-router.dev.ts +++ b/backend/src/server/routes/bdd/bdd-nock-router.dev.ts @@ -10,8 +10,8 @@ import { AuthMode } from "@app/services/auth/auth-type"; // When running in production, we don't want to even import nock, because it's not needed and it increases memory usage a lots. // It once caused an outage in the production environment. // This is why we would rather to crash the app if it's not in development mode (in that case, Kubernetes should stop it from rolling out). -if (process.env.NODE_ENV !== "development") { - throw new Error("BDD Nock API is not enabled"); +if (process.env.NODE_ENV === "production") { + throw new Error("BDD Nock API can only be enabled in development or test mode"); } export const registerBddNockRouter = async (server: FastifyZodProvider) => { @@ -24,7 +24,7 @@ export const registerBddNockRouter = async (server: FastifyZodProvider) => { const checkIfBddNockApiEnabled = () => { // 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) { + if (appCfg.NODE_ENV === "production" || !appCfg.isBddNockApiEnabled) { throw new ForbiddenRequestError({ message: "BDD Nock API is not enabled" }); } }; From 855bc6cd73c922a6b2cd61be49c43cc90118d0af Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 15:36:12 -0800 Subject: [PATCH 33/53] Linter issue --- backend/src/server/routes/bdd/bdd-nock-router.dev.ts | 1 + backend/src/server/routes/bdd/bdd-nock-router.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/server/routes/bdd/bdd-nock-router.dev.ts b/backend/src/server/routes/bdd/bdd-nock-router.dev.ts index 2c119d42b7..c5f6001f55 100644 --- a/backend/src/server/routes/bdd/bdd-nock-router.dev.ts +++ b/backend/src/server/routes/bdd/bdd-nock-router.dev.ts @@ -17,6 +17,7 @@ if (process.env.NODE_ENV === "production") { export const registerBddNockRouter = async (server: FastifyZodProvider) => { const appCfg = getConfig(); const importNock = async () => { + // eslint-disable-next-line import/no-extraneous-dependencies const { default: nock } = await import("nock"); return nock; }; diff --git a/backend/src/server/routes/bdd/bdd-nock-router.ts b/backend/src/server/routes/bdd/bdd-nock-router.ts index 6373207d7a..a8e466b9fa 100644 --- a/backend/src/server/routes/bdd/bdd-nock-router.ts +++ b/backend/src/server/routes/bdd/bdd-nock-router.ts @@ -1,3 +1,3 @@ -export const registerBddNockRouter = async (server: FastifyZodProvider) => { +export const registerBddNockRouter = async () => { throw new Error("BDD Nock should not be enabled in production"); }; From bd8a417e56fdc7860540925041b0713e38f9632e Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 15:39:02 -0800 Subject: [PATCH 34/53] Fix bdd tests --- backend/bdd/features/steps/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/bdd/features/steps/utils.py b/backend/bdd/features/steps/utils.py index de02f094de..93269d8bcb 100644 --- a/backend/bdd/features/steps/utils.py +++ b/backend/bdd/features/steps/utils.py @@ -266,7 +266,7 @@ def x509_cert_to_dict(cert: x509.Certificate) -> dict: def define_nock(context: Context, definitions: list[dict]): jwt_token = context.vars["AUTH_TOKEN"] response = context.http_client.post( - f"/{NOCK_API_PREFIX}/define", + f"{NOCK_API_PREFIX}/define", headers=dict(authorization="Bearer {}".format(jwt_token)), json=dict(definitions=definitions), ) From 2bd904b8af64cfd7b4514ad746c1b7a5cd20fd8a Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 15:41:13 -0800 Subject: [PATCH 35/53] Add comment --- backend/src/server/routes/bdd/bdd-nock-router.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/src/server/routes/bdd/bdd-nock-router.ts b/backend/src/server/routes/bdd/bdd-nock-router.ts index a8e466b9fa..90f2ed00c6 100644 --- a/backend/src/server/routes/bdd/bdd-nock-router.ts +++ b/backend/src/server/routes/bdd/bdd-nock-router.ts @@ -1,3 +1,6 @@ export const registerBddNockRouter = async () => { + // This route is only available in development or test mode. + // The actual implementation is in the dev.ts file and will be aliased to that file in development or test mode. + // And if somehow we try to enable it in production, we will throw an error. throw new Error("BDD Nock should not be enabled in production"); }; From df395076f99ea69530c1f0a5ef8fd7286599e0a5 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 15:52:50 -0800 Subject: [PATCH 36/53] Try to fix tsup as well --- backend/tsup.config.js | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/backend/tsup.config.js b/backend/tsup.config.js index e09a21ff23..fd614635ff 100644 --- a/backend/tsup.config.js +++ b/backend/tsup.config.js @@ -2,8 +2,8 @@ import path from "node:path"; import fs from "fs/promises"; -import {replaceTscAliasPaths} from "tsc-alias"; -import {defineConfig} from "tsup"; +import { replaceTscAliasPaths } from "tsc-alias"; +import { defineConfig } from "tsup"; // Instead of using tsx or tsc for building, consider using tsup. // TSX serves as an alternative to Node.js, allowing you to build directly on the Node.js runtime. @@ -45,22 +45,26 @@ export default defineConfig({ const isRelativePath = args.path.startsWith("."); const absPath = isRelativePath ? path.join(args.resolveDir, args.path) - : path.join(args.path.replace("@app", "./src")); + : path.join( + args.path + .replace("@app", "./src") + .replace("@bdd_routes/bdd-nock-router", "./src/server/routes/bdd/bdd-nock-router.ts") + ); const isFile = await fs .stat(`${absPath}.ts`) .then((el) => el.isFile) - .catch(async (err) => { - if (err.code === "ENOTDIR") { - return true; - } + .catch(async (err) => { + if (err.code === "ENOTDIR") { + return true; + } - // If .ts file doesn't exist, try checking for .tsx file - return fs - .stat(`${absPath}.tsx`) - .then((el) => el.isFile) - .catch((err) => err.code === "ENOTDIR"); - }); + // If .ts file doesn't exist, try checking for .tsx file + return fs + .stat(`${absPath}.tsx`) + .then((el) => el.isFile) + .catch((err) => err.code === "ENOTDIR"); + }); return { path: isFile ? `${args.path}.mjs` : `${args.path}/index.mjs`, From 0edf3c8b15b9fabe11cb4598e3696e4cdffc9ad7 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 16:54:26 -0800 Subject: [PATCH 37/53] Fix ems build --- backend/tsconfig.json | 2 +- backend/tsup.config.js | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 72fc638afb..db076a30d6 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -25,7 +25,7 @@ "baseUrl": ".", "paths": { "@app/*": ["./src/*"], - "@bdd_routes/bdd-nock-router": ["./src/server/routes/bdd/bdd-nock-router.ts"] + "@bdd_routes/*": ["./src/server/routes/bdd/*"] }, "jsx": "react-jsx" }, diff --git a/backend/tsup.config.js b/backend/tsup.config.js index fd614635ff..80ec73a14f 100644 --- a/backend/tsup.config.js +++ b/backend/tsup.config.js @@ -29,7 +29,7 @@ export default defineConfig({ external: ["../../../frontend/node_modules/next/dist/server/next-server.js"], outDir: "dist", tsconfig: "./tsconfig.json", - entry: ["./src"], + entry: ["./src", "!./src/**/*.dev.ts"], sourceMap: true, skipNodeModulesBundle: true, esbuildPlugins: [ @@ -45,11 +45,7 @@ export default defineConfig({ const isRelativePath = args.path.startsWith("."); const absPath = isRelativePath ? path.join(args.resolveDir, args.path) - : path.join( - args.path - .replace("@app", "./src") - .replace("@bdd_routes/bdd-nock-router", "./src/server/routes/bdd/bdd-nock-router.ts") - ); + : path.join(args.path.replace("@app", "./src").replace("@bdd_routes", "./src/server/routes/bdd")); const isFile = await fs .stat(`${absPath}.ts`) From c342650ac6dca758a303ac845fdd9582297405e4 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 17:22:41 -0800 Subject: [PATCH 38/53] Address comments --- backend/src/server/routes/bdd/bdd-nock-router.dev.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/routes/bdd/bdd-nock-router.dev.ts b/backend/src/server/routes/bdd/bdd-nock-router.dev.ts index c5f6001f55..a400873c7c 100644 --- a/backend/src/server/routes/bdd/bdd-nock-router.dev.ts +++ b/backend/src/server/routes/bdd/bdd-nock-router.dev.ts @@ -10,7 +10,7 @@ import { AuthMode } from "@app/services/auth/auth-type"; // When running in production, we don't want to even import nock, because it's not needed and it increases memory usage a lots. // It once caused an outage in the production environment. // This is why we would rather to crash the app if it's not in development mode (in that case, Kubernetes should stop it from rolling out). -if (process.env.NODE_ENV === "production") { +if (getConfig().NODE_ENV === "production") { throw new Error("BDD Nock API can only be enabled in development or test mode"); } From af40e15cb737a0823d7ae60a963d04a4b9fe4fb1 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 17:33:33 -0800 Subject: [PATCH 39/53] Revert "Address comments" This reverts commit 57d204c3cd80f005cc0c673efbaee438e2cab6e8. --- backend/src/server/routes/bdd/bdd-nock-router.dev.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/routes/bdd/bdd-nock-router.dev.ts b/backend/src/server/routes/bdd/bdd-nock-router.dev.ts index a400873c7c..c5f6001f55 100644 --- a/backend/src/server/routes/bdd/bdd-nock-router.dev.ts +++ b/backend/src/server/routes/bdd/bdd-nock-router.dev.ts @@ -10,7 +10,7 @@ import { AuthMode } from "@app/services/auth/auth-type"; // When running in production, we don't want to even import nock, because it's not needed and it increases memory usage a lots. // It once caused an outage in the production environment. // This is why we would rather to crash the app if it's not in development mode (in that case, Kubernetes should stop it from rolling out). -if (getConfig().NODE_ENV === "production") { +if (process.env.NODE_ENV === "production") { throw new Error("BDD Nock API can only be enabled in development or test mode"); } From c3fdb10b3e94d0d1984d90638e9dad01efddb30a Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Thu, 20 Nov 2025 09:10:00 -0800 Subject: [PATCH 40/53] Fix broken tests --- .../certificate-profile-service.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 1e31d5788a..3b75c10883 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.test.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.test.ts @@ -428,7 +428,13 @@ describe("CertificateProfileService", () => { service.createProfile({ ...mockActor, projectId: "project-123", - data: validProfileData + data: { + ...validProfileData, + enrollmentType: EnrollmentType.ACME, + acmeConfig: {}, + apiConfig: undefined, + estConfig: undefined + } }) ).rejects.toThrowError( new BadRequestError({ From c5802c645cf250c79d0b30a186edea7d41b26e33 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Thu, 20 Nov 2025 09:19:54 -0800 Subject: [PATCH 41/53] Fix import order --- backend/src/server/routes/index.ts | 118 ++++++++++++++--------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index e617cedeb6..5569acbc34 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1,3 +1,4 @@ +import { registerBddNockRouter } from "@bdd_routes/bdd-nock-router"; import { CronJob } from "cron"; import { Knex } from "knex"; import { monitorEventLoopDelay } from "perf_hooks"; @@ -17,30 +18,30 @@ import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approva import { accessApprovalRequestReviewerDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-reviewer-dal"; import { accessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service"; import { assumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-service"; -import { auditLogStreamDALFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-dal"; -import { auditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service"; import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal"; import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue"; import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; +import { auditLogStreamDALFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-dal"; +import { auditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service"; import { certificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal"; import { certificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-service"; import { certificateEstServiceFactory } from "@app/ee/services/certificate-est/certificate-est-service"; -import { dynamicSecretLeaseDALFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal"; -import { dynamicSecretLeaseQueueServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue"; -import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service"; import { dynamicSecretDALFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-dal"; import { dynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service"; import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/providers"; +import { dynamicSecretLeaseDALFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal"; +import { dynamicSecretLeaseQueueServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue"; +import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service"; import { eventBusFactory } from "@app/ee/services/event/event-bus-service"; import { sseServiceFactory } from "@app/ee/services/event/event-sse-service"; import { externalKmsDALFactory } from "@app/ee/services/external-kms/external-kms-dal"; import { externalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service"; -import { gatewayV2DalFactory } from "@app/ee/services/gateway-v2/gateway-v2-dal"; -import { gatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; -import { orgGatewayConfigV2DalFactory } from "@app/ee/services/gateway-v2/org-gateway-config-v2-dal"; import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal"; import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal"; +import { gatewayV2DalFactory } from "@app/ee/services/gateway-v2/gateway-v2-dal"; +import { gatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; +import { orgGatewayConfigV2DalFactory } from "@app/ee/services/gateway-v2/org-gateway-config-v2-dal"; import { githubOrgSyncDALFactory } from "@app/ee/services/github-org-sync/github-org-sync-dal"; import { githubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service"; import { groupDALFactory } from "@app/ee/services/group/group-dal"; @@ -105,39 +106,39 @@ import { secretApprovalRequestReviewerDALFactory } from "@app/ee/services/secret import { secretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal"; import { secretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service"; import { secretReplicationServiceFactory } from "@app/ee/services/secret-replication/secret-replication-service"; -import { secretRotationV2DALFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-dal"; -import { secretRotationV2QueueServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-queue"; -import { secretRotationV2ServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-service"; import { secretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal"; import { secretRotationQueueFactory } from "@app/ee/services/secret-rotation/secret-rotation-queue"; import { secretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service"; -import { secretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal"; -import { secretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue"; -import { secretScanningV2ServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-service"; +import { secretRotationV2DALFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-dal"; +import { secretRotationV2QueueServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-queue"; +import { secretRotationV2ServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-service"; import { gitAppDALFactory } from "@app/ee/services/secret-scanning/git-app-dal"; import { gitAppInstallSessionDALFactory } from "@app/ee/services/secret-scanning/git-app-install-session-dal"; import { secretScanningDALFactory } from "@app/ee/services/secret-scanning/secret-scanning-dal"; import { secretScanningQueueFactory } from "@app/ee/services/secret-scanning/secret-scanning-queue"; import { secretScanningServiceFactory } from "@app/ee/services/secret-scanning/secret-scanning-service"; +import { secretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal"; +import { secretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue"; +import { secretScanningV2ServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-service"; import { secretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service"; import { snapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal"; import { snapshotFolderDALFactory } from "@app/ee/services/secret-snapshot/snapshot-folder-dal"; import { snapshotSecretDALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-dal"; import { snapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal"; -import { sshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal"; -import { sshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service"; +import { sshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; +import { sshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal"; +import { sshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh-certificate-authority-service"; import { sshCertificateBodyDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-body-dal"; import { sshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal"; -import { sshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal"; -import { sshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal"; -import { sshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service"; +import { sshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal"; +import { sshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service"; import { sshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal"; import { sshHostLoginUserMappingDALFactory } from "@app/ee/services/ssh-host/ssh-host-login-user-mapping-dal"; import { sshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service"; import { sshHostLoginUserDALFactory } from "@app/ee/services/ssh-host/ssh-login-user-dal"; -import { sshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; -import { sshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal"; -import { sshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh-certificate-authority-service"; +import { sshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal"; +import { sshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal"; +import { sshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service"; import { subOrgServiceFactory } from "@app/ee/services/sub-org/sub-org-service"; import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal"; import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service"; @@ -157,12 +158,16 @@ import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal"; import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service"; import { appConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; import { appConnectionServiceFactory } from "@app/services/app-connection/app-connection-service"; -import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal"; -import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { authDALFactory } from "@app/services/auth/auth-dal"; import { authLoginServiceFactory } from "@app/services/auth/auth-login-service"; import { authPaswordServiceFactory } from "@app/services/auth/auth-password-service"; import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service"; +import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal"; +import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service"; +import { certificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; +import { certificateDALFactory } from "@app/services/certificate/certificate-dal"; +import { certificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; +import { certificateServiceFactory } from "@app/services/certificate/certificate-service"; import { certificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal"; import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue"; @@ -176,17 +181,13 @@ import { certificateEstV3ServiceFactory } from "@app/services/certificate-est-v3 import { certificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; import { certificateProfileServiceFactory } from "@app/services/certificate-profile/certificate-profile-service"; import { certificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal"; -import { certificateTemplateV2DALFactory } from "@app/services/certificate-template-v2/certificate-template-v2-dal"; -import { certificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal"; import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal"; import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service"; +import { certificateTemplateV2DALFactory } from "@app/services/certificate-template-v2/certificate-template-v2-dal"; +import { certificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; import { certificateV3QueueServiceFactory } from "@app/services/certificate-v3/certificate-v3-queue"; import { certificateV3ServiceFactory } from "@app/services/certificate-v3/certificate-v3-service"; -import { certificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; -import { certificateDALFactory } from "@app/services/certificate/certificate-dal"; -import { certificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; -import { certificateServiceFactory } from "@app/services/certificate/certificate-service"; import { cmekServiceFactory } from "@app/services/cmek/cmek-service"; import { convertorServiceFactory } from "@app/services/convertor/convertor-service"; import { acmeEnrollmentConfigDALFactory } from "@app/services/enrollment-config/acme-enrollment-config-dal"; @@ -197,17 +198,21 @@ import { externalGroupOrgRoleMappingServiceFactory } from "@app/services/externa import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue"; import { externalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service"; import { vaultExternalMigrationConfigDALFactory } from "@app/services/external-migration/vault-external-migration-config-dal"; -import { folderCheckpointResourcesDALFactory } from "@app/services/folder-checkpoint-resources/folder-checkpoint-resources-dal"; import { folderCheckpointDALFactory } from "@app/services/folder-checkpoint/folder-checkpoint-dal"; -import { folderCommitChangesDALFactory } from "@app/services/folder-commit-changes/folder-commit-changes-dal"; +import { folderCheckpointResourcesDALFactory } from "@app/services/folder-checkpoint-resources/folder-checkpoint-resources-dal"; import { folderCommitDALFactory } from "@app/services/folder-commit/folder-commit-dal"; import { folderCommitQueueServiceFactory } from "@app/services/folder-commit/folder-commit-queue"; import { folderCommitServiceFactory } from "@app/services/folder-commit/folder-commit-service"; -import { folderTreeCheckpointResourcesDALFactory } from "@app/services/folder-tree-checkpoint-resources/folder-tree-checkpoint-resources-dal"; +import { folderCommitChangesDALFactory } from "@app/services/folder-commit-changes/folder-commit-changes-dal"; import { folderTreeCheckpointDALFactory } from "@app/services/folder-tree-checkpoint/folder-tree-checkpoint-dal"; +import { folderTreeCheckpointResourcesDALFactory } from "@app/services/folder-tree-checkpoint-resources/folder-tree-checkpoint-resources-dal"; import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal"; import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service"; import { healthAlertServiceFactory } from "@app/services/health-alert/health-alert-queue"; +import { identityDALFactory } from "@app/services/identity/identity-dal"; +import { identityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal"; +import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal"; +import { identityServiceFactory } from "@app/services/identity/identity-service"; import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal"; import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service"; import { identityAliCloudAuthDALFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-dal"; @@ -239,27 +244,23 @@ import { identityUaDALFactory } from "@app/services/identity-ua/identity-ua-dal" import { identityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service"; import { identityV2DALFactory } from "@app/services/identity-v2/identity-dal"; import { identityV2ServiceFactory } from "@app/services/identity-v2/identity-service"; -import { identityDALFactory } from "@app/services/identity/identity-dal"; -import { identityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal"; -import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal"; -import { identityServiceFactory } from "@app/services/identity/identity-service"; -import { integrationAuthDALFactory } from "@app/services/integration-auth/integration-auth-dal"; -import { integrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service"; import { integrationDALFactory } from "@app/services/integration/integration-dal"; import { integrationServiceFactory } from "@app/services/integration/integration-service"; +import { integrationAuthDALFactory } from "@app/services/integration-auth/integration-auth-dal"; +import { integrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service"; import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal"; import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal"; import { TKmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal"; import { kmsServiceFactory } from "@app/services/kms/kms-service"; import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types"; +import { membershipDALFactory } from "@app/services/membership/membership-dal"; +import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal"; import { membershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal"; import { membershipGroupServiceFactory } from "@app/services/membership-group/membership-group-service"; import { membershipIdentityDALFactory } from "@app/services/membership-identity/membership-identity-dal"; import { membershipIdentityServiceFactory } from "@app/services/membership-identity/membership-identity-service"; import { membershipUserDALFactory } from "@app/services/membership-user/membership-user-dal"; import { membershipUserServiceFactory } from "@app/services/membership-user/membership-user-service"; -import { membershipDALFactory } from "@app/services/membership/membership-dal"; -import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal"; import { microsoftTeamsIntegrationDALFactory } from "@app/services/microsoft-teams/microsoft-teams-integration-dal"; import { microsoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service"; import { projectMicrosoftTeamsConfigDALFactory } from "@app/services/microsoft-teams/project-microsoft-teams-config-dal"; @@ -268,20 +269,20 @@ import { notificationServiceFactory } from "@app/services/notification/notificat import { userNotificationDALFactory } from "@app/services/notification/user-notification-dal"; import { offlineUsageReportDALFactory } from "@app/services/offline-usage-report/offline-usage-report-dal"; import { offlineUsageReportServiceFactory } from "@app/services/offline-usage-report/offline-usage-report-service"; -import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service"; -import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { incidentContactDALFactory } from "@app/services/org/incident-contacts-dal"; import { orgDALFactory } from "@app/services/org/org-dal"; import { orgServiceFactory } from "@app/services/org/org-service"; +import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service"; +import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { pamAccountRotationServiceFactory } from "@app/services/pam-account-rotation/pam-account-rotation-queue"; +import { dailyExpiringPkiItemAlertQueueServiceFactory } from "@app/services/pki-alert/expiring-pki-item-alert-queue"; +import { pkiAlertDALFactory } from "@app/services/pki-alert/pki-alert-dal"; +import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service"; import { pkiAlertChannelDALFactory } from "@app/services/pki-alert-v2/pki-alert-channel-dal"; import { pkiAlertHistoryDALFactory } from "@app/services/pki-alert-v2/pki-alert-history-dal"; import { pkiAlertV2DALFactory } from "@app/services/pki-alert-v2/pki-alert-v2-dal"; import { pkiAlertV2QueueServiceFactory } from "@app/services/pki-alert-v2/pki-alert-v2-queue"; import { pkiAlertV2ServiceFactory } from "@app/services/pki-alert-v2/pki-alert-v2-service"; -import { dailyExpiringPkiItemAlertQueueServiceFactory } from "@app/services/pki-alert/expiring-pki-item-alert-queue"; -import { pkiAlertDALFactory } from "@app/services/pki-alert/pki-alert-dal"; -import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service"; import { pkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal"; import { pkiCollectionItemDALFactory } from "@app/services/pki-collection/pki-collection-item-dal"; import { pkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service"; @@ -294,6 +295,10 @@ import { pkiSyncQueueFactory } from "@app/services/pki-sync/pki-sync-queue"; import { pkiSyncServiceFactory } from "@app/services/pki-sync/pki-sync-service"; import { pkiTemplatesDALFactory } from "@app/services/pki-templates/pki-templates-dal"; import { pkiTemplatesServiceFactory } from "@app/services/pki-templates/pki-templates-service"; +import { projectDALFactory } from "@app/services/project/project-dal"; +import { projectQueueFactory } from "@app/services/project/project-queue"; +import { projectServiceFactory } from "@app/services/project/project-service"; +import { projectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal"; import { projectBotDALFactory } from "@app/services/project-bot/project-bot-dal"; import { projectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { projectEnvDALFactory } from "@app/services/project-env/project-env-dal"; @@ -302,18 +307,19 @@ import { projectKeyDALFactory } from "@app/services/project-key/project-key-dal" import { projectKeyServiceFactory } from "@app/services/project-key/project-key-service"; import { projectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { projectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service"; -import { projectDALFactory } from "@app/services/project/project-dal"; -import { projectQueueFactory } from "@app/services/project/project-queue"; -import { projectServiceFactory } from "@app/services/project/project-service"; -import { projectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal"; -import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-recipient-dal"; import { reminderDALFactory } from "@app/services/reminder/reminder-dal"; import { dailyReminderQueueServiceFactory } from "@app/services/reminder/reminder-queue"; import { reminderServiceFactory } from "@app/services/reminder/reminder-service"; +import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-recipient-dal"; import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue"; import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal"; import { roleDALFactory } from "@app/services/role/role-dal"; import { roleServiceFactory } from "@app/services/role/role-service"; +import { secretDALFactory } from "@app/services/secret/secret-dal"; +import { secretQueueFactory } from "@app/services/secret/secret-queue"; +import { secretServiceFactory } from "@app/services/secret/secret-service"; +import { secretVersionDALFactory } from "@app/services/secret/secret-version-dal"; +import { secretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal"; import { secretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal"; import { secretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service"; import { secretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; @@ -333,11 +339,6 @@ import { secretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret- import { secretV2BridgeServiceFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-service"; import { secretVersionV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-version-dal"; import { secretVersionV2TagBridgeDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal"; -import { secretDALFactory } from "@app/services/secret/secret-dal"; -import { secretQueueFactory } from "@app/services/secret/secret-queue"; -import { secretServiceFactory } from "@app/services/secret/secret-service"; -import { secretVersionDALFactory } from "@app/services/secret/secret-version-dal"; -import { secretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal"; import { serviceTokenDALFactory } from "@app/services/service-token/service-token-dal"; import { serviceTokenServiceFactory } from "@app/services/service-token/service-token-service"; import { projectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal"; @@ -353,15 +354,14 @@ import { telemetryServiceFactory } from "@app/services/telemetry/telemetry-servi import { totpConfigDALFactory } from "@app/services/totp/totp-config-dal"; import { totpServiceFactory } from "@app/services/totp/totp-service"; import { upgradePathServiceFactory } from "@app/services/upgrade-path/upgrade-path-service"; -import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; -import { userEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service"; import { userDALFactory } from "@app/services/user/user-dal"; import { userServiceFactory } from "@app/services/user/user-service"; +import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; +import { userEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service"; import { webhookDALFactory } from "@app/services/webhook/webhook-dal"; import { webhookServiceFactory } from "@app/services/webhook/webhook-service"; import { workflowIntegrationDALFactory } from "@app/services/workflow-integration/workflow-integration-dal"; import { workflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service"; -import { registerBddNockRouter } from "@bdd_routes/bdd-nock-router"; import { injectAuditLogInfo } from "../plugins/audit-log"; import { injectAssumePrivilege } from "../plugins/auth/inject-assume-privilege"; From ae48787834103050217da3b5e4331f2f1ac817a2 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 17:29:39 -0800 Subject: [PATCH 42/53] Return external account required --- backend/src/ee/services/pki-acme/pki-acme-schemas.ts | 10 +++++++++- backend/src/ee/services/pki-acme/pki-acme-service.ts | 5 ++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/backend/src/ee/services/pki-acme/pki-acme-schemas.ts b/backend/src/ee/services/pki-acme/pki-acme-schemas.ts index 58ca7e8333..23b86d172c 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-schemas.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-schemas.ts @@ -58,7 +58,15 @@ export const GetAcmeDirectoryResponseSchema = z.object({ newNonce: z.string(), newAccount: z.string(), newOrder: z.string(), - revokeCert: z.string().optional() + revokeCert: z.string().optional(), + meta: z + .object({ + termsOfService: z.string().optional(), + website: z.string().optional(), + caaIdentities: z.array(z.string()).optional(), + externalAccountRequired: z.boolean().optional() + }) + .optional() }); // New Account payload schema 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 43da08b1cd..ccacf816e8 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -353,7 +353,10 @@ export const pkiAcmeServiceFactory = ({ return { newNonce: buildUrl(profile.id, "/new-nonce"), newAccount: buildUrl(profile.id, "/new-account"), - newOrder: buildUrl(profile.id, "/new-order") + newOrder: buildUrl(profile.id, "/new-order"), + meta: { + externalAccountRequired: true + } }; }; From b251cf480206488658f1ae2cd505eead94832acb Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 17 Nov 2025 17:40:29 -0800 Subject: [PATCH 43/53] Update BDD test to ensure external account required value is returned --- .../pki/acme/{dicrectory.feature => directory.feature} | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) rename backend/bdd/features/pki/acme/{dicrectory.feature => directory.feature} (86%) diff --git a/backend/bdd/features/pki/acme/dicrectory.feature b/backend/bdd/features/pki/acme/directory.feature similarity index 86% rename from backend/bdd/features/pki/acme/dicrectory.feature rename to backend/bdd/features/pki/acme/directory.feature index 664ff7457d..53084a6817 100644 --- a/backend/bdd/features/pki/acme/dicrectory.feature +++ b/backend/bdd/features/pki/acme/directory.feature @@ -9,6 +9,9 @@ Feature: Directory { "newNonce": "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/new-nonce", "newAccount": "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/new-account", - "newOrder": "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/new-order" + "newOrder": "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/new-order", + "meta": { + "externalAccountRequired": true + } } """ From d190fb15c986362d6a09fdecc6352a1683538b56 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Tue, 18 Nov 2025 09:20:22 -0800 Subject: [PATCH 44/53] Add test to reproduce the problem --- backend/bdd/features/pki/acme/account.feature | 23 ++++++++++++++-- backend/bdd/features/steps/pki_acme.py | 27 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/backend/bdd/features/pki/acme/account.feature b/backend/bdd/features/pki/acme/account.feature index 589c5ab244..04b1f29c6a 100644 --- a/backend/bdd/features/pki/acme/account.feature +++ b/backend/bdd/features/pki/acme/account.feature @@ -11,8 +11,27 @@ Feature: Account When I have an ACME client connecting to "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory" Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account And I memorize acme_account.uri as account_uri - And I find the existing ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account - And the value acme_account.uri should be equal to "{account_uri}" + And I find the existing ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as retrieved_account + And the value retrieved_account.uri should be equal to "{account_uri}" + + # Note: This is a very special case for cert-manager. + # There's a bug in their ACME client implementation, they don't take the account KID value they have + # and relying on a '{"onlyReturnExisting": true}' new-account request to find out their KID value. + # But the problem is, that new-account request doesn't come with EAB. And while the get existing account operation + # fails, they just discard the error and proceed to request a new order. Since no KID provided, their ACME + # client will send JWK instead. As a result, we are seeing KID not provide in header error for the new-order + # endpoint. + # + # To solve the problem, we lose the check for EAB a bit for the onlyReturnExisting new account request + # ref: https://github.com/cert-manager/cert-manager/issues/7388#issuecomment-3535630925 + Scenario: Create a new account with EAB then retrieve it without EAB + Given I have an ACME cert profile as "acme_profile" + When I have an ACME client connecting to "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory" + Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account + And I memorize acme_account.uri as account_uri + And I find the existing ACME account without EAB as retrieved_account + And the value error with should be absent + And the value retrieved_account.uri should be equal to "{account_uri}" Scenario: Create a new account without EAB Given I have an ACME cert profile as "acme_profile" diff --git a/backend/bdd/features/steps/pki_acme.py b/backend/bdd/features/steps/pki_acme.py index 5043b0e7df..e194fdb84d 100644 --- a/backend/bdd/features/steps/pki_acme.py +++ b/backend/bdd/features/steps/pki_acme.py @@ -434,6 +434,20 @@ def step_impl(context: Context, email: str, kid: str, secret: str, account_var: ) +@then("I find the existing ACME account without EAB as {account_var}") +def step_impl(context: Context, account_var: str): + acme_client = context.acme_client + registration = messages.NewRegistration.from_data( + only_return_existing=True, + ) + # Reset account so that it will send JWK instead of KID + acme_client.net.account = None + try: + context.vars[account_var] = acme_client.new_account(registration) + except Exception as exp: + context.vars["error"] = exp + + @then("I register a new ACME account with email {email} without EAB") def step_impl(context: Context, email: str): acme_client = context.acme_client @@ -600,6 +614,19 @@ def step_impl(context: Context, var_path: str, jq_query: str): ) +@then("the value {var_path} with should be absent") +def step_impl(context: Context, var_path: str): + try: + value = eval_var(context, var_path) + except Exception as exp: + if isinstance(exp, KeyError): + return + raise + assert False, ( + f"value at {var_path!r} should be absent, but we got this instead: {value!r}" + ) + + @then('the value {var_path} with jq "{jq_query}" should be equal to {expected}') def step_impl(context: Context, var_path: str, jq_query: str, expected: str): value, result = apply_value_with_jq( From a668822e192495c4163ab0701d18b55d5566a5fb Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Tue, 18 Nov 2025 09:28:10 -0800 Subject: [PATCH 45/53] Only check EAB when creating a new account --- backend/bdd/features/pki/acme/account.feature | 9 --- .../ee/services/pki-acme/pki-acme-service.ts | 63 +++++++++++-------- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/backend/bdd/features/pki/acme/account.feature b/backend/bdd/features/pki/acme/account.feature index 04b1f29c6a..5214464a3c 100644 --- a/backend/bdd/features/pki/acme/account.feature +++ b/backend/bdd/features/pki/acme/account.feature @@ -15,15 +15,6 @@ Feature: Account And the value retrieved_account.uri should be equal to "{account_uri}" # Note: This is a very special case for cert-manager. - # There's a bug in their ACME client implementation, they don't take the account KID value they have - # and relying on a '{"onlyReturnExisting": true}' new-account request to find out their KID value. - # But the problem is, that new-account request doesn't come with EAB. And while the get existing account operation - # fails, they just discard the error and proceed to request a new order. Since no KID provided, their ACME - # client will send JWK instead. As a result, we are seeing KID not provide in header error for the new-order - # endpoint. - # - # To solve the problem, we lose the check for EAB a bit for the onlyReturnExisting new account request - # ref: https://github.com/cert-manager/cert-manager/issues/7388#issuecomment-3535630925 Scenario: Create a new account with EAB then retrieve it without EAB Given I have an ACME cert profile as "acme_profile" When I have an ACME client connecting to "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory" 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 ccacf816e8..d9c28b4986 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -389,11 +389,48 @@ export const pkiAcmeServiceFactory = ({ payload: TCreateAcmeAccountPayload; }): Promise> => { const profile = await validateAcmeProfile(profileId); + const publicKeyThumbprint = await calculateJwkThumbprint(jwk, "sha256"); + + if (onlyReturnExisting) { + const existingAccount: TPkiAcmeAccounts | null = await acmeAccountDAL.findByProfileIdAndPublicKeyThumbprintAndAlg( + profileId, + alg, + publicKeyThumbprint + ); + if (!existingAccount) { + throw new AcmeAccountDoesNotExistError({ message: "ACME account not found" }); + } + // With the same public key, we found an existing account, just return it + return { + status: 200, + body: { + status: "valid", + contact: existingAccount.emails, + orders: buildUrl(profile.id, `/accounts/${existingAccount.id}/orders`) + }, + headers: { + Location: buildUrl(profile.id, `/accounts/${existingAccount.id}`), + Link: `<${buildUrl(profile.id, "/directory")}>;rel="index"` + } + }; + } + + // Note: We only check EAB for the new account request. This is a very special case for cert-manager. + // There's a bug in their ACME client implementation, they don't take the account KID value they have + // and relying on a '{"onlyReturnExisting": true}' new-account request to find out their KID value. + // But the problem is, that new-account request doesn't come with EAB. And while the get existing account operation + // fails, they just discard the error and proceed to request a new order. Since no KID provided, their ACME + // client will send JWK instead. As a result, we are seeing KID not provide in header error for the new-order + // endpoint. + // + // To solve the problem, we lose the check for EAB a bit for the onlyReturnExisting new account request. + // It should be fine as we've already checked EAB when they created the account. + // And the private key ownership indicating they are the same user. + // ref: https://github.com/cert-manager/cert-manager/issues/7388#issuecomment-3535630925 if (!externalAccountBinding) { throw new AcmeExternalAccountRequiredError({ message: "External account binding is required" }); } - const publicKeyThumbprint = await calculateJwkThumbprint(jwk, "sha256"); const certificateManagerKmsId = await getProjectKmsCertificateKeyId({ projectId: profile.projectId, projectDAL, @@ -444,30 +481,6 @@ export const pkiAcmeServiceFactory = ({ }); } - const existingAccount: TPkiAcmeAccounts | null = await acmeAccountDAL.findByProfileIdAndPublicKeyThumbprintAndAlg( - profileId, - alg, - publicKeyThumbprint - ); - if (onlyReturnExisting && !existingAccount) { - throw new AcmeAccountDoesNotExistError({ message: "ACME account not found" }); - } - if (existingAccount) { - // With the same public key, we found an existing account, just return it - return { - status: 200, - body: { - status: "valid", - contact: existingAccount.emails, - orders: buildUrl(profile.id, `/accounts/${existingAccount.id}/orders`) - }, - headers: { - Location: buildUrl(profile.id, `/accounts/${existingAccount.id}`), - Link: `<${buildUrl(profile.id, "/directory")}>;rel="index"` - } - }; - } - const newAccount = await acmeAccountDAL.create({ profileId: profile.id, alg, From 9ae862fc1fbd2e68ae67f885f250a8a0ce97159b Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Tue, 18 Nov 2025 09:46:40 -0800 Subject: [PATCH 46/53] Fix find existing account --- backend/bdd/features/steps/pki_acme.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/backend/bdd/features/steps/pki_acme.py b/backend/bdd/features/steps/pki_acme.py index e194fdb84d..1390cab3f9 100644 --- a/backend/bdd/features/steps/pki_acme.py +++ b/backend/bdd/features/steps/pki_acme.py @@ -437,13 +437,10 @@ def step_impl(context: Context, email: str, kid: str, secret: str, account_var: @then("I find the existing ACME account without EAB as {account_var}") def step_impl(context: Context, account_var: str): acme_client = context.acme_client - registration = messages.NewRegistration.from_data( - only_return_existing=True, - ) - # Reset account so that it will send JWK instead of KID - acme_client.net.account = None + # registration = messages.RegistrationResource.from_json(dict(uri="")) + registration = acme_client.net.account try: - context.vars[account_var] = acme_client.new_account(registration) + context.vars[account_var] = acme_client.query_registration(registration) except Exception as exp: context.vars["error"] = exp From b50f03d4fb961dec4b3ac9ff322280c9fc69fce5 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Tue, 18 Nov 2025 15:20:30 -0800 Subject: [PATCH 47/53] Add test case for both jwk and kid provided --- .../features/pki/acme/access-control.feature | 50 ++++++++++++++++++- .../pki-acme/pki-acme-challenge-service.ts | 7 ++- .../ee/services/pki-acme/pki-acme-service.ts | 3 ++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/backend/bdd/features/pki/acme/access-control.feature b/backend/bdd/features/pki/acme/access-control.feature index 6615d00f80..50588be765 100644 --- a/backend/bdd/features/pki/acme/access-control.feature +++ b/backend/bdd/features/pki/acme/access-control.feature @@ -221,7 +221,6 @@ Feature: Access Control | order | .authorizations[0].uri | auth_uri | {auth_uri} | | | order | .authorizations[0].body.challenges[0].url | challenge_uri | {challenge_uri} | {} | - Scenario Outline: URL mismatch Given I have an ACME cert profile as "acme_profile" When I have an ACME client connecting to "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory" @@ -271,3 +270,52 @@ Feature: Access Control | order | .authorizations[0].uri | auth_uri | {auth_uri} | https://example.com/acmes/auths/FOOBAR | URL mismatch in the protected header | | order | .authorizations[0].body.challenges[0].url | challenge_uri | {challenge_uri} | BAD | Invalid URL in the protected header | | order | .authorizations[0].body.challenges[0].url | challenge_uri | {challenge_uri} | https://example.com/acmes/challenges/FOOBAR | URL mismatch in the protected header | + + Scenario Outline: Send KID and JWK in the same time + Given I have an ACME cert profile as "acme_profile" + When I have an ACME client connecting to "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory" + Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account + And I memorize acme_account.uri with jq "capture("/(?[^/]+)$") | .id" as account_id + When I create certificate signing request as csr + Then I add names to certificate signing request csr + """ + { + "COMMON_NAME": "localhost" + } + """ + Then I create a RSA private key pair as cert_key + And I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format + And I submit the certificate signing request PEM csr_pem certificate order to the ACME server as order + And I peak and memorize the next nonce as nonce_value + And I memorize with jq "" as + When I send a raw ACME request to "" + """ + { + "protected": { + "alg": "RS256", + "nonce": "{nonce_value}", + "url": "", + "kid": "{acme_account.uri}", + "jwk": { + "n": "mmEWxUv2lUYDZe_M2FXJ_WDXgHoEG7PVvg-dfz1STzyMwx0qvM66KMenXSyVA0r-_Ssb6p8VexSWGOFKskM4ryKUihn2KNH5e8nXZBqzqYeKQ8vqaCdaWzTxFI1dg0xhk0CWptkZHxpRpLalztFJ1Pq7L2qvQOM2YT7wPYbwQhpaSiVNXAb1W4FwAPyC04v1mHehvST-esaDT7j_5-eU5cCcmyi4_g5nBawcinOjj5o3VCg4X8UjK--AjhAyYHx1nRMr-7xk4x-0VIpQ_OODjLB3WzN8s1YEb0Jx5Bv1JyeCw35zahqs3fAFyRje-p5ENk9NCxfz5x9ZGkszkkNt0Q", + "e": "AQAB", + "kty": "RSA" + } + }, + "payload": {} + } + """ + Then the value response.status_code should be equal to 400 + And the value response with jq ".status" should be equal to 400 + And the value response with jq ".type" should be equal to "urn:ietf:params:acme:error:malformed" + And the value response with jq ".detail" should be equal to "Both JWK and KID are provided in the protected header" + + Examples: Endpoints + | src_var | jq | dest_var | url | + | order | . | not_used | {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/accounts/{account_id}/orders | + | order | . | not_used | {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/new-order | + | order | . | not_used | {order.uri} | + | order | . | not_used | {order.uri}/finalize | + | order | . | not_used | {order.uri}/certificate | + | order | .authorizations[0].uri | auth_uri | {auth_uri} | + | order | .authorizations[0].body.challenges[0].url | challenge_uri | {challenge_uri} | diff --git a/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts b/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts index 9148b03366..7a3747fedf 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts @@ -74,7 +74,12 @@ export const pkiAcmeChallengeServiceFactory = ({ // Notice: well, we are in a transaction, ideally we should not hold transaction and perform // a long running operation for long time. But assuming we are not performing a tons of // challenge validation at the same time, it should be fine. - const challengeResponse = await fetch(challengeUrl, { signal: AbortSignal.timeout(timeoutMs) }); + const challengeResponse = await fetch(challengeUrl, { + // In case if we override the host in the development mode, still provide the original host in the header + // to help the upstream server to validate the request + headers: { Host: host }, + signal: AbortSignal.timeout(timeoutMs) + }); if (challengeResponse.status !== 200) { throw new AcmeIncorrectResponseError({ message: `ACME challenge response is not 200: ${challengeResponse.status}` 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 d9c28b4986..71a2383b62 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -206,6 +206,9 @@ export const pkiAcmeServiceFactory = ({ const { protectedHeader: rawProtectedHeader, payload: rawPayload } = result; try { const protectedHeader = ProtectedHeaderSchema.parse(rawProtectedHeader); + if (protectedHeader.jwk && protectedHeader.kid) { + throw new AcmeMalformedError({ message: "Both JWK and KID are provided in the protected header" }); + } const parsedUrl = (() => { try { return new URL(protectedHeader.url); From d341dd0fb75987dc0890b7ae68469fe9251aff80 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Tue, 18 Nov 2025 16:59:06 -0800 Subject: [PATCH 48/53] Return existing even if `onlyReturnExisting` is not set to ture while creating the new one --- .../ee/services/pki-acme/pki-acme-service.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) 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 71a2383b62..1d724495fb 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -291,6 +291,7 @@ export const pkiAcmeServiceFactory = ({ url, rawJwsPayload, getJWK: async (protectedHeader) => { + // get jwk instead of kid if (!protectedHeader.kid) { throw new AcmeMalformedError({ message: "KID is required in the protected header" }); } @@ -394,16 +395,15 @@ export const pkiAcmeServiceFactory = ({ const profile = await validateAcmeProfile(profileId); const publicKeyThumbprint = await calculateJwkThumbprint(jwk, "sha256"); + const existingAccount: TPkiAcmeAccounts | null = await acmeAccountDAL.findByProfileIdAndPublicKeyThumbprintAndAlg( + profileId, + alg, + publicKeyThumbprint + ); if (onlyReturnExisting) { - const existingAccount: TPkiAcmeAccounts | null = await acmeAccountDAL.findByProfileIdAndPublicKeyThumbprintAndAlg( - profileId, - alg, - publicKeyThumbprint - ); if (!existingAccount) { throw new AcmeAccountDoesNotExistError({ message: "ACME account not found" }); } - // With the same public key, we found an existing account, just return it return { status: 200, body: { @@ -433,6 +433,20 @@ export const pkiAcmeServiceFactory = ({ if (!externalAccountBinding) { throw new AcmeExternalAccountRequiredError({ message: "External account binding is required" }); } + if (existingAccount) { + return { + status: 200, + body: { + status: "valid", + contact: existingAccount.emails, + orders: buildUrl(profile.id, `/accounts/${existingAccount.id}/orders`) + }, + headers: { + Location: buildUrl(profile.id, `/accounts/${existingAccount.id}`), + Link: `<${buildUrl(profile.id, "/directory")}>;rel="index"` + } + }; + } const certificateManagerKmsId = await getProjectKmsCertificateKeyId({ projectId: profile.projectId, From d9d6d3b3d985ccc3118cc6c29aff6279f061e543 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Tue, 18 Nov 2025 18:48:42 -0800 Subject: [PATCH 49/53] Add test to ensure that we don't create duplicate account --- backend/bdd/features/pki/acme/account.feature | 9 +++++++++ backend/bdd/features/steps/pki_acme.py | 2 ++ 2 files changed, 11 insertions(+) diff --git a/backend/bdd/features/pki/acme/account.feature b/backend/bdd/features/pki/acme/account.feature index 5214464a3c..14e304c6ca 100644 --- a/backend/bdd/features/pki/acme/account.feature +++ b/backend/bdd/features/pki/acme/account.feature @@ -6,6 +6,15 @@ Feature: Account Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account And the value acme_account.uri with jq "." should match pattern {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/accounts/(.+) + Scenario: Create a new account with the same key pair twice + Given I have an ACME cert profile as "acme_profile" + When I have an ACME client connecting to "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory" + Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account + And I memorize acme_account.uri as kid + And I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account2 + And the value error.__class__.__name__ should be equal to "ConflictError" + And the value error.location should be equal to "{kid}" + Scenario: Find an existing account Given I have an ACME cert profile as "acme_profile" When I have an ACME client connecting to "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory" diff --git a/backend/bdd/features/steps/pki_acme.py b/backend/bdd/features/steps/pki_acme.py index 1390cab3f9..51ab4209e4 100644 --- a/backend/bdd/features/steps/pki_acme.py +++ b/backend/bdd/features/steps/pki_acme.py @@ -387,6 +387,8 @@ def register_account_with_eab( ): acme_client = context.acme_client account_public_key = acme_client.net.key.public_key() + # clear the account in case if we want to register twice + acme_client.net.account = None if hasattr(context, "alt_eab_url"): eab_directory = messages.Directory.from_json( {"newAccount": context.alt_eab_url} From 022c3802e3a2124350be0274c492c1a832f498f1 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Tue, 18 Nov 2025 19:00:10 -0800 Subject: [PATCH 50/53] Add unique constraint for public key under the same profile --- ...-acme-account-public-key-and-profile-id.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 backend/src/db/migrations/20251119025017_add-unique-constraint-for-pki-acme-account-public-key-and-profile-id.ts diff --git a/backend/src/db/migrations/20251119025017_add-unique-constraint-for-pki-acme-account-public-key-and-profile-id.ts b/backend/src/db/migrations/20251119025017_add-unique-constraint-for-pki-acme-account-public-key-and-profile-id.ts new file mode 100644 index 0000000000..5bc4601e33 --- /dev/null +++ b/backend/src/db/migrations/20251119025017_add-unique-constraint-for-pki-acme-account-public-key-and-profile-id.ts @@ -0,0 +1,32 @@ +import { Knex } from "knex"; + +import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists"; +import { TableName } from "@app/db/schemas"; + +const CONSTRAINT_NAME = "unique_pki_acme_account_public_key_and_profile_id"; + +export async function up(knex: Knex): Promise { + if (await knex.schema.hasTable(TableName.PkiAcmeAccount)) { + const hasProfileId = await knex.schema.hasColumn(TableName.PkiAcmeAccount, "profileId"); + const hasPublicKeyThumbprint = await knex.schema.hasColumn(TableName.PkiAcmeAccount, "publicKeyThumbprint"); + + if (hasProfileId && hasPublicKeyThumbprint) { + await knex.schema.alterTable(TableName.PkiAcmeAccount, (table) => { + table.unique(["profileId", "publicKeyThumbprint"], { indexName: CONSTRAINT_NAME }); + }); + } + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasTable(TableName.PkiAcmeAccount)) { + const hasProfileId = await knex.schema.hasColumn(TableName.PkiAcmeAccount, "profileId"); + const hasPublicKeyThumbprint = await knex.schema.hasColumn(TableName.PkiAcmeAccount, "publicKeyThumbprint"); + + await knex.schema.alterTable(TableName.PkiAcmeAccount, async () => { + if (hasProfileId && hasPublicKeyThumbprint) { + await dropConstraintIfExists(TableName.PkiAcmeAccount, CONSTRAINT_NAME, knex); + } + }); + } +} From 2026fcd628a48b55070d062fd3f79148113bec01 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Tue, 18 Nov 2025 19:22:37 -0800 Subject: [PATCH 51/53] Isolate vars --- backend/bdd/features/environment.py | 17 +++++++++++------ backend/bdd/features/steps/pki_acme.py | 13 ++++++++++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/backend/bdd/features/environment.py b/backend/bdd/features/environment.py index 9a2e9f90b0..dce0eb5389 100644 --- a/backend/bdd/features/environment.py +++ b/backend/bdd/features/environment.py @@ -185,28 +185,33 @@ def bootstrap_infisical(context: Context): def before_all(context: Context): + base_vars = { + "BASE_URL": BASE_URL, + "PEBBLE_URL": PEBBLE_URL, + } if BOOTSTRAP_INFISICAL: details = bootstrap_infisical(context) - context.vars = { - "BASE_URL": BASE_URL, - "PEBBLE_URL": PEBBLE_URL, + vars = base_vars | { "PROJECT_ID": details["project"]["id"], "CERT_CA_ID": details["ca"]["id"], "CERT_TEMPLATE_ID": details["cert_template"]["id"], "AUTH_TOKEN": details["auth_token"], } else: - context.vars = { - "BASE_URL": BASE_URL, - "PEBBLE_URL": PEBBLE_URL, + vars = base_vars | { "PROJECT_ID": PROJECT_ID, "CERT_CA_ID": CERT_CA_ID, "CERT_TEMPLATE_ID": CERT_TEMPLATE_ID, "AUTH_TOKEN": AUTH_TOKEN, } + context._initial_vars = vars context.http_client = httpx.Client(base_url=BASE_URL) +def before_scenario(context: Context, scenario: typing.Any): + context.vars = deepcopy(context._initial_vars) + + def after_scenario(context: Context, scenario: typing.Any): if hasattr(context, "web_server"): context.web_server.shutdown_and_server_close() diff --git a/backend/bdd/features/steps/pki_acme.py b/backend/bdd/features/steps/pki_acme.py index 51ab4209e4..353ec942da 100644 --- a/backend/bdd/features/steps/pki_acme.py +++ b/backend/bdd/features/steps/pki_acme.py @@ -387,8 +387,9 @@ def register_account_with_eab( ): acme_client = context.acme_client account_public_key = acme_client.net.key.public_key() - # clear the account in case if we want to register twice - acme_client.net.account = None + if not only_return_existing: + # clear the account in case if we want to register twice + acme_client.net.account = None if hasattr(context, "alt_eab_url"): eab_directory = messages.Directory.from_json( {"newAccount": context.alt_eab_url} @@ -408,8 +409,14 @@ def register_account_with_eab( only_return_existing=only_return_existing, ) try: - context.vars[account_var] = acme_client.new_account(registration) + if not only_return_existing: + context.vars[account_var] = acme_client.new_account(registration) + else: + context.vars[account_var] = acme_client.query_registration( + acme_client.net.account + ) except Exception as exp: + logger.error(f"Failed to register: {exp}", exc_info=True) context.vars["error"] = exp From ce418036294e30dfe3e92ece5f9c1e1dd66a6ebf Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Tue, 18 Nov 2025 19:24:20 -0800 Subject: [PATCH 52/53] deepcopy --- backend/bdd/features/environment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/bdd/features/environment.py b/backend/bdd/features/environment.py index dce0eb5389..976998c72b 100644 --- a/backend/bdd/features/environment.py +++ b/backend/bdd/features/environment.py @@ -3,6 +3,7 @@ import os import pathlib import typing +from copy import deepcopy import httpx from behave.runner import Context From 2093dee2d1a5103eef43c439af4cf8357bdeb1a8 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Tue, 18 Nov 2025 19:40:04 -0800 Subject: [PATCH 53/53] Added todo --- backend/src/ee/services/pki-acme/pki-acme-service.ts | 1 + 1 file changed, 1 insertion(+) 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 1d724495fb..4f560ade76 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -498,6 +498,7 @@ export const pkiAcmeServiceFactory = ({ }); } + // TODO: handle unique constraint violation error, should be very very rare const newAccount = await acmeAccountDAL.create({ profileId: profile.id, alg,