feat(settings): restrict AI readiness checks to admins

This commit is contained in:
2026-03-30 11:00:42 +02:00
parent 81a46c81bd
commit c8e82ac221
5 changed files with 80 additions and 3 deletions
@@ -626,11 +626,11 @@ describe("assistant router tool gating", () => {
expect(userNames).not.toContain("get_system_settings");
expect(userNames).not.toContain("update_system_settings");
expect(userNames).not.toContain("test_ai_connection");
expect(userNames).not.toContain("get_ai_configured");
expect(userNames).not.toContain("list_system_role_configs");
expect(userNames).not.toContain("update_system_role_config");
expect(userNames).not.toContain("list_webhooks");
expect(userNames).not.toContain("create_webhook");
expect(userNames).toContain("get_ai_configured");
});
it("keeps holiday calendar catalog tools admin-only while leaving preview available", () => {
@@ -0,0 +1,67 @@
import { SystemRole } from "@capakraken/shared";
import { describe, expect, it, vi } from "vitest";
import { settingsRouter } from "../router/settings.js";
import { createCallerFactory } from "../trpc.js";
function createProtectedContext(
db: Record<string, unknown>,
systemRole: SystemRole,
) {
return {
session: {
user: { email: "user@example.com", name: "User", image: null },
expires: "2099-01-01T00:00:00.000Z",
},
db: db as never,
dbUser: {
id: "user_1",
systemRole,
permissionOverrides: null,
},
};
}
describe("settings router authorization", () => {
it("forbids non-admin users from reading AI configuration status", async () => {
const findUnique = vi.fn();
const caller = createCallerFactory(settingsRouter)(createProtectedContext({
systemSettings: {
findUnique,
},
}, SystemRole.USER));
await expect(caller.getAiConfigured()).rejects.toMatchObject({
code: "FORBIDDEN",
message: "Admin role required",
});
expect(findUnique).not.toHaveBeenCalled();
});
it("allows admins to read AI configuration status", async () => {
const findUnique = vi.fn().mockResolvedValue({
aiProvider: "azure",
azureOpenAiEndpoint: "https://example.openai.azure.com",
azureOpenAiDeployment: "gpt-4o",
azureOpenAiApiKey: "secret",
});
const caller = createCallerFactory(settingsRouter)(createProtectedContext({
systemSettings: {
findUnique,
},
}, SystemRole.ADMIN));
const result = await caller.getAiConfigured();
expect(result).toEqual({ configured: true });
expect(findUnique).toHaveBeenCalledWith({
where: { id: "singleton" },
select: {
aiProvider: true,
azureOpenAiEndpoint: true,
azureOpenAiDeployment: true,
azureOpenAiApiKey: true,
},
});
});
});
+1
View File
@@ -344,6 +344,7 @@ const ADMIN_ONLY_TOOLS = new Set([
"commit_dispo_import_batch",
"get_system_settings",
"update_system_settings",
"get_ai_configured",
"test_ai_connection",
"test_smtp_connection",
"test_gemini_connection",
+2 -2
View File
@@ -1,5 +1,5 @@
import { z } from "zod";
import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc.js";
import { adminProcedure, createTRPCRouter } from "../trpc.js";
import { createAiClient, isAiConfigured, parseAiError } from "../ai-client.js";
import { DEFAULT_SUMMARY_PROMPT } from "./resource.js";
import { VALUE_SCORE_WEIGHTS } from "@capakraken/shared";
@@ -361,7 +361,7 @@ export const settingsRouter = createTRPCRouter({
}
}),
getAiConfigured: protectedProcedure.query(async ({ ctx }) => {
getAiConfigured: adminProcedure.query(async ({ ctx }) => {
const settings = await ctx.db.systemSettings.findUnique({
where: { id: "singleton" },
select: {