fix: security hardening, Prisma singleton, tsconfig cleanup

- tsconfig: remove deprecated downlevelIteration (es2017 has native iterators),
  change moduleResolution from node to bundler
- auth: set allowDangerousEmailAccountLinking=false on all OAuth providers,
  restrict debug mode to development only
- prisma: replace new PrismaClient() with singleton import in 10 API routes
  to prevent connection pool exhaustion
- goal API: validate user-supplied quote URLs to block SSRF (private IPs,
  non-http(s) schemes)
- email: guard DEV verification/reset link console.logs behind NODE_ENV check

v1.98.0
This commit is contained in:
mARTin 2026-04-20 19:52:47 +02:00
parent aa4c6a085d
commit 3e19ebe85a
22 changed files with 667 additions and 45 deletions

1
build.pid Normal file
View File

@ -0,0 +1 @@
2331610

27
clear-data.ts Normal file
View File

@ -0,0 +1,27 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const user = await prisma.user.findUnique({
where: { email: 'martin.bierschenk@gmail.com' }
});
if (!user) return;
// Delete all tasks in the target range created today
const res = await prisma.task.deleteMany({
where: {
userId: user.id,
scheduledDate: {
gte: new Date('2026-03-15T00:00:00Z'),
lte: new Date('2026-04-15T00:00:00Z')
},
createdAt: {
gte: new Date(new Date().setHours(0,0,0,0))
}
}
});
console.log(`Deleted ${res.count} tasks from martin`);
}
main().finally(() => prisma.$disconnect());

214
fill-data-v2.ts Normal file
View File

