fix(custom-widgets): configure secrets during import
This commit is contained in:
16 files changed
+331
-93
No files matched your search
@@ -4,6 +4,7 @@ import { z } from "zod/v4";
|
||||
import { decryptSecret } from "@homarr/common/server";
|
||||
import { eq, or } from "@homarr/db";
|
||||
import { boards, customWidgetDefinitions } from "@homarr/db/schema";
|
||||
import { getCustomWidgetSecretRequirements } from "@homarr/custom-widgets/core";
|
||||
|
||||
import { permissionRequiredProcedure, protectedProcedure } from "../../trpc";
|
||||
import { throwIfActionForbiddenAsync } from "../board/board-access";
|
||||
@@ -21,9 +22,11 @@ export const managementQueryProcedures = {
|
||||
.query(async ({ ctx }) => {
|
||||
const definitions = await ctx.db.query.customWidgetDefinitions.findMany({
|
||||
orderBy: (table, { asc }) => asc(table.name),
|
||||
with: { secrets: true },
|
||||
});
|
||||
return definitions.map((definition) => {
|
||||
const widget = parseStoredCustomWidgetDefinition(definition);
|
||||
const configuredSecrets = new Set(definition.secrets.map((secret) => `${secret.sourceId}:${secret.kind}`));
|
||||
return {
|
||||
id: definition.id,
|
||||
name: widget.name,
|
||||
@@ -37,6 +40,9 @@ export const managementQueryProcedures = {
|
||||
authType: auth.type,
|
||||
})),
|
||||
requestCount: widget.requests.length,
|
||||
missingSecrets: getCustomWidgetSecretRequirements(widget.sources).filter(
|
||||
(requirement) => !configuredSecrets.has(`${requirement.sourceId}:${requirement.kind}`),
|
||||
),
|
||||
defaultOptions: widget.defaultOptions,
|
||||
updatedAt: definition.updatedAt,
|
||||
enabled: definition.enabled,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { getCustomWidgetRequiredSecretKinds } from "@homarr/custom-widgets/core";
|
||||
|
||||
export function requiredSecretKinds(authType: string) {
|
||||
if (authType === "basic") return ["username", "password"] as const;
|
||||
if (authType === "bearer" || authType === "apiKeyHeader" || authType === "apiKeyQuery") {
|
||||
return ["apiKey"] as const;
|
||||
}
|
||||
return [] as const;
|
||||
return getCustomWidgetRequiredSecretKinds(authType as Parameters<typeof getCustomWidgetRequiredSecretKinds>[0]);
|
||||
}
|
||||
|
||||
export function assertSecretSources(
|
||||
|
||||
@@ -2,12 +2,14 @@ import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createId } from "@homarr/common";
|
||||
import { encryptSecret } from "@homarr/common/server";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { eq } from "@homarr/db";
|
||||
import { customWidgetDefinitions } from "@homarr/db/schema";
|
||||
import { customWidgetImportSchema } from "@homarr/custom-widgets/core";
|
||||
import { customWidgetDefinitions, customWidgetSecrets } from "@homarr/db/schema";
|
||||
import { customWidgetImportSchema, customWidgetSecretsInputSchema } from "@homarr/custom-widgets/core";
|
||||
|
||||
import { permissionRequiredProcedure } from "../../trpc";
|
||||
import { assertSecretSources } from "./secret-policy";
|
||||
import { parseStoredCustomWidgetDefinition, serializeCustomWidgetDefinition } from "./stored-definition";
|
||||
|
||||
const manageProcedure = permissionRequiredProcedure.requiresPermission("custom-widget-manage");
|
||||
@@ -22,14 +24,34 @@ export const transferProcedures = {
|
||||
return parseStoredCustomWidgetDefinition(definition);
|
||||
}),
|
||||
|
||||
import: manageProcedure.input(customWidgetImportSchema).mutation(async ({ ctx, input }) => {
|
||||
const id = createId();
|
||||
await ctx.db.insert(customWidgetDefinitions).values({
|
||||
id,
|
||||
...serializeCustomWidgetDefinition(input),
|
||||
creatorId: ctx.session.user.id,
|
||||
});
|
||||
logger.info("Imported custom widget definition", { id, name: input.name });
|
||||
return { id };
|
||||
}),
|
||||
import: manageProcedure
|
||||
.input(z.object({ widget: customWidgetImportSchema, secrets: customWidgetSecretsInputSchema.default([]) }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
if (input.secrets.length > 0 && !ctx.session.user.permissions.includes("custom-widget-secret-write")) {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Writing imported widget secrets requires dedicated permission",
|
||||
});
|
||||
}
|
||||
assertSecretSources(input.widget.sources, input.secrets);
|
||||
const id = createId();
|
||||
await ctx.db.insert(customWidgetDefinitions).values({
|
||||
id,
|
||||
...serializeCustomWidgetDefinition(input.widget),
|
||||
creatorId: ctx.session.user.id,
|
||||
});
|
||||
if (input.secrets.length > 0) {
|
||||
await ctx.db.insert(customWidgetSecrets).values(
|
||||
input.secrets.map((secret) => ({
|
||||
definitionId: id,
|
||||
sourceId: secret.sourceId,
|
||||
kind: secret.kind,
|
||||
encryptedValue: encryptSecret(secret.value),
|
||||
updatedAt: new Date(),
|
||||
})),
|
||||
);
|
||||
}
|
||||
logger.info("Imported custom widget definition", { id, name: input.widget.name });
|
||||
return { id };
|
||||
}),
|
||||
};
|
||||
Reference in new issue
Block a user