From a7d8378f52002432f2e6261a72801d584d54c42a Mon Sep 17 00:00:00 2001 From: Yash Kumar Date: Mon, 21 Sep 2026 01:18:28 +0530 Subject: [PATCH] fix(permissions): mask apiKey on ai.one, add audit logging, remove dead endpoint --- apps/dokploy/server/api/routers/ai.ts | 55 +++++++++++++++++----- packages/server/src/db/schema/audit-log.ts | 3 +- packages/server/src/services/ai.ts | 10 ++++ 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/apps/dokploy/server/api/routers/ai.ts b/apps/dokploy/server/api/routers/ai.ts index 956475590..2f1511787 100644 --- a/apps/dokploy/server/api/routers/ai.ts +++ b/apps/dokploy/server/api/routers/ai.ts @@ -15,6 +15,8 @@ import { getAiSettingById, getAiSettingsByOrganizationId, getCustomAiProviders, + maskAiApiKey, + mergeAiApiKey, saveAiSettings, saveCustomAiProviders, suggestVariants, @@ -41,16 +43,18 @@ import { withAnyPermission, withPermission, } from "@/server/api/trpc"; +import { audit } from "@/server/api/utils/audit"; import { generatePassword } from "@/templates/utils"; export const aiRouter = createTRPCRouter({ one: withAnyPermission("ai", ["read", "update"]) .input(z.object({ aiId: z.string() })) .query(async ({ input, ctx }) => { - return await getAiSettingById( + const setting = await getAiSettingById( input.aiId, ctx.session.activeOrganizationId, ); + return maskAiApiKey(setting); }), getModels: withAnyPermission("ai", ["read", "create", "update"]) @@ -184,13 +188,39 @@ export const aiRouter = createTRPCRouter({ create: withPermission("ai", "create") .input(apiCreateAi) .mutation(async ({ ctx, input }) => { - return await saveAiSettings(ctx.session.activeOrganizationId, input); + const result = await saveAiSettings( + ctx.session.activeOrganizationId, + input, + ); + await audit(ctx, { + action: "create", + resourceType: "ai", + resourceName: input.name, + }); + return result; }), update: withPermission("ai", "update") .input(apiUpdateAi) .mutation(async ({ ctx, input }) => { - return await saveAiSettings(ctx.session.activeOrganizationId, input); + if (input.apiKey) { + const existing = await getAiSettingById( + input.aiId, + ctx.session.activeOrganizationId, + ); + input.apiKey = mergeAiApiKey(input.apiKey, existing.apiKey); + } + const result = await saveAiSettings( + ctx.session.activeOrganizationId, + input, + ); + await audit(ctx, { + action: "update", + resourceType: "ai", + resourceId: input.aiId, + resourceName: input.name, + }); + return result; }), getAll: withAnyPermission("ai", ["read", "create", "update", "delete"]).query( @@ -202,22 +232,21 @@ export const aiRouter = createTRPCRouter({ }, ), - get: withPermission("ai", "read") - .input(z.object({ aiId: z.string() })) - .query(async ({ input, ctx }) => { - return await getAiSettingById( - input.aiId, - ctx.session.activeOrganizationId, - ); - }), - delete: withPermission("ai", "delete") .input(z.object({ aiId: z.string() })) .mutation(async ({ input, ctx }) => { - return await deleteAiSettings( + const setting = await getAiSettingById( input.aiId, ctx.session.activeOrganizationId, ); + await deleteAiSettings(input.aiId, ctx.session.activeOrganizationId); + await audit(ctx, { + action: "delete", + resourceType: "ai", + resourceId: input.aiId, + resourceName: setting.name, + }); + return true; }), getCustomProviders: withAnyPermission("ai", [ diff --git a/packages/server/src/db/schema/audit-log.ts b/packages/server/src/db/schema/audit-log.ts index c42839b00..587c34ce6 100644 --- a/packages/server/src/db/schema/audit-log.ts +++ b/packages/server/src/db/schema/audit-log.ts @@ -95,4 +95,5 @@ export type AuditResourceType = | "network" | "vaultProvider" | "dnsProvider" - | "telemetryProvider"; + | "telemetryProvider" + | "ai"; diff --git a/packages/server/src/services/ai.ts b/packages/server/src/services/ai.ts index 998a4bf3b..157ed3386 100644 --- a/packages/server/src/services/ai.ts +++ b/packages/server/src/services/ai.ts @@ -10,6 +10,16 @@ import { IS_CLOUD } from "../constants"; import { findServerById } from "./server"; import { getWebServerSettings } from "./web-server-settings"; +export const AI_SECRET_MASK = "********"; + +export const maskAiApiKey = (settings: T): T => ({ + ...settings, + apiKey: AI_SECRET_MASK, +}); + +export const mergeAiApiKey = (incoming: string, existing: string): string => + incoming === AI_SECRET_MASK ? existing : incoming; + interface SuggestionItem { id: string; name: string;