@ -0,0 +1,214 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const dailyTasks = [
"Pretend to listen to partner's work drama",
"Feed the kids (again? really?)",
"Hide Amazon packages from spouse",
"Attempt to fold a fitted sheet, give up and roll it into a ball",
"Cook a meal that exactly 0% of the family will appreciate",
"Look at the mess. Sigh. Walk away.",
"Move the laundry from washer to dryer, leave it there for 3 days",
"Find out what died in the fridge",
"Pay bills and cry softly",
"Unload dishwasher with immense resentment",
"Scroll on phone until legs go numb on the toilet",
"Perform archeological dig in the teenage bedroom",
"Interrogate child about missing tupperware lid",
"Nod enthusiastically at toddler's incomprehensible story",
"Try to decipher spouse's 'helpful' grocery list",
"Pretend I don't see the full trash can",
"Vacuum the rug and ignore the corners",
"Wonder where all my money went"
];
const workEvents = [
"Meaningless sync meeting #42",
"Stare blankly at spreadsheet",
"Listen to boss talk about synergy",
"Reply 'As per my previous email...' to Gary",
"Pretend to be busy so no one asks me for help",
"'Quick chat' that ruins my entire afternoon",
"Update Jira tickets to make it look like I did something",
"Mute mic and eat aggressively loud chips during all-hands",
"Consider moving to the woods and becoming a hermit",
"Frantically search for the tab that is playing music",
"Draft angry email, delete it, send 'Sounds good!'",
"Nod meaningfully during presentation I don't understand"
];
const birthdayTasks = [
"Buy a gift that makes me look thoughtful but was actually on sale",
"Wrap the present (using newspaper because I forgot wrapping paper)",
"Attend birthday party and strategically position myself near the snack table",
"Fake a smile while listening to Uncle Bob's views",
"Smuggle leftover cake home in napkins"
];
async function main() {
const user = await prisma.user.findUnique({
where: { email: 'kugelblitz@gmx.de' }
});
if (!user) return;
// 1. Delete all recently generated tasks
await prisma.task.deleteMany({
where: {
userId: user.id,
createdAt: { gte: new Date(new Date().setHours(0,0,0,0)) },
title: { not: 'Meeting with team' } // leave the one subagent added if you want, or just let it delete if the title is strictly dailyTasks. Actually just delete all created today!
}
});
// 2. Create Projects for beautiful colors
let pWork = await prisma.project.findFirst({ where: { userId: user.id, name: 'Work' } });
if (!pWork) pWork = await prisma.project.create({ data: { userId: user.id, name: 'Work', color: '#3b82f6', icon: 'faBriefcase' } });
let pFamily = await prisma.project.findFirst({ where: { userId: user.id, name: 'Family' } });
if (!pFamily) pFamily = await prisma.project.create({ data: { userId: user.id, name: 'Family', color: '#10b981', icon: 'faHouse' } });
let pLife = await prisma.project.findFirst({ where: { userId: user.id, name: 'Life' } });
if (!pLife) pLife = await prisma.project.create({ data: { userId: user.id, name: 'Life', color: '#f59e0b', icon: 'faHeart' } });
// 3. Find target Someday lists to populate the bottom
const lists = await prisma.somedayList.findMany({ where: { userId: user.id } });
const kugelList = lists.find(l => l.title.includes('kugel'));
const papaList = lists.find(l => l.title.includes('Papa'));
// Target dates: March 16 to April 12, 2026
const startDate = new Date('2026-03-16T00:00:00Z');
const endDate = new Date('2026-04-12T00:00:00Z');
const birthdayDate = new Date('2026-04-04T00:00:00Z');
const aprilFools = new Date('2026-04-01T00:00:00Z');
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
const isBirthday = d.getTime() === birthdayDate.getTime();
const isAprilFools = d.getTime() === aprilFools.getTime();
const dateStr = d.toISOString().split('T')[0];
const isWeekend = d.getDay() === 0 || d.getDay() === 6;
// We want a DENSE calendar for the tutorial. 50% fill rate for EACH SLOT.
// Daily Life events (scheduled on grid)
const numLife = Math.floor(Math.random() * 2) + 1;
for (let i = 0; i < numLife; i++) {
const h = Math.floor(Math.random() * 3) + 6; // 6-8 AM or evening
await prisma.task.create({
data: {
title: dailyTasks[Math.floor(Math.random() * dailyTasks.length)],
userId: user.id,
scheduledDate: new Date(dateStr),
dayOfWeek: d.getDay(),
startTime: `${h.toString().padStart(2, '0')}:00`,
endTime: `${(h + 1).toString().padStart(2, '0')}:00`,
projectId: pLife.id
}
});
}
if (!isWeekend) {
// Work events
let currentHour = 8;
while (currentHour <= 16) {
if (Math.random() > 0.4) { // 60% chance to put a work meeting in this block
const duration = Math.random() > 0.5 ? 1 : 2;
await prisma.task.create({
data: {
title: workEvents[Math.floor(Math.random() * workEvents.length)],
userId: user.id,
scheduledDate: new Date(dateStr),
dayOfWeek: d.getDay(),
startTime: `${currentHour.toString().padStart(2, '0')}:00`,
endTime: `${(currentHour + duration).toString().padStart(2, '0')}:00`,
projectId: pWork.id
}
});
currentHour += duration + 1; // leave at least 1h gap
} else {
currentHour += 1;
}
}
} else {
// Weekend Family events
const numFam = Math.floor(Math.random() * 3) + 2;
let famHour = 9;
for (let i = 0; i < numFam; i++) {
await prisma.task.create({
data: {
title: dailyTasks[Math.floor(Math.random() * dailyTasks.length)],
userId: user.id,
scheduledDate: new Date(dateStr),
dayOfWeek: d.getDay(),
startTime: `${famHour.toString().padStart(2, '0')}:00`,
endTime: `${(famHour + 1).toString().padStart(2, '0')}:00`,
projectId: pFamily.id
}
});
famHour += 2;
}
}
// Add April Fools extra
if (isAprilFools) {
await prisma.task.create({
data: {
title: "Attempt a prank, fail miserably, apologize to HR",
userId: user.id,
scheduledDate: new Date(dateStr),
dayOfWeek: d.getDay(),
startTime: "10:30",
endTime: "11:30",
projectId: pWork.id
}
});
}
// Add Birthday extras
if (isBirthday) {
for (let i = 0; i < birthdayTasks.length; i++) {
const time = 14 + i;
await prisma.task.create({
data: {
title: birthdayTasks[i],
userId: user.id,
scheduledDate: new Date(dateStr),
dayOfWeek: d.getDay(),
startTime: `${time}:00`,
endTime: `${time + 1}:00`,
projectId: pFamily.id
}
});
}
}
}
// Populate SomeDay Lists
if (kugelList) {
for(let i=0; i<3; i++) {
await prisma.task.create({
data: {
title: dailyTasks[Math.floor(Math.random() * dailyTasks.length)],
userId: user.id,
somedayListId: kugelList.id,
order: i
}
});
}
}
if (papaList) {
for(let i=0; i<3; i++) {
await prisma.task.create({
data: {
title: workEvents[Math.floor(Math.random() * workEvents.length)],
userId: user.id,
somedayListId: papaList.id,
order: i
}
});
}
}
console.log("Densley populated the calendar with wonderful colored events!");
}
main().catch(console.error).finally(() => prisma.$disconnect());

