mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 20:51:50 +00:00
Merge branch 'heads/main' into daniel/kms-sign-verify
This commit is contained in:
@@ -39,3 +39,10 @@ export const GenericResourceNameSchema = z
|
||||
])(val),
|
||||
"Name can only contain alphanumeric characters, dashes, underscores, and spaces"
|
||||
);
|
||||
|
||||
export const BaseSecretNameSchema = z.string().trim().min(1);
|
||||
|
||||
export const SecretNameSchema = BaseSecretNameSchema.refine(
|
||||
(el) => !el.includes(" "),
|
||||
"Secret name cannot contain spaces."
|
||||
).refine((el) => !el.includes(":"), "Secret name cannot contain colon.");
|
||||
|
||||
@@ -76,6 +76,9 @@ import { secretReplicationServiceFactory } from "@app/ee/services/secret-replica
|
||||
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 { 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";
|
||||
@@ -406,6 +409,8 @@ export const registerRoutes = async (
|
||||
const gatewayDAL = gatewayDALFactory(db);
|
||||
const projectGatewayDAL = projectGatewayDALFactory(db);
|
||||
|
||||
const secretRotationV2DAL = secretRotationV2DALFactory(db, folderDAL);
|
||||
|
||||
const permissionService = permissionServiceFactory({
|
||||
permissionDAL,
|
||||
orgRoleDAL,
|
||||
@@ -662,6 +667,7 @@ export const registerRoutes = async (
|
||||
});
|
||||
|
||||
const orgAdminService = orgAdminServiceFactory({
|
||||
smtpService,
|
||||
projectDAL,
|
||||
permissionService,
|
||||
projectUserMembershipRoleDAL,
|
||||
@@ -964,7 +970,8 @@ export const registerRoutes = async (
|
||||
projectSlackConfigDAL,
|
||||
slackIntegrationDAL,
|
||||
projectTemplateService,
|
||||
groupProjectDAL
|
||||
groupProjectDAL,
|
||||
smtpService
|
||||
});
|
||||
|
||||
const projectEnvService = projectEnvServiceFactory({
|
||||
@@ -1497,6 +1504,35 @@ export const registerRoutes = async (
|
||||
permissionService
|
||||
});
|
||||
|
||||
const secretRotationV2Service = secretRotationV2ServiceFactory({
|
||||
secretRotationV2DAL,
|
||||
permissionService,
|
||||
appConnectionService,
|
||||
folderDAL,
|
||||
projectBotService,
|
||||
licenseService,
|
||||
kmsService,
|
||||
auditLogService,
|
||||
secretV2BridgeDAL,
|
||||
secretTagDAL,
|
||||
secretVersionTagV2BridgeDAL,
|
||||
secretVersionV2BridgeDAL,
|
||||
keyStore,
|
||||
resourceMetadataDAL,
|
||||
snapshotService,
|
||||
secretQueueService,
|
||||
queueService
|
||||
});
|
||||
|
||||
await secretRotationV2QueueServiceFactory({
|
||||
secretRotationV2Service,
|
||||
secretRotationV2DAL,
|
||||
queueService,
|
||||
projectDAL,
|
||||
projectMembershipDAL,
|
||||
smtpService
|
||||
});
|
||||
|
||||
await superAdminService.initServerCfg();
|
||||
|
||||
// setup the communication with license key server
|
||||
@@ -1598,7 +1634,8 @@ export const registerRoutes = async (
|
||||
secretSync: secretSyncService,
|
||||
kmip: kmipService,
|
||||
kmipOperation: kmipOperationService,
|
||||
gateway: gatewayService
|
||||
gateway: gatewayService,
|
||||
secretRotationV2: secretRotationV2Service
|
||||
});
|
||||
|
||||
const cronJobs: CronJob[] = [];
|
||||
@@ -1607,6 +1644,10 @@ export const registerRoutes = async (
|
||||
if (rateLimitSyncJob) {
|
||||
cronJobs.push(rateLimitSyncJob);
|
||||
}
|
||||
const licenseSyncJob = await licenseService.initializeBackgroundSync();
|
||||
if (licenseSyncJob) {
|
||||
cronJobs.push(licenseSyncJob);
|
||||
}
|
||||
}
|
||||
|
||||
server.decorate<FastifyZodProvider["store"]>("store", {
|
||||
|
||||
@@ -134,7 +134,9 @@ export const secretRawSchema = z.object({
|
||||
membershipId: z.string().nullable().optional()
|
||||
})
|
||||
.optional()
|
||||
.nullable()
|
||||
.nullable(),
|
||||
isRotatedSecret: z.boolean().optional(),
|
||||
rotationId: z.string().uuid().nullish()
|
||||
});
|
||||
|
||||
export const ProjectPermissionSchema = z.object({
|
||||
|
||||
@@ -24,8 +24,14 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
method: I["method"];
|
||||
credentials: I["credentials"];
|
||||
description?: string | null;
|
||||
isPlatformManagedCredentials?: boolean;
|
||||
}>;
|
||||
updateSchema: z.ZodType<{
|
||||
name?: string;
|
||||
credentials?: I["credentials"];
|
||||
description?: string | null;
|
||||
isPlatformManagedCredentials?: boolean;
|
||||
}>;
|
||||
updateSchema: z.ZodType<{ name?: string; credentials?: I["credentials"]; description?: string | null }>;
|
||||
sanitizedResponseSchema: z.ZodTypeAny;
|
||||
}) => {
|
||||
const appName = APP_CONNECTION_NAME_MAP[app];
|
||||
@@ -208,10 +214,10 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const { name, method, credentials, description } = req.body;
|
||||
const { name, method, credentials, description, isPlatformManagedCredentials } = req.body;
|
||||
|
||||
const appConnection = (await server.services.appConnection.createAppConnection(
|
||||
{ name, method, app, credentials, description },
|
||||
{ name, method, app, credentials, description, isPlatformManagedCredentials },
|
||||
req.permission
|
||||
)) as T;
|
||||
|
||||
@@ -224,7 +230,8 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
name,
|
||||
method,
|
||||
app,
|
||||
connectionId: appConnection.id
|
||||
connectionId: appConnection.id,
|
||||
isPlatformManagedCredentials
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -251,11 +258,11 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const { name, credentials, description } = req.body;
|
||||
const { name, credentials, description, isPlatformManagedCredentials } = req.body;
|
||||
const { connectionId } = req.params;
|
||||
|
||||
const appConnection = (await server.services.appConnection.updateAppConnection(
|
||||
{ name, credentials, connectionId, description },
|
||||
{ name, credentials, connectionId, description, isPlatformManagedCredentials },
|
||||
req.permission
|
||||
)) as T;
|
||||
|
||||
@@ -268,7 +275,8 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
name,
|
||||
description,
|
||||
credentialsUpdated: Boolean(credentials),
|
||||
connectionId
|
||||
connectionId,
|
||||
isPlatformManagedCredentials
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -22,6 +22,11 @@ import {
|
||||
HumanitecConnectionListItemSchema,
|
||||
SanitizedHumanitecConnectionSchema
|
||||
} from "@app/services/app-connection/humanitec";
|
||||
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
|
||||
import {
|
||||
PostgresConnectionListItemSchema,
|
||||
SanitizedPostgresConnectionSchema
|
||||
} from "@app/services/app-connection/postgres";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
// can't use discriminated due to multiple schemas for certain apps
|
||||
@@ -32,7 +37,9 @@ const SanitizedAppConnectionSchema = z.union([
|
||||
...SanitizedAzureKeyVaultConnectionSchema.options,
|
||||
...SanitizedAzureAppConfigurationConnectionSchema.options,
|
||||
...SanitizedDatabricksConnectionSchema.options,
|
||||
...SanitizedHumanitecConnectionSchema.options
|
||||
...SanitizedHumanitecConnectionSchema.options,
|
||||
...SanitizedPostgresConnectionSchema.options,
|
||||
...SanitizedMsSqlConnectionSchema.options
|
||||
]);
|
||||
|
||||
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
||||
@@ -42,7 +49,9 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
||||
AzureKeyVaultConnectionListItemSchema,
|
||||
AzureAppConfigurationConnectionListItemSchema,
|
||||
DatabricksConnectionListItemSchema,
|
||||
HumanitecConnectionListItemSchema
|
||||
HumanitecConnectionListItemSchema,
|
||||
PostgresConnectionListItemSchema,
|
||||
MsSqlConnectionListItemSchema
|
||||
]);
|
||||
|
||||
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
@@ -7,6 +7,8 @@ import { registerDatabricksConnectionRouter } from "./databricks-connection-rout
|
||||
import { registerGcpConnectionRouter } from "./gcp-connection-router";
|
||||
import { registerGitHubConnectionRouter } from "./github-connection-router";
|
||||
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
||||
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
|
||||
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
|
||||
|
||||
export * from "./app-connection-router";
|
||||
|
||||
@@ -18,5 +20,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
||||
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
|
||||
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter,
|
||||
[AppConnection.Databricks]: registerDatabricksConnectionRouter,
|
||||
[AppConnection.Humanitec]: registerHumanitecConnectionRouter
|
||||
[AppConnection.Humanitec]: registerHumanitecConnectionRouter,
|
||||
[AppConnection.Postgres]: registerPostgresConnectionRouter,
|
||||
[AppConnection.MsSql]: registerMsSqlConnectionRouter
|
||||
};
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import {
|
||||
CreateMsSqlConnectionSchema,
|
||||
SanitizedMsSqlConnectionSchema,
|
||||
UpdateMsSqlConnectionSchema
|
||||
} from "@app/services/app-connection/mssql";
|
||||
|
||||
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||
|
||||
export const registerMsSqlConnectionRouter = async (server: FastifyZodProvider) => {
|
||||
registerAppConnectionEndpoints({
|
||||
app: AppConnection.MsSql,
|
||||
server,
|
||||
sanitizedResponseSchema: SanitizedMsSqlConnectionSchema,
|
||||
createSchema: CreateMsSqlConnectionSchema,
|
||||
updateSchema: UpdateMsSqlConnectionSchema
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import {
|
||||
CreatePostgresConnectionSchema,
|
||||
SanitizedPostgresConnectionSchema,
|
||||
UpdatePostgresConnectionSchema
|
||||
} from "@app/services/app-connection/postgres";
|
||||
|
||||
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||
|
||||
export const registerPostgresConnectionRouter = async (server: FastifyZodProvider) => {
|
||||
registerAppConnectionEndpoints({
|
||||
app: AppConnection.Postgres,
|
||||
server,
|
||||
sanitizedResponseSchema: SanitizedPostgresConnectionSchema,
|
||||
createSchema: CreatePostgresConnectionSchema,
|
||||
updateSchema: UpdatePostgresConnectionSchema
|
||||
});
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
||||
import { DASHBOARD } from "@app/lib/api-docs";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
@@ -101,12 +102,30 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeSecrets),
|
||||
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeFolders),
|
||||
includeImports: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeImports),
|
||||
includeSecretRotations: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeSecretRotations),
|
||||
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeDynamicSecrets)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
folders: SecretFoldersSchema.extend({ environment: z.string() }).array().optional(),
|
||||
dynamicSecrets: SanitizedDynamicSecretSchema.extend({ environment: z.string() }).array().optional(),
|
||||
secretRotations: z
|
||||
.intersection(
|
||||
SecretRotationV2Schema,
|
||||
z.object({
|
||||
secrets: secretRawSchema
|
||||
.extend({
|
||||
secretValueHidden: z.boolean(),
|
||||
secretPath: z.string().optional(),
|
||||
secretMetadata: ResourceMetadataSchema.optional(),
|
||||
tags: SanitizedTagSchema.array().optional()
|
||||
})
|
||||
.nullable()
|
||||
.array()
|
||||
})
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
secrets: secretRawSchema
|
||||
.extend({
|
||||
secretValueHidden: z.boolean(),
|
||||
@@ -127,6 +146,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
totalDynamicSecretCount: z.number().optional(),
|
||||
totalSecretCount: z.number().optional(),
|
||||
totalImportCount: z.number().optional(),
|
||||
totalSecretRotationCount: z.number().optional(),
|
||||
totalCount: z.number()
|
||||
})
|
||||
}
|
||||
@@ -144,7 +164,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
includeFolders,
|
||||
includeSecrets,
|
||||
includeImports,
|
||||
includeDynamicSecrets
|
||||
includeDynamicSecrets,
|
||||
includeSecretRotations
|
||||
} = req.query;
|
||||
|
||||
const environments = req.query.environments.split(",");
|
||||
@@ -166,11 +187,15 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
let dynamicSecrets:
|
||||
| Awaited<ReturnType<typeof server.services.dynamicSecret.listDynamicSecretsByEnvs>>
|
||||
| undefined;
|
||||
let secretRotations:
|
||||
| Awaited<ReturnType<typeof server.services.secretRotationV2.getDashboardSecretRotations>>
|
||||
| undefined;
|
||||
|
||||
let totalFolderCount: number | undefined;
|
||||
let totalDynamicSecretCount: number | undefined;
|
||||
let totalSecretCount: number | undefined;
|
||||
let totalImportCount: number | undefined;
|
||||
let totalSecretRotationCount: number | undefined;
|
||||
|
||||
if (includeImports) {
|
||||
totalImportCount = await server.services.secretImport.getProjectImportMultiEnvCount({
|
||||
@@ -322,6 +347,56 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (includeSecretRotations) {
|
||||
totalSecretRotationCount = await server.services.secretRotationV2.getDashboardSecretRotationCount(
|
||||
{
|
||||
projectId,
|
||||
search,
|
||||
environments,
|
||||
secretPath
|
||||
},
|
||||
req.permission
|
||||
);
|
||||
|
||||
if (remainingLimit > 0 && totalSecretRotationCount > adjustedOffset) {
|
||||
secretRotations = await server.services.secretRotationV2.getDashboardSecretRotations(
|
||||
{
|
||||
projectId,
|
||||
search,
|
||||
orderBy,
|
||||
orderDirection,
|
||||
environments,
|
||||
secretPath,
|
||||
limit: remainingLimit,
|
||||
offset: adjustedOffset
|
||||
},
|
||||
req.permission
|
||||
);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
projectId,
|
||||
...req.auditLogInfo,
|
||||
event: {
|
||||
type: EventType.GET_SECRET_ROTATIONS,
|
||||
metadata: {
|
||||
count: secretRotations.length,
|
||||
rotationIds: secretRotations.map((rotation) => rotation.id),
|
||||
secretPath,
|
||||
environment: environments.join(",")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// get the count of unique secret rotation names to properly adjust remaining limit
|
||||
const uniqueSecretRotationCount = new Set(secretRotations.map((rotation) => rotation.name)).size;
|
||||
|
||||
remainingLimit -= uniqueSecretRotationCount;
|
||||
adjustedOffset = 0;
|
||||
} else {
|
||||
adjustedOffset = Math.max(0, adjustedOffset - totalSecretRotationCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (includeSecrets) {
|
||||
// this is the unique count, ie duplicate secrets across envs only count as 1
|
||||
totalSecretCount = await server.services.secret.getSecretsCountMultiEnv({
|
||||
@@ -353,38 +428,44 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
offset: adjustedOffset,
|
||||
isInternal: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for await (const environment of environments) {
|
||||
const secretCountFromEnv = secrets.filter((secret) => secret.environment === environment).length;
|
||||
if (secrets?.length || secretRotations?.length) {
|
||||
for await (const environment of environments) {
|
||||
const secretCountFromEnv =
|
||||
(secrets?.filter((secret) => secret.environment === environment).length ?? 0) +
|
||||
(secretRotations
|
||||
?.filter((rotation) => rotation.environment.slug === environment)
|
||||
.flatMap((rotation) => rotation.secrets.filter((secret) => Boolean(secret))).length ?? 0);
|
||||
|
||||
if (secretCountFromEnv) {
|
||||
await server.services.auditLog.createAuditLog({
|
||||
projectId,
|
||||
...req.auditLogInfo,
|
||||
event: {
|
||||
type: EventType.GET_SECRETS,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath,
|
||||
numberOfSecrets: secretCountFromEnv
|
||||
}
|
||||
if (secretCountFromEnv) {
|
||||
await server.services.auditLog.createAuditLog({
|
||||
projectId,
|
||||
...req.auditLogInfo,
|
||||
event: {
|
||||
type: EventType.GET_SECRETS,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath,
|
||||
numberOfSecrets: secretCountFromEnv
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (getUserAgentType(req.headers["user-agent"]) !== UserAgentType.K8_OPERATOR) {
|
||||
await server.services.telemetry.sendPostHogEvents({
|
||||
event: PostHogEventTypes.SecretPulled,
|
||||
distinctId: getTelemetryDistinctId(req),
|
||||
properties: {
|
||||
numberOfSecrets: secretCountFromEnv,
|
||||
workspaceId: projectId,
|
||||
environment,
|
||||
secretPath,
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
|
||||
if (getUserAgentType(req.headers["user-agent"]) !== UserAgentType.K8_OPERATOR) {
|
||||
await server.services.telemetry.sendPostHogEvents({
|
||||
event: PostHogEventTypes.SecretPulled,
|
||||
distinctId: getTelemetryDistinctId(req),
|
||||
properties: {
|
||||
numberOfSecrets: secretCountFromEnv,
|
||||
workspaceId: projectId,
|
||||
environment,
|
||||
secretPath,
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,12 +476,18 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
dynamicSecrets,
|
||||
secrets,
|
||||
imports,
|
||||
secretRotations,
|
||||
totalFolderCount,
|
||||
totalDynamicSecretCount,
|
||||
totalImportCount,
|
||||
totalSecretCount,
|
||||
totalSecretRotationCount,
|
||||
totalCount:
|
||||
(totalFolderCount ?? 0) + (totalDynamicSecretCount ?? 0) + (totalSecretCount ?? 0) + (totalImportCount ?? 0)
|
||||
(totalFolderCount ?? 0) +
|
||||
(totalDynamicSecretCount ?? 0) +
|
||||
(totalSecretCount ?? 0) +
|
||||
(totalImportCount ?? 0) +
|
||||
(totalSecretRotationCount ?? 0)
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -445,7 +532,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeSecrets),
|
||||
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeFolders),
|
||||
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeDynamicSecrets),
|
||||
includeImports: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeImports)
|
||||
includeImports: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeImports),
|
||||
includeSecretRotations: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeSecretRotations)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@@ -457,6 +545,23 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
.optional(),
|
||||
folders: SecretFoldersSchema.array().optional(),
|
||||
dynamicSecrets: SanitizedDynamicSecretSchema.array().optional(),
|
||||
secretRotations: z
|
||||
.intersection(
|
||||
SecretRotationV2Schema,
|
||||
z.object({
|
||||
secrets: secretRawSchema
|
||||
.extend({
|
||||
secretValueHidden: z.boolean(),
|
||||
secretPath: z.string().optional(),
|
||||
secretMetadata: ResourceMetadataSchema.optional(),
|
||||
tags: SanitizedTagSchema.array().optional()
|
||||
})
|
||||
.nullable()
|
||||
.array()
|
||||
})
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
secrets: secretRawSchema
|
||||
.extend({
|
||||
secretValueHidden: z.boolean(),
|
||||
@@ -470,6 +575,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
totalFolderCount: z.number().optional(),
|
||||
totalDynamicSecretCount: z.number().optional(),
|
||||
totalSecretCount: z.number().optional(),
|
||||
totalSecretRotationCount: z.number().optional(),
|
||||
totalCount: z.number()
|
||||
})
|
||||
}
|
||||
@@ -488,7 +594,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
includeFolders,
|
||||
includeSecrets,
|
||||
includeDynamicSecrets,
|
||||
includeImports
|
||||
includeImports,
|
||||
includeSecretRotations
|
||||
} = req.query;
|
||||
|
||||
if (!projectId || !environment) throw new BadRequestError({ message: "Missing workspace id or environment" });
|
||||
@@ -507,11 +614,15 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
let folders: Awaited<ReturnType<typeof server.services.folder.getFolders>> | undefined;
|
||||
let secrets: Awaited<ReturnType<typeof server.services.secret.getSecretsRaw>>["secrets"] | undefined;
|
||||
let dynamicSecrets: Awaited<ReturnType<typeof server.services.dynamicSecret.listDynamicSecretsByEnv>> | undefined;
|
||||
let secretRotations:
|
||||
| Awaited<ReturnType<typeof server.services.secretRotationV2.getDashboardSecretRotations>>
|
||||
| undefined;
|
||||
|
||||
let totalImportCount: number | undefined;
|
||||
let totalFolderCount: number | undefined;
|
||||
let totalDynamicSecretCount: number | undefined;
|
||||
let totalSecretCount: number | undefined;
|
||||
let totalSecretRotationCount: number | undefined;
|
||||
|
||||
if (includeImports) {
|
||||
totalImportCount = await server.services.secretImport.getProjectImportCount({
|
||||
@@ -594,6 +705,53 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (includeSecretRotations) {
|
||||
totalSecretRotationCount = await server.services.secretRotationV2.getDashboardSecretRotationCount(
|
||||
{
|
||||
projectId,
|
||||
search,
|
||||
environments: [environment],
|
||||
secretPath
|
||||
},
|
||||
req.permission
|
||||
);
|
||||
|
||||
if (remainingLimit > 0 && totalSecretRotationCount > adjustedOffset) {
|
||||
secretRotations = await server.services.secretRotationV2.getDashboardSecretRotations(
|
||||
{
|
||||
projectId,
|
||||
search,
|
||||
orderBy,
|
||||
orderDirection,
|
||||
environments: [environment],
|
||||
secretPath,
|
||||
limit: remainingLimit,
|
||||
offset: adjustedOffset
|
||||
},
|
||||
req.permission
|
||||
);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
projectId,
|
||||
...req.auditLogInfo,
|
||||
event: {
|
||||
type: EventType.GET_SECRET_ROTATIONS,
|
||||
metadata: {
|
||||
count: secretRotations.length,
|
||||
rotationIds: secretRotations.map((rotation) => rotation.id),
|
||||
secretPath,
|
||||
environment
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
remainingLimit -= secretRotations.length;
|
||||
adjustedOffset = 0;
|
||||
} else {
|
||||
adjustedOffset = Math.max(0, adjustedOffset - totalSecretRotationCount);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (includeDynamicSecrets) {
|
||||
totalDynamicSecretCount = await server.services.dynamicSecret.getDynamicSecretCount({
|
||||
@@ -629,7 +787,13 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
adjustedOffset = Math.max(0, adjustedOffset - totalDynamicSecretCount);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (!(error instanceof ForbiddenError)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (includeSecrets) {
|
||||
totalSecretCount = await server.services.secret.getSecretsCount({
|
||||
actorId: req.permission.id,
|
||||
@@ -663,34 +827,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
tagSlugs: tags
|
||||
})
|
||||
).secrets;
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
projectId,
|
||||
...req.auditLogInfo,
|
||||
event: {
|
||||
type: EventType.GET_SECRETS,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath,
|
||||
numberOfSecrets: secrets.length
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (getUserAgentType(req.headers["user-agent"]) !== UserAgentType.K8_OPERATOR) {
|
||||
await server.services.telemetry.sendPostHogEvents({
|
||||
event: PostHogEventTypes.SecretPulled,
|
||||
distinctId: getTelemetryDistinctId(req),
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
workspaceId: projectId,
|
||||
environment,
|
||||
secretPath,
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -699,17 +835,57 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (secrets?.length || secretRotations?.length) {
|
||||
const secretCount =
|
||||
(secrets?.length ?? 0) +
|
||||
(secretRotations?.flatMap((rotation) => rotation.secrets.filter((secret) => Boolean(secret))).length ?? 0);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
projectId,
|
||||
...req.auditLogInfo,
|
||||
event: {
|
||||
type: EventType.GET_SECRETS,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath,
|
||||
numberOfSecrets: secretCount
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (getUserAgentType(req.headers["user-agent"]) !== UserAgentType.K8_OPERATOR) {
|
||||
await server.services.telemetry.sendPostHogEvents({
|
||||
event: PostHogEventTypes.SecretPulled,
|
||||
distinctId: getTelemetryDistinctId(req),
|
||||
properties: {
|
||||
numberOfSecrets: secretCount,
|
||||
workspaceId: projectId,
|
||||
environment,
|
||||
secretPath,
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
...req.auditLogInfo
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
imports,
|
||||
folders,
|
||||
dynamicSecrets,
|
||||
secrets,
|
||||
secretRotations,
|
||||
totalImportCount,
|
||||
totalFolderCount,
|
||||
totalDynamicSecretCount,
|
||||
totalSecretCount,
|
||||
totalSecretRotationCount,
|
||||
totalCount:
|
||||
(totalImportCount ?? 0) + (totalFolderCount ?? 0) + (totalDynamicSecretCount ?? 0) + (totalSecretCount ?? 0)
|
||||
(totalImportCount ?? 0) +
|
||||
(totalFolderCount ?? 0) +
|
||||
(totalDynamicSecretCount ?? 0) +
|
||||
(totalSecretCount ?? 0) +
|
||||
(totalSecretRotationCount ?? 0)
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -747,7 +923,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
tags: SanitizedTagSchema.array().optional()
|
||||
})
|
||||
.array()
|
||||
.optional()
|
||||
.optional(),
|
||||
secretRotations: SecretRotationV2Schema.array().optional()
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -811,6 +988,17 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
req.permission
|
||||
);
|
||||
|
||||
const secretRotations = searchHasTags
|
||||
? []
|
||||
: await server.services.secretRotationV2.getQuickSearchSecretRotations(
|
||||
{
|
||||
projectId,
|
||||
folderMappings,
|
||||
filters: sharedFilters
|
||||
},
|
||||
req.permission
|
||||
);
|
||||
|
||||
for await (const environment of environments) {
|
||||
const secretCountForEnv = secrets.filter((secret) => secret.environment === environment).length;
|
||||
|
||||
@@ -843,6 +1031,24 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const secretRotationsFromEnv = secretRotations.filter((rotation) => rotation.environment.slug === environment);
|
||||
|
||||
if (secretRotationsFromEnv.length) {
|
||||
await server.services.auditLog.createAuditLog({
|
||||
projectId,
|
||||
...req.auditLogInfo,
|
||||
event: {
|
||||
type: EventType.GET_SECRET_ROTATIONS,
|
||||
metadata: {
|
||||
count: secretRotationsFromEnv.length,
|
||||
rotationIds: secretRotationsFromEnv.map((rotation) => rotation.id),
|
||||
secretPath,
|
||||
environment
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const sliceQuickSearch = <T>(array: T[]) => array.slice(0, 25);
|
||||
@@ -856,6 +1062,9 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
? dynamicSecrets.filter((dynamicSecret) => dynamicSecret.path.endsWith(searchPath))
|
||||
: dynamicSecrets
|
||||
),
|
||||
secretRotations: sliceQuickSearch(
|
||||
searchPath ? secretRotations.filter((rotation) => rotation.folder.path.endsWith(searchPath)) : secretRotations
|
||||
),
|
||||
folders: searchHasTags
|
||||
? []
|
||||
: sliceQuickSearch(
|
||||
|
||||
@@ -3,15 +3,26 @@ import { z } from "zod";
|
||||
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgMembershipRole, OrgRolesSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { IDENTITIES } from "@app/lib/api-docs";
|
||||
import { buildSearchZodSchema, SearchResourceOperators } from "@app/lib/search-resource/search";
|
||||
import { OrderByDirection } from "@app/lib/types";
|
||||
import { CharacterType, zodValidateCharacters } from "@app/lib/validator/validate-string";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { OrgIdentityOrderBy } from "@app/services/identity/identity-types";
|
||||
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||
|
||||
import { SanitizedProjectSchema } from "../sanitizedSchemas";
|
||||
|
||||
const searchResourceZodValidate = zodValidateCharacters([
|
||||
CharacterType.AlphaNumeric,
|
||||
CharacterType.Spaces,
|
||||
CharacterType.Underscore,
|
||||
CharacterType.Hyphen
|
||||
]);
|
||||
|
||||
export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
@@ -245,7 +256,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
||||
method: "GET",
|
||||
url: "/",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
@@ -289,6 +300,103 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/search",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Search identities",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
orderBy: z
|
||||
.nativeEnum(OrgIdentityOrderBy)
|
||||
.default(OrgIdentityOrderBy.Name)
|
||||
.describe(IDENTITIES.SEARCH.orderBy)
|
||||
.optional(),
|
||||
orderDirection: z
|
||||
.nativeEnum(OrderByDirection)
|
||||
.default(OrderByDirection.ASC)
|
||||
.describe(IDENTITIES.SEARCH.orderDirection)
|
||||
.optional(),
|
||||
limit: z.number().max(100).default(50).describe(IDENTITIES.SEARCH.limit),
|
||||
offset: z.number().default(0).describe(IDENTITIES.SEARCH.offset),
|
||||
search: buildSearchZodSchema(
|
||||
z
|
||||
.object({
|
||||
name: z
|
||||
.union([
|
||||
searchResourceZodValidate(z.string().max(255), "Name"),
|
||||
z
|
||||
.object({
|
||||
[SearchResourceOperators.$eq]: searchResourceZodValidate(z.string().max(255), "Name $eq"),
|
||||
[SearchResourceOperators.$contains]: searchResourceZodValidate(
|
||||
z.string().max(255),
|
||||
"Name $contains"
|
||||
),
|
||||
[SearchResourceOperators.$in]: searchResourceZodValidate(z.string().max(255), "Name $in").array()
|
||||
})
|
||||
.partial()
|
||||
])
|
||||
.describe(IDENTITIES.SEARCH.search.name),
|
||||
role: z
|
||||
.union([
|
||||
searchResourceZodValidate(z.string().max(255), "Role"),
|
||||
z
|
||||
.object({
|
||||
[SearchResourceOperators.$eq]: searchResourceZodValidate(z.string().max(255), "Role $eq"),
|
||||
[SearchResourceOperators.$in]: searchResourceZodValidate(z.string().max(255), "Role $in").array()
|
||||
})
|
||||
.partial()
|
||||
])
|
||||
.describe(IDENTITIES.SEARCH.search.role)
|
||||
})
|
||||
.describe(IDENTITIES.SEARCH.search.desc)
|
||||
.partial()
|
||||
)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
identities: IdentityOrgMembershipsSchema.extend({
|
||||
customRole: OrgRolesSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
permissions: true,
|
||||
description: true
|
||||
}).optional(),
|
||||
identity: IdentitiesSchema.pick({ name: true, id: true }).extend({
|
||||
authMethods: z.array(z.string())
|
||||
})
|
||||
}).array(),
|
||||
totalCount: z.number()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const { identityMemberships, totalCount } = await server.services.identity.searchOrgIdentities({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
searchFilter: req.body.search,
|
||||
orgId: req.permission.orgId,
|
||||
limit: req.body.limit,
|
||||
offset: req.body.offset,
|
||||
orderBy: req.body.orderBy,
|
||||
orderDirection: req.body.orderDirection
|
||||
});
|
||||
|
||||
return { identities: identityMemberships, totalCount };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:identityId/identity-memberships",
|
||||
|
||||
@@ -8,15 +8,17 @@ import {
|
||||
ProjectSlackConfigsSchema,
|
||||
ProjectType,
|
||||
SecretFoldersSchema,
|
||||
SortDirection,
|
||||
UserEncryptionKeysSchema,
|
||||
UsersSchema
|
||||
} from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { PROJECTS } from "@app/lib/api-docs";
|
||||
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { ProjectFilterType } from "@app/services/project/project-types";
|
||||
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||
import { ProjectFilterType, SearchProjectSortBy } from "@app/services/project/project-types";
|
||||
import { validateSlackChannelsField } from "@app/services/slack/slack-auth-validators";
|
||||
|
||||
import { integrationAuthPubSchema, SanitizedProjectSchema } from "../sanitizedSchemas";
|
||||
@@ -704,4 +706,107 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
return environmentsFolders;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/search",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
limit: z.number().default(100),
|
||||
offset: z.number().default(0),
|
||||
type: z.nativeEnum(ProjectType).optional(),
|
||||
orderBy: z.nativeEnum(SearchProjectSortBy).optional().default(SearchProjectSortBy.NAME),
|
||||
orderDirection: z.nativeEnum(SortDirection).optional().default(SortDirection.ASC),
|
||||
name: z
|
||||
.string()
|
||||
.trim()
|
||||
.refine((val) => characterValidator([CharacterType.AlphaNumeric, CharacterType.Hyphen])(val), {
|
||||
message: "Invalid pattern: only alphanumeric characters, - are allowed."
|
||||
})
|
||||
.optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
projects: SanitizedProjectSchema.extend({ isMember: z.boolean() }).array(),
|
||||
totalCount: z.number()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const { docs: projects, totalCount } = await server.services.project.searchProjects({
|
||||
permission: req.permission,
|
||||
...req.body
|
||||
});
|
||||
|
||||
return { projects, totalCount };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:workspaceId/project-access",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
comment: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(2500)
|
||||
.refine(
|
||||
(val) =>
|
||||
characterValidator([
|
||||
CharacterType.AlphaNumeric,
|
||||
CharacterType.Hyphen,
|
||||
CharacterType.Comma,
|
||||
CharacterType.Fullstop,
|
||||
CharacterType.Spaces,
|
||||
CharacterType.Exclamation
|
||||
])(val),
|
||||
{
|
||||
message: "Invalid pattern: only alphanumeric characters, spaces, -.!, are allowed."
|
||||
}
|
||||
)
|
||||
.optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
await server.services.project.requestProjectAccess({
|
||||
permission: req.permission,
|
||||
comment: req.body.comment,
|
||||
projectId: req.params.workspaceId
|
||||
});
|
||||
|
||||
if (req.auth.actor === ActorType.USER) {
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: req.params.workspaceId,
|
||||
event: {
|
||||
type: EventType.PROJECT_ACCESS_REQUEST,
|
||||
metadata: {
|
||||
projectId: req.params.workspaceId,
|
||||
requesterEmail: req.auth.user.email || req.auth.user.username,
|
||||
requesterId: req.auth.userId
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { message: "Project access request has been send to project admins" };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import { RAW_SECRETS, SECRETS } from "@app/lib/api-docs";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { secretsLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { BaseSecretNameSchema, SecretNameSchema } from "@app/server/lib/schemas";
|
||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||
import { getUserAgentType } from "@app/server/plugins/audit-log";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
@@ -39,13 +40,6 @@ const SecretReferenceNodeTree: z.ZodType<TSecretReferenceNode> = SecretReference
|
||||
children: z.lazy(() => SecretReferenceNodeTree.array())
|
||||
});
|
||||
|
||||
const BaseSecretNameSchema = z.string().trim().min(1);
|
||||
|
||||
const SecretNameSchema = BaseSecretNameSchema.refine(
|
||||
(el) => !el.includes(" "),
|
||||
"Secret name cannot contain spaces."
|
||||
).refine((el) => !el.includes(":"), "Secret name cannot contain colon.");
|
||||
|
||||
export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
@@ -630,6 +624,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
secretValue: z
|
||||
.string()
|
||||
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
||||
.optional()
|
||||
.describe(RAW_SECRETS.UPDATE.secretValue),
|
||||
secretPath: z
|
||||
.string()
|
||||
@@ -2049,6 +2044,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
secretValue: z
|
||||
.string()
|
||||
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
||||
.optional()
|
||||
.describe(RAW_SECRETS.UPDATE.secretValue),
|
||||
secretPath: z
|
||||
.string()
|
||||
|
||||
Reference in New Issue
Block a user