fix: Deploy-Skript darf Daten nicht mehr automatisch loeschen, Fehlermeldungen haerten
Erste drei Punkte aus dem TODO-Block L (Sicherheitsdurchgang vom 12.09.): - scripts/update-app.sh: kein automatischer "prisma db push --accept-data-loss"- Fallback mehr, wenn "migrate deploy" fehlschlaegt. Das lief am 11.09. beim Boot-Lauf tatsaechlich unbeaufsichtigt durch. Ein fehlgeschlagenes Migrate bricht das Deployment jetzt sauber ab, exakt wie ein fehlgeschlagener Build. - 14 API-Routen gaben error.message direkt an den Client zurueck (Kalender- Connect/-Sync, Task-Import/-Sync, Someday-Listen, Datenimport). Neuer Helper src/lib/api-error.ts (safeErrorMessage): generische Meldung in Produktion, echte Meldung nur noch lokal. Logging unveraendert in console.error. - src/lib/email.ts: tls.rejectUnauthorized: false entfernt -- stand seit dem allerersten Commit unbegruendet drin, produktiver SMTP-Host (Kasserver) hat ein gueltiges Zertifikat. Verifiziert: Typecheck, Lint, 79 Unit-Tests, Produktions-Build alle gruen. v1.123.2 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
e62294a204
commit
8f017cb879
21
CHANGELOG.md
21
CHANGELOG.md
@ -2,6 +2,27 @@
|
||||
|
||||
All notable changes to this project are documented in this file.
|
||||
|
||||
## [1.123.2] - 2026-09-12
|
||||
|
||||
Erste drei Punkte aus dem TODO-Block L (Sicherheitsdurchgang vom 12.09.) — sichere, in
|
||||
sich abgeschlossene Änderungen ohne Datenmodell-Migration.
|
||||
|
||||
### Security
|
||||
- **Kein automatischer `db push --accept-data-loss`-Fallback mehr in
|
||||
`scripts/update-app.sh`.** Eine fehlgeschlagene `prisma migrate deploy` bricht das
|
||||
Deployment jetzt sauber ab statt unbeaufsichtigt das Schema zu überschreiben — genau
|
||||
dieser Fallback ist am 11.09. beim Boot-Lauf tatsächlich ausgelöst worden.
|
||||
- **14 API-Routen gaben rohe Fehlermeldungen (`error.message`) direkt an den Client
|
||||
zurück** (Kalender-Connect/-Sync, Task-Import/-Sync, Someday-Listen, Datenimport). Neuer
|
||||
Helper `src/lib/api-error.ts` (`safeErrorMessage`): generische Meldung in Produktion,
|
||||
echte Meldung nur noch lokal beim Entwickeln. Volles Logging bleibt unverändert in
|
||||
`console.error`.
|
||||
- SMTP-Zertifikatsprüfung wieder aktiv: `tls: { rejectUnauthorized: false }` in
|
||||
`src/lib/email.ts` entfernt — stand unbegründet seit dem allerersten Commit der Datei und
|
||||
öffnete Tür für Man-in-the-Middle auf Passwort-Reset-/Verifizierungs-Mails.
|
||||
|
||||
Verifiziert: Typecheck, Lint, 79 Unit-Tests, Produktions-Build alle grün.
|
||||
|
||||
## [1.123.1] - 2026-09-12
|
||||
|
||||
### Fixed
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.123.1",
|
||||
"version": "1.123.2",
|
||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@ -104,11 +104,15 @@ main() {
|
||||
npm ci
|
||||
fi
|
||||
|
||||
# Kein automatischer db-push-Fallback mehr: "--accept-data-loss" darf niemand
|
||||
# unbeaufsichtigt aus einem Deploy-Skript heraus zustimmen. Das ist am 11.09. beim
|
||||
# Boot-Lauf tatsaechlich ausgeloest worden ("migrate deploy failed, falling back to
|
||||
# db push..."), ohne dass irgendjemand das gesehen oder bestaetigt haette. Ein
|
||||
# fehlgeschlagenes Migrate bricht das Update jetzt sauber ab -- die laufende App bleibt
|
||||
# unveraendert, exakt wie bei einem fehlgeschlagenen Build. Migrationsfehler muessen von
|
||||
# Hand untersucht werden, nicht automatisch ueberschrieben.
|
||||
log "--- Updating database schema (safe migrate) ---"
|
||||
npx prisma migrate deploy || {
|
||||
log "migrate deploy failed, falling back to db push..."
|
||||
npx prisma db push --accept-data-loss
|
||||
}
|
||||
npx prisma migrate deploy
|
||||
|
||||
log "--- Regenerating Prisma client ---"
|
||||
npx prisma generate
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { safeErrorMessage } from '@/lib/api-error';
|
||||
import { validateCredentials } from '@/lib/apple-calendar';
|
||||
import { CalendarConnection } from '@/lib/calendar-sync';
|
||||
import { getServerSession } from 'next-auth';
|
||||
@ -82,7 +83,7 @@ export async function POST(req: Request) {
|
||||
} catch (error: any) {
|
||||
console.error('Error connecting Apple Calendar:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to connect Apple Calendar' },
|
||||
{ error: safeErrorMessage(error, 'Failed to connect Apple Calendar') },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { getUpcomingEvents } from '@/lib/apple-calendar';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { safeErrorMessage } from '@/lib/api-error';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
@ -36,7 +37,7 @@ export async function POST(req: Request) {
|
||||
} catch (error: any) {
|
||||
console.error('Error syncing Apple Calendar:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to sync Apple Calendar' },
|
||||
{ error: safeErrorMessage(error, 'Failed to sync Apple Calendar') },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { safeErrorMessage } from '@/lib/api-error';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { prisma } from '@/lib/prisma';
|
||||
@ -98,7 +99,7 @@ export async function POST(request: NextRequest) {
|
||||
return NextResponse.json({ event });
|
||||
} catch (error: any) {
|
||||
console.error('Error creating event:', error);
|
||||
return NextResponse.json({ error: error.message || 'Failed to create event' }, { status: 500 });
|
||||
return NextResponse.json({ error: safeErrorMessage(error, 'Failed to create event') }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,7 +196,7 @@ export async function PATCH(request: NextRequest) {
|
||||
return NextResponse.json({ event });
|
||||
} catch (error: any) {
|
||||
console.error('Error updating event:', error);
|
||||
return NextResponse.json({ error: error.message || 'Failed to update event' }, { status: 500 });
|
||||
return NextResponse.json({ error: safeErrorMessage(error, 'Failed to update event') }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,6 +242,6 @@ export async function DELETE(request: NextRequest) {
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error: any) {
|
||||
console.error('Error deleting event:', error);
|
||||
return NextResponse.json({ error: error.message || 'Failed to delete event' }, { status: 500 });
|
||||
return NextResponse.json({ error: safeErrorMessage(error, 'Failed to delete event') }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import { authOptions } from "@/lib/auth";
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { readCachedEvents, isCacheStale, refreshConnectionCache, RefreshableConnection } from '@/lib/calendar-cache';
|
||||
import { runSyncRules } from '@/lib/calendar-cross-sync';
|
||||
import { safeErrorMessage } from '@/lib/api-error';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@ -108,7 +109,7 @@ export async function POST(request: NextRequest) {
|
||||
console.error('[CALENDAR SYNC] Sync request failed:', error);
|
||||
return NextResponse.json({
|
||||
error: 'Sync failed',
|
||||
details: error instanceof Error ? error.message : 'Unknown error'
|
||||
details: safeErrorMessage(error, 'Unknown error')
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { validateCredentials } from '@/lib/synology-calendar';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { safeErrorMessage } from '@/lib/api-error';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
@ -80,7 +81,7 @@ export async function POST(req: Request) {
|
||||
} catch (error: any) {
|
||||
console.error('Error connecting Synology Calendar:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to connect Synology Calendar' },
|
||||
{ error: safeErrorMessage(error, 'Failed to connect Synology Calendar') },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
3
src/app/api/someday-lists/external/route.ts
vendored
3
src/app/api/someday-lists/external/route.ts
vendored
@ -4,6 +4,7 @@ import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { createGoogleClient, createGoogleTaskList } from '@/lib/google-tasks';
|
||||
import { safeErrorMessage } from '@/lib/api-error';
|
||||
import { createMsTodoList } from '@/lib/microsoft-todo';
|
||||
import { getOutlookAccessToken } from '@/lib/outlook-token';
|
||||
import { createAppleReminderList } from '@/lib/apple-reminders';
|
||||
@ -104,7 +105,7 @@ export async function POST(req: NextRequest) {
|
||||
} catch (error: any) {
|
||||
console.error('Error creating external someday list:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to create list' },
|
||||
{ error: safeErrorMessage(error, 'Failed to create list') },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { createGoogleClient, fetchGoogleTasks, fetchGoogleTaskLists } from '@/lib/google-tasks';
|
||||
import { safeErrorMessage } from '@/lib/api-error';
|
||||
import { fetchMsTodoLists, fetchMsTodoTasks, isMsTodoTaskCompleted, fetchMsChecklistItems } from '@/lib/microsoft-todo';
|
||||
import { getOutlookAccessToken } from '@/lib/outlook-token';
|
||||
|
||||
@ -343,7 +344,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
} catch (error: unknown) {
|
||||
console.error('Import error:', error);
|
||||
const message = error instanceof Error ? error.message : 'Import failed';
|
||||
const message = safeErrorMessage(error, 'Import failed');
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import { authOptions } from "@/lib/auth";
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { createGoogleClient, fetchGoogleTaskLists } from '@/lib/google-tasks';
|
||||
import { fetchMsTodoLists } from '@/lib/microsoft-todo';
|
||||
import { safeErrorMessage } from '@/lib/api-error';
|
||||
import { getOutlookAccessToken } from '@/lib/outlook-token';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
@ -83,7 +84,7 @@ export async function GET(req: NextRequest) {
|
||||
|
||||
} catch (error: unknown) {
|
||||
console.error('Fetch lists error:', error);
|
||||
const message = error instanceof Error ? error.message : 'Failed to fetch lists';
|
||||
const message = safeErrorMessage(error, 'Failed to fetch lists');
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import { authOptions } from "@/lib/auth";
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { createGoogleClient, createGoogleTask, updateGoogleTaskStatus, updateGoogleTask, deleteGoogleTask, fetchGoogleTasksForSync, GoogleTask } from '@/lib/google-tasks';
|
||||
import { fetchMsTodoTasksForSync, fetchMsTodoTasksDelta, updateMsTodoTask, deleteMsTodoTask, createMsTodoTask, isMsTodoTaskCompleted } from '@/lib/microsoft-todo';
|
||||
import { safeErrorMessage } from '@/lib/api-error';
|
||||
import { getOutlookAccessToken } from '@/lib/outlook-token';
|
||||
|
||||
// GET - Pull changes from Google Tasks into local DB
|
||||
@ -567,7 +568,7 @@ export async function GET(req: NextRequest) {
|
||||
|
||||
} catch (error: unknown) {
|
||||
console.error('Pull sync error:', error);
|
||||
const message = error instanceof Error ? error.message : 'Pull sync failed';
|
||||
const message = safeErrorMessage(error, 'Pull sync failed');
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@ -706,7 +707,7 @@ export async function PATCH(req: NextRequest) {
|
||||
|
||||
} catch (error: unknown) {
|
||||
console.error('Sync error:', error);
|
||||
const message = error instanceof Error ? error.message : 'Sync failed';
|
||||
const message = safeErrorMessage(error, 'Sync failed');
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@ -839,7 +840,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
} catch (error: unknown) {
|
||||
console.error('Task creation sync error:', error);
|
||||
const message = error instanceof Error ? error.message : 'Task creation sync failed';
|
||||
const message = safeErrorMessage(error, 'Task creation sync failed');
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { safeErrorMessage } from '@/lib/api-error';
|
||||
|
||||
interface ImportTask {
|
||||
id?: string;
|
||||
@ -262,7 +263,7 @@ export async function POST(request: Request) {
|
||||
if (error instanceof SyntaxError) {
|
||||
message = 'Invalid JSON file';
|
||||
} else if (error instanceof Error) {
|
||||
message = `Import failed: ${error.message}`;
|
||||
message = safeErrorMessage(error, 'Import failed');
|
||||
}
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
}
|
||||
|
||||
16
src/lib/api-error.ts
Normal file
16
src/lib/api-error.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Sichere Fehlermeldung fuer API-Antworten.
|
||||
*
|
||||
* Mehrere Routen haben bislang `error.message` direkt in die JSON-Antwort geschrieben --
|
||||
* das kann Datenbankfehler, Dateipfade oder Provider-interne Details an den Client (und
|
||||
* damit an jeden, der die API abfragt) durchreichen. In der Produktion gibt es deshalb nur
|
||||
* noch die generische Fallback-Meldung; lokal beim Entwickeln weiterhin die echte Meldung,
|
||||
* damit Fehlersuche nicht erschwert wird. Das vollstaendige Loggen des Fehlers bleibt
|
||||
* Aufgabe der aufrufenden Stelle (meist schon vorhandenes `console.error` direkt davor).
|
||||
*/
|
||||
export function safeErrorMessage(error: unknown, fallback: string): string {
|
||||
if (process.env.NODE_ENV !== 'production' && error instanceof Error && error.message) {
|
||||
return error.message;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
@ -20,7 +20,11 @@ function getTransporter() {
|
||||
port,
|
||||
secure,
|
||||
auth: { user, pass },
|
||||
tls: { rejectUnauthorized: false },
|
||||
// Kein rejectUnauthorized: false mehr -- das schaltete die Zertifikatspruefung fuer
|
||||
// die SMTP-Verbindung komplett ab (Tuer fuer MITM auf Passwort-Reset-/Verifizierungs-
|
||||
// Mails) und war seit dem allerersten Commit unbegruendet gesetzt. Der produktiv
|
||||
// konfigurierte Host (Kasserver) hat ein gueltiges Zertifikat; keine Notwendigkeit,
|
||||
// die Pruefung abzuschalten.
|
||||
});
|
||||
}
|
||||
return _transporter;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user