153
fill-data.ts Normal file
View File

@ -0,0 +1,153 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const dailyTasks = [
"Pretend to listen to partner's work drama",
"Feed the kids (again? really?)",
"Hide Amazon packages from spouse",
"Attempt to fold a fitted sheet, give up and roll it into a ball",
"Cook a meal that exactly 0% of the family will appreciate",
"Look at the mess. Sigh. Walk away.",
"Move the laundry from washer to dryer, leave it there for 3 days",
"Find out what died in the fridge",
"Pay bills and cry softly",
"Unload dishwasher with immense resentment",
"Scroll on phone until legs go numb on the toilet",
"Perform archeological dig in the teenage bedroom",
"Interrogate child about missing tupperware lid",
"Nod enthusiastically at toddler's incomprehensible story",
"Try to decipher spouse's 'helpful' grocery list",
"Pretend I don't see the full trash can"
];
const workEvents = [
"Meaningless sync meeting #42",
"Stare blankly at spreadsheet",
"Listen to boss talk about synergy",
"Reply 'As per my previous email...' to Gary",
"Pretend to be busy so no one asks me for help",
"'Quick chat' that ruins my entire afternoon",
"Update Jira tickets to make it look like I did something",
"Mute mic and eat aggressively loud chips during all-hands",
"Consider moving to the woods and becoming a hermit",
"Frantically search for the tab that is playing music"
];
const birthdayTasks = [
"Buy a gift that makes me look thoughtful but was actually on sale",
"Wrap the present (using newspaper because I forgot wrapping paper)",
"Attend birthday party and strategically position myself near the snack table",
"Fake a smile while listening to Uncle Bob's views",
"Smuggle leftover cake home in napkins"
];
async function main() {
const user = await prisma.user.findUnique({
where: { email: 'kugelblitz@gmx.de' }
});
if (!user) {
console.log("User kugelblitz@gmx.de not found in database.");
return;
}
console.log(`Using user: ${user.email} (${user.id})`);
// Target dates: March 16 to April 12, 2026
const startDate = new Date('2026-03-16T00:00:00Z');
const endDate = new Date('2026-04-12T00:00:00Z');
// Birthday party around April 4th
const birthdayDate = new Date('2026-04-04T00:00:00Z');
// April Fools on April 1st
const aprilFools = new Date('2026-04-01T00:00:00Z');
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
// ~40% fill rate for normal days
const isBirthday = d.getTime() === birthdayDate.getTime();
const isAprilFools = d.getTime() === aprilFools.getTime();
// Skip ~60% of the regular days to keep density 30-50%
if (!isBirthday && !isAprilFools && Math.random() > 0.45) {
continue;
}
const dateStr = d.toISOString().split('T')[0];
const isWeekend = d.getDay() === 0 || d.getDay() === 6;
// Add 1-2 daily tasks (without specific time)
const numTasks = Math.floor(Math.random() * 2) + 1;
for (let i = 0; i < numTasks; i++) {
const taskName = dailyTasks[Math.floor(Math.random() * dailyTasks.length)];
await prisma.task.create({
data: {
title: taskName,
userId: user.id,
scheduledDate: new Date(dateStr),
dayOfWeek: d.getDay(),
order: i,
}
});
}
// Add 1-2 work events (with time) if it's a weekday
if (!isWeekend) {
if (Math.random() > 0.3) {
const numEvents = Math.floor(Math.random() * 2) + 1;
let currentHour = Math.floor(Math.random() * (11 - 8 + 1)) + 8; // Morning 8-11
for (let e = 0; e < numEvents; e++) {
const eventName = workEvents[Math.floor(Math.random() * workEvents.length)];
const startTime = `${currentHour.toString().padStart(2, '0')}:00`;
await prisma.task.create({
data: {
title: eventName,
userId: user.id,
scheduledDate: new Date(dateStr),
dayOfWeek: d.getDay(),
startTime: startTime,
endTime: `${(currentHour+1).toString().padStart(2, '0')}:00`,
}
});
currentHour += Math.floor(Math.random() * 3) + 2; // Jump 2-4 hours for next event
if (currentHour > 17) break;
}
}
}
// Add April Fools extra
if (isAprilFools) {
await prisma.task.create({
data: {
title: "Attempt a prank, fail miserably, apologize to HR",
userId: user.id,
scheduledDate: new Date(dateStr),
dayOfWeek: d.getDay(),
startTime: "10:30",
endTime: "11:00",
}
});
}
// Add Birthday extras
if (isBirthday) {
for (let i = 0; i < birthdayTasks.length; i++) {
const time = i >= 2 ? `${14 + i}:00` : undefined; // Party elements have times 16:00, 17:00, 18:00
await prisma.task.create({
data: {
title: birthdayTasks[i],
userId: user.id,
scheduledDate: new Date(dateStr),
dayOfWeek: d.getDay(),
startTime: time,
endTime: time ? `${15 + i}:00` : undefined,
}
});
}
}
}
console.log("Successfully seeded the calendar with fun, sarcastic data!");
}
main().catch(console.error).finally(() => prisma.$disconnect());

