fix(coach): enforce the module gate on coach chat and list routes

The streaming POST and the conversation-list GET enforced only the
legacy operator assistant flag (requireAssistantSurface("coach")),
missing the per-user disableCoach opt-out and the operator
module-availability layer. Both handlers now run the two-layer
requireModuleEnabled(userId, "coach") gate right after auth, returning
403 module.disabled when the account has Coach turned off — even with a
valid Bearer token. The legacy assistant-flag check stays in place.
This commit is contained in:
Marc-André Bombeck committed 2026-06-16 01:24:20 +02:00
1 parent 00d16ea275
commit 2b5974cf4a
1 file changed
+8
+8
View File
@@ -35,6 +35,7 @@ import { auditLog } from "@/lib/auth/audit";
import { prisma } from "@/lib/db";
import { checkRateLimit } from "@/lib/rate-limit";
import { requireAssistantSurface } from "@/lib/feature-flags";
import { requireModuleEnabled } from "@/lib/modules/gate";
import { resolveServerLocale } from "@/lib/i18n/server-locale";
import {
@@ -151,6 +152,10 @@ function buildHistoryWindow(
async function handleChatRequest(request: NextRequest): Promise<Response> {
const auth = await requireAuth();
// v1.18.0 — two-layer module gate (operator availability + per-user
// disableCoach) on top of the legacy assistant flag. 403 module.disabled.
const gate = await requireModuleEnabled(auth.user.id, "coach");
if (!gate.enabled) return gate.response;
// v1.4.31 — operator can disable the Coach surface app-wide.
// Throws AssistantDisabledError → apiHandler returns 403 +
// `errorCode: "assistant.disabled.coach"` per the iOS contract.
@@ -687,6 +692,9 @@ export const POST = apiHandler(handleChatRequest);
*/
export const GET = apiHandler(async (request: NextRequest) => {
const auth = await requireAuth();
// v1.18.0 — same two-layer module gate as the SSE POST.
const gate = await requireModuleEnabled(auth.user.id, "coach");
if (!gate.enabled) return gate.response;
// v1.4.31 — same gate as the SSE POST. Hiding the rail when
// the operator has disabled Coach matches the FAB suppression
// on the client.