mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
fix(settings): refuse enabling a channel the operator switched off
With the switches now enforced at dispatch, a user could still flip a channel on, see the toggle stick, and get nothing. Refuse the enable instead, with 403 and a message that names the reason: the ntfy and Telegram config endpoints on both accepted body shapes, and the Web Push subscribe endpoint, which is the enable step for that channel because it creates the channel row. Telegram is checked before the webhook registration, so a refused enable makes no outbound call and stores nothing. Disabling a channel keeps working while the switch is off — a user must always be able to turn a channel off, whatever the operator has done. The three refusals are published in the contract; `/api/settings/global-services` already exposes the switches so a client can hide the control instead of provoking the refusal.
This commit is contained in:
1 parent
8d2753f942
commit
de0876b215
8 files changed
+368
-11
No files matched your search
@@ -26,12 +26,19 @@ vi.mock("@/lib/api-handler", () => ({
|
||||
|
||||
vi.mock("@/lib/db", () => ({
|
||||
prisma: {
|
||||
// The route reads the operator's instance-wide Web Push switch before it
|
||||
// touches the body; the default stub leaves it on so these cases keep
|
||||
// asserting the validation refusals they were written for.
|
||||
appSettings: { findUnique: vi.fn() },
|
||||
pushSubscription: { upsert: vi.fn(), deleteMany: vi.fn() },
|
||||
notificationChannel: { findFirst: vi.fn(), create: vi.fn() },
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/logging/context", () => ({ annotate: vi.fn() }));
|
||||
vi.mock("@/lib/logging/context", () => ({
|
||||
annotate: vi.fn(),
|
||||
getEvent: vi.fn(() => null),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/crypto", () => ({ encrypt: (v: string) => `enc(${v})` }));
|
||||
|
||||
@@ -67,6 +74,12 @@ type Handler = (r: Request) => Promise<Response>;
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(requireAuth).mockResolvedValue({ user: USER } as never);
|
||||
vi.mocked(prisma.appSettings.findUnique).mockResolvedValue({
|
||||
telegramGlobal: true,
|
||||
ntfyGlobal: true,
|
||||
webPushGlobal: true,
|
||||
apiGlobal: true,
|
||||
} as never);
|
||||
});
|
||||
|
||||
describe("POST /api/notifications/web-push — the subscribe refusal", () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { isChannelGloballyEnabled } from "@/lib/app-settings";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { apiSuccess, apiError, returnAllZodIssues } from "@/lib/api-response";
|
||||
import { encrypt } from "@/lib/crypto";
|
||||
@@ -43,6 +44,18 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
const { user } = await requireAuth();
|
||||
annotate({ action: { name: "notifications.web-push.subscribe" } });
|
||||
|
||||
// Subscribing IS the enable step for this channel — it creates the
|
||||
// WEB_PUSH `NotificationChannel` row. An operator who switched Web Push
|
||||
// off instance-wide (`AppSettings.webPushGlobal`) gets a refusal that says
|
||||
// so rather than a subscription that stores fine and delivers nothing.
|
||||
if (!(await isChannelGloballyEnabled("WEB_PUSH"))) {
|
||||
annotate({ meta: { refused: "globally_disabled" } });
|
||||
return apiError(
|
||||
"Web Push is disabled on this instance by the operator",
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
let body: unknown;
|
||||
try {
|
||||
const raw = await request.text();
|
||||
|
||||
Reference in new issue
Block a user