118
fill-ext-events.js Normal file
View File

@ -0,0 +1,118 @@
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
const eventNames = [
"Dentist Appointment",
"Coffee with Sarah",
"Quarterly Review",
"Car Inspection",
"Therapy Session",
"Lunch with Bob"
];
const allDayEvents = [
"Spring Festival",
"Bank Holiday",
"Company Offsite",
"Project Deadline"
];
function getWeekStart(date) {
const d = new Date(date);
d.setUTCHours(0, 0, 0, 0);
const day = d.getUTCDay();
const diff = d.getUTCDate() - day + (day === 0 ? -6 : 1); // Monday is 1
d.setUTCDate(diff);
return d;
}
async function main() {
const user = await prisma.user.findUnique({
where: { email: 'kugelblitz@gmx.de' }
});
if (!user) return;
// 1. Ensure a dummy calendar connection exists
let connection = await prisma.calendarConnection.findFirst({
where: { userId: user.id, provider: 'google' }
});
if (!connection) {
connection = await prisma.calendarConnection.create({
data: {
userId: user.id,
provider: 'google',
accessToken: 'dummy-token',
calendars: JSON.stringify([{ id: 'primary', name: 'My Calendar', color: '#4285F4' }])
}
});
}
// 2. Clear old generated mock events
const mockTitles = [...eventNames, ...allDayEvents];
await prisma.cachedCalendarEvent.deleteMany({
where: {
connectionId: connection.id,
title: { in: mockTitles }
}
});
const startDate = new Date('2026-03-16T00:00:00Z');
const endDate = new Date('2026-04-12T00:00:00Z');
let idCounter = 1;
for (let d = new Date(startDate); d <= endDate; d.setUTCDate(d.getUTCDate() + 1)) {
console.log('Processing date:', d.toISOString());
const dateStr = d.toISOString().split('T')[0];
const weekStart = getWeekStart(d);
if (Math.random() > 0.6) {
// Add All-Day Event
await prisma.cachedCalendarEvent.create({
data: {
userId: user.id,
externalId: `mock-allday-${idCounter++}`,
connectionId: connection.id,
provider: 'google',
calendarId: 'primary',
calendarTitle: 'My Calendar',
calendarColor: '#EA4335',
title: allDayEvents[Math.floor(Math.random() * allDayEvents.length)],
startDate: dateStr,
endDate: dateStr,
weekStart: weekStart
}
});
}
if (Math.random() > 0.5) {
// Add Timed Event
const hour = Math.floor(Math.random() * 6) + 10; // 10 to 15
const startDt = new Date(d);
startDt.setUTCHours(hour, 0, 0, 0);
const endDt = new Date(d);
endDt.setUTCHours(hour + 1, 0, 0, 0);
await prisma.cachedCalendarEvent.create({
data: {
userId: user.id,
externalId: `mock-timed-${idCounter++}`,
connectionId: connection.id,
provider: 'google',
calendarId: 'primary',
calendarTitle: 'My Calendar',
calendarColor: '#4285F4',
title: eventNames[Math.floor(Math.random() * eventNames.length)],
startDateTime: startDt,
endDateTime: endDt,
weekStart: weekStart
}
});
}
}
console.log("Successfully added simulated external calendar events!");
}
main().catch(console.error).finally(() => prisma.$disconnect());

