mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
The incident forwarder stripped the query string but not path-segment secrets from the reported URL; route it through the same redaction the message and stack already use. Several delete-and-swallow paths caught every error as the expected unique/absent-row case, masking real failures on security-notification, logout, and integration-disconnect paths and the Telegram reply-consume guard; narrow each to the specific Prisma code and let real failures surface.
89 lines
2.6 KiB
TypeScript
89 lines
2.6 KiB
TypeScript
import { prisma } from "@/lib/db";
|
|
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
|
import { apiSuccess, apiError, safeJson } from "@/lib/api-response";
|
|
import { isP2025 } from "@/lib/prisma-errors";
|
|
import { annotate } from "@/lib/logging/context";
|
|
import { encrypt } from "@/lib/crypto";
|
|
import { withingsCredentialsSchema } from "@/lib/validations/withings";
|
|
import { NextRequest } from "next/server";
|
|
import { z } from "zod/v4";
|
|
|
|
/**
|
|
* Check if user has Withings credentials stored.
|
|
*/
|
|
export const GET = apiHandler(async () => {
|
|
const { user } = await requireAuth();
|
|
annotate({ action: { name: "withings.credentials.get" } });
|
|
|
|
const dbUser = await prisma.user.findUnique({
|
|
where: { id: user.id },
|
|
select: {
|
|
withingsClientIdEncrypted: true,
|
|
withingsClientSecretEncrypted: true,
|
|
},
|
|
});
|
|
|
|
return apiSuccess({
|
|
hasCredentials:
|
|
!!dbUser?.withingsClientIdEncrypted &&
|
|
!!dbUser?.withingsClientSecretEncrypted,
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Save Withings API credentials (encrypted).
|
|
*/
|
|
export const PUT = apiHandler(async (request: NextRequest) => {
|
|
const { user } = await requireAuth();
|
|
annotate({ action: { name: "withings.credentials.update" } });
|
|
|
|
const { data: body, error: jsonError } = await safeJson(request, {
|
|
maxBytes: 64 * 1024,
|
|
});
|
|
|
|
if (jsonError) return jsonError;
|
|
const result = z.safeParse(withingsCredentialsSchema, body);
|
|
if (!result.success) {
|
|
return apiError("Client ID and Client Secret are required", 422);
|
|
}
|
|
|
|
await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: {
|
|
withingsClientIdEncrypted: encrypt(result.data.clientId),
|
|
withingsClientSecretEncrypted: encrypt(result.data.clientSecret),
|
|
},
|
|
});
|
|
|
|
return apiSuccess({ updated: true });
|
|
});
|
|
|
|
/**
|
|
* Delete Withings credentials and disconnect.
|
|
*/
|
|
export const DELETE = apiHandler(async () => {
|
|
const { user } = await requireAuth();
|
|
annotate({ action: { name: "withings.credentials.delete" } });
|
|
|
|
// Remove connection first. A missing connection row (P2025) is a benign
|
|
// "already disconnected" no-op; any other failure must propagate rather
|
|
// than be swallowed — otherwise the encrypted OAuth tokens can be left
|
|
// orphaned while the user is told the integration was disconnected.
|
|
await prisma.withingsConnection
|
|
.delete({ where: { userId: user.id } })
|
|
.catch((err) => {
|
|
if (!isP2025(err)) throw err;
|
|
});
|
|
|
|
// Remove credentials
|
|
await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: {
|
|
withingsClientIdEncrypted: null,
|
|
withingsClientSecretEncrypted: null,
|
|
},
|
|
});
|
|
|
|
return apiSuccess({ deleted: true });
|
|
});
|