116
fill-ext-events.ts Normal file
View File

@ -0,0 +1,116 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const eventNames = [
"Dentist Appointment",
"Coffee with Sarah",
"Quarterly Review",
"Car Inspection",
"Therapy Session",
"Lunch with Bob"
];
const allDayEvents = [
"Spring Festival",
"Bank Holiday",
"Company Offsite",
"Project Deadline"
];
function getWeekStart(date: Date): Date {
const d = new Date(date);
d.setHours(0, 0, 0, 0);
const day = d.getDay();
const diff = d.getDate() - day + (day === 0 ? -6 : 1); // Monday is 1
return new Date(d.setDate(diff));
}
async function main() {
const user = await prisma.user.findUnique({
where: { email: 'kugelblitz@gmx.de' }
});
if (!user) return;
// 1. Ensure a dummy calendar connection exists
let connection = await prisma.calendarConnection.findFirst({
where: { userId: user.id, provider: 'google' }
});
if (!connection) {
connection = await prisma.calendarConnection.create({
data: {
userId: user.id,
provider: 'google',
accessToken: 'dummy-token',
calendars: JSON.stringify([{ id: 'primary', name: 'My Calendar', color: '#4285F4' }])
}
});
}
// 2. Clear old generated mock events
const mockTitles = [...eventNames, ...allDayEvents];
await prisma.cachedCalendarEvent.deleteMany({
where: {
connectionId: connection.id,
title: { in: mockTitles }
}
});
const startDate = new Date('2026-03-16T00:00:00Z');
const endDate = new Date('2026-04-12T00:00:00Z');
let idCounter = 1;
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
const dateStr = d.toISOString().split('T')[0];
const weekStart = getWeekStart(d);
if (Math.random() > 0.6) {
// Add All-Day Event
await prisma.cachedCalendarEvent.create({
data: {
userId: user.id,
externalId: `mock-allday-${idCounter++}`,
connectionId: connection.id,
provider: 'google',
calendarId: 'primary',
calendarTitle: 'My Calendar',
calendarColor: '#primary', // Or any string, frontend handles #
title: allDayEvents[Math.floor(Math.random() * allDayEvents.length)],
startDate: dateStr,
endDate: dateStr,
weekStart: weekStart
}
});
}
if (Math.random() > 0.5) {
// Add Timed Event
const hour = Math.floor(Math.random() * 6) + 10; // 10 to 15
const startDt = new Date(d);
startDt.setUTCHours(hour, 0, 0, 0);
const endDt = new Date(d);
endDt.setUTCHours(hour + 1, 0, 0, 0);
await prisma.cachedCalendarEvent.create({
data: {
userId: user.id,
externalId: `mock-timed-${idCounter++}`,
connectionId: connection.id,
provider: 'google',
calendarId: 'primary',
calendarTitle: 'My Calendar',
calendarColor: '#EA4335',
title: eventNames[Math.floor(Math.random() * eventNames.length)],
startDateTime: startDt,
endDateTime: endDt,
weekStart: weekStart
}
});
}
}
console.log("Successfully added simulated external calendar events!");
}
main().catch(console.error).finally(() => prisma.$disconnect());

View File

@ -1,6 +1,6 @@
{
"name": "my-weekly-todo-list",
"version": "1.97.4",
"version": "1.98.0",
"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": {

View File

@ -1,11 +1,9 @@
import { NextRequest, NextResponse } from 'next/server';
export const dynamic = 'force-dynamic';
import { PrismaClient } from '@prisma/client';
import { prisma } from '@/lib/prisma';
import { hash } from 'bcryptjs';
import { sendVerificationEmail, generateVerificationCode, generateVerificationToken } from '@/lib/email';
const prisma = new PrismaClient();
export async function POST(request: NextRequest) {
try {
const { name, email, password } = await request.json();

View File

@ -3,9 +3,7 @@ import { validateCredentials } from '@/lib/apple-calendar';
import { CalendarConnection } from '@/lib/calendar-sync';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/lib/auth";
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
import { prisma } from '@/lib/prisma';
export async function POST(req: Request) {
try {

View File

@ -2,12 +2,10 @@ import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/lib/auth";
import { google } from 'googleapis';
import { PrismaClient } from '@prisma/client';
import { prisma } from '@/lib/prisma';
export const dynamic = 'force-dynamic';
const prisma = new PrismaClient();
// Google Calendar OAuth callback endpoint
export async function GET(request: NextRequest) {
try {

View File

@ -1,13 +1,11 @@
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/lib/auth";
import { PrismaClient } from '@prisma/client';
import { prisma } from '@/lib/prisma';
import { getUserDatabases } from '@/lib/notion-calendar';
export const dynamic = 'force-dynamic';
const prisma = new PrismaClient();
export async function GET(request: NextRequest) {
try {
const session = await getServerSession(authOptions);

View File

@ -2,9 +2,7 @@ import { NextResponse } from 'next/server';
import { validateCredentials } from '@/lib/synology-calendar';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/lib/auth";
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
import { prisma } from '@/lib/prisma';
export async function POST(req: Request) {
try {

View File

@ -127,7 +127,16 @@ export async function GET(req: Request) {
};
// Try each user-configured URL first
const userUrls: string[] = (user as any)?.quoteSourceUrls?.filter(Boolean) || [];
const PRIVATE_IP = /^(localhost|127\.|0\.0\.0\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|169\.254\.|::1$|fc00:|fe80:)/i;
function isSafeQuoteUrl(raw: string): boolean {
try {
const u = new URL(raw);
if (!['http:', 'https:'].includes(u.protocol)) return false;
if (PRIVATE_IP.test(u.hostname)) return false;
return true;
} catch { return false; }
}
const userUrls: string[] = ((user as any)?.quoteSourceUrls?.filter(Boolean) || []).filter(isSafeQuoteUrl);
for (const url of userUrls) {
try {
const res = await fetch(url, { signal: AbortSignal.timeout(4000) });

View File

@ -2,15 +2,13 @@
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/lib/auth";
import { PrismaClient } from '@prisma/client';
import { prisma } from '@/lib/prisma';
import { createGoogleClient, createGoogleTaskList } from '@/lib/google-tasks';
import { createMsTodoList } from '@/lib/microsoft-todo';
import { getOutlookAccessToken } from '@/lib/outlook-token';
import { createAppleReminderList } from '@/lib/apple-reminders';
import { createSynologyReminderList } from '@/lib/synology-tasks';
const prisma = new PrismaClient();
export async function POST(req: NextRequest) {
try {
const session = await getServerSession(authOptions);

View File

@ -1,11 +1,9 @@
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/lib/auth";
import { PrismaClient } from '@prisma/client';
import { prisma } from '@/lib/prisma';
import { notifyUser } from '@/lib/sse';
const prisma = new PrismaClient();
async function resolveUserId(email: string): Promise<string | null> {
const user = await prisma.user.findUnique({
where: { email },

View File

@ -2,13 +2,11 @@
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/lib/auth";
import { PrismaClient } from '@prisma/client';
import { prisma } from '@/lib/prisma';
import { createGoogleClient, fetchGoogleTasks, fetchGoogleTaskLists } from '@/lib/google-tasks';
import { fetchMsTodoLists, fetchMsTodoTasks, isMsTodoTaskCompleted, fetchMsChecklistItems } from '@/lib/microsoft-todo';
import { getOutlookAccessToken } from '@/lib/outlook-token';
const prisma = new PrismaClient();
interface SourceList {
id: string;
title: string;

View File

@ -1,15 +1,13 @@
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/lib/auth";
import { PrismaClient } from '@prisma/client';
import { prisma } from '@/lib/prisma';
import { createGoogleClient, fetchGoogleTaskLists } from '@/lib/google-tasks';
import { fetchMsTodoLists } from '@/lib/microsoft-todo';
import { getOutlookAccessToken } from '@/lib/outlook-token';
export const dynamic = 'force-dynamic';
const prisma = new PrismaClient();
export async function GET(req: NextRequest) {
try {
const session = await getServerSession(authOptions);

View File

@ -1,11 +1,10 @@
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/lib/auth";
import { PrismaClient, Task } from '@prisma/client';
import { Task } from '@prisma/client';
import { prisma } from '@/lib/prisma';
import { notifyUser } from '@/lib/sse';
const prisma = new PrismaClient();
// Validate and sanitize a URL — only allow http/https, reject javascript: and data: schemes
function sanitizeUrl(raw: string | null | undefined): string | null {
if (!raw) return null;

View File

@ -13,7 +13,7 @@ export const authOptions: NextAuthOptions = {
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID || "",
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
allowDangerousEmailAccountLinking: true,
allowDangerousEmailAccountLinking: false,
authorization: {
params: {
prompt: "consent",
@ -26,13 +26,13 @@ export const authOptions: NextAuthOptions = {
AppleProvider({
clientId: process.env.APPLE_ID || "",
clientSecret: process.env.APPLE_SECRET || "",
allowDangerousEmailAccountLinking: true,
allowDangerousEmailAccountLinking: false,
}),
AzureADProvider({
clientId: process.env.MICROSOFT_CLIENT_ID || "",
clientSecret: process.env.MICROSOFT_CLIENT_SECRET || "",
tenantId: "common",
allowDangerousEmailAccountLinking: true,
allowDangerousEmailAccountLinking: false,
authorization: {
params: {
prompt: "consent",
@ -85,7 +85,7 @@ export const authOptions: NextAuthOptions = {
}
})
],
debug: true,
debug: process.env.NODE_ENV === 'development',
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days

View File

@ -108,9 +108,11 @@ export async function sendVerificationEmail(
const from = process.env.SMTP_FROM || '"My Weekly ToDo\'s" <mail@carrylight.de>';
console.log(`[EMAIL] Sending verification email to ${email} from ${from}`);
console.log(`\n======================================================`);
console.log(`[DEV VERIFICATION LINK]:\n${verifyLink}`);
console.log(`======================================================\n`);
if (process.env.NODE_ENV === 'development') {
console.log(`\n======================================================`);
console.log(`[DEV VERIFICATION LINK]:\n${verifyLink}`);
console.log(`======================================================\n`);
}
try {
// 15 second timeout for SMTP
@ -191,9 +193,11 @@ export async function sendPasswordResetEmail(email: string, token: string) {
const from = process.env.SMTP_FROM || '"My Weekly ToDo\'s" <mail@carrylight.de>';
console.log(`[EMAIL] Sending password reset email to ${email} from ${from}`);
console.log(`\n======================================================`);
console.log(`[DEV RESET LINK]:\n${resetLink}`);
console.log(`======================================================\n`);
if (process.env.NODE_ENV === 'development') {
console.log(`\n======================================================`);
console.log(`[DEV RESET LINK]:\n${resetLink}`);
console.log(`======================================================\n`);
}
try {
const timeoutPromise = new Promise((_, reject) =>

View File

@ -1,7 +1,6 @@
{
"compilerOptions": {
"target": "es2017",
"downlevelIteration": true,
"lib": [
"dom",
"dom.iterable",
@ -14,7 +13,7 @@
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",

File diff suppressed because one or more lines are too long