Compare commits

..

3 commits

Author SHA1 Message Date
5e36c8a848 remove: /api/setup route — bootstrap should be done via psql, not HTTP
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 11:58:01 +05:30
07bb149dd8 fix: notifications IDOR — verify child belongs to caller's family
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 11:57:57 +05:30
389f66955c fix: stop leaking password reset tokens in response
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 11:57:54 +05:30
3 changed files with 9 additions and 215 deletions

View file

@ -37,8 +37,8 @@ export async function POST(request: Request) {
);
// In production, send email with reset link
// For now, return token for testing
return NextResponse.json({ success: true, token: `reset_${token}`, message: "Reset link sent" });
console.log(`[RESET-TOKEN] user=${user.id} email=${email} token=reset_${token} expires=${expiresAt.toISOString()}`);
return NextResponse.json({ success: true, message: "If email exists, reset link sent" });
} catch (error) {
console.error("Reset request error:", error);
return NextResponse.json({ success: true, message: "If email exists, reset link sent" });

View file

@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { sql } from "@/db";
import { requireFamily, requireOwnership } from "@/lib/auth";
// IAP Vaccination Schedule (weeks from birth)
const IAP_SCHEDULE = [
@ -33,6 +34,9 @@ const IAP_SCHEDULE = [
export async function GET(request: Request) {
try {
const auth = await requireFamily();
if (!auth.success) return NextResponse.json({ error: auth.error }, { status: auth.status });
const { searchParams } = new URL(request.url);
const childId = searchParams.get("childId");
@ -40,6 +44,9 @@ export async function GET(request: Request) {
return NextResponse.json({ error: "childId required" }, { status: 400 });
}
const ownership = await requireOwnership(childId, "children", "Child");
if (!ownership.success) return NextResponse.json({ error: ownership.error }, { status: ownership.status });
// Get child's birth date
const children = await sql`
SELECT id, name, birth_date FROM children WHERE id = ${childId}

View file

@ -1,213 +0,0 @@
import { NextResponse } from "next/server";
import { sql } from "@/db";
export async function GET() {
try {
// Create enums one at a time
await sql.unsafe("CREATE TYPE child_sex AS ENUM('male', 'female', 'other')").catch(() => {});
await sql.unsafe("CREATE TYPE child_stage AS ENUM('newborn', 'infant', 'solids_start', 'toddler_early', 'toddler_late', 'preschool')").catch(() => {});
await sql.unsafe("CREATE TYPE member_role AS ENUM('admin', 'caregiver', 'viewer')").catch(() => {});
// Create users table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT,
email TEXT NOT NULL UNIQUE,
email_verified TIMESTAMP,
image TEXT,
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
)
`).catch(() => {});
// Create accounts table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS accounts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
type TEXT NOT NULL,
provider TEXT NOT NULL,
provider_account_id TEXT NOT NULL,
refresh_token TEXT,
access_token TEXT,
expires_at TIMESTAMP,
token_type TEXT,
scope TEXT,
id_token TEXT,
session_state TEXT,
UNIQUE(provider, provider_account_id)
)
`).catch(() => {});
// Create sessions table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_token TEXT NOT NULL UNIQUE,
user_id UUID NOT NULL REFERENCES users(id),
expires TIMESTAMP NOT NULL
)
`).catch(() => {});
// Create verification_tokens table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS verification_tokens (
identifier TEXT NOT NULL,
token TEXT NOT NULL,
expires TIMESTAMP NOT NULL,
UNIQUE(identifier, token)
)
`).catch(() => {});
// Create families table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS families (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
)
`).catch(() => {});
// Create family_members table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS family_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
family_id UUID NOT NULL REFERENCES families(id),
user_id UUID NOT NULL REFERENCES users(id),
role member_role NOT NULL DEFAULT 'caregiver',
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
UNIQUE(family_id, user_id)
)
`).catch(() => {});
// Create children table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS children (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
family_id UUID NOT NULL REFERENCES families(id),
name TEXT NOT NULL,
birth_date DATE NOT NULL,
sex child_sex,
stage child_stage NOT NULL DEFAULT 'newborn',
image_url TEXT,
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP DEFAULT NOW() NOT NULL
)
`).catch(() => {});
// Create family_invites table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS family_invites (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
family_id UUID NOT NULL REFERENCES families(id),
email TEXT NOT NULL,
role member_role NOT NULL DEFAULT 'viewer',
token TEXT NOT NULL UNIQUE,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
UNIQUE(family_id, email)
)
`).catch(() => {});
// === LOG TABLES ===
// Feed type enum
await sql.unsafe("CREATE TYPE feed_type AS ENUM('breast_milk', 'formula', 'solid', 'water', 'other')").catch(() => {});
await sql.unsafe("CREATE TYPE feed_method AS ENUM('bottle', 'breast_left', 'breast_right', 'breast_both', 'cup', 'spoon', 'finger', 'self')").catch(() => {});
await sql.unsafe("CREATE TYPE diaper_type AS ENUM('wet', 'dirty', 'both', 'dry')").catch(() => {});
await sql.unsafe("CREATE TYPE sleep_type AS ENUM('nap', 'night')").catch(() => {});
// Feeds table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS feeds (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
child_id UUID NOT NULL REFERENCES children(id),
type feed_type NOT NULL,
method feed_method,
amount_ml REAL,
notes TEXT,
logged_at TIMESTAMP DEFAULT NOW() NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL
)
`).catch(() => {});
// Diapers table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS diapers_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
child_id UUID NOT NULL REFERENCES children(id),
type diaper_type NOT NULL,
notes TEXT,
logged_at TIMESTAMP DEFAULT NOW() NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL
)
`).catch(() => {});
// Sleeps table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS sleeps (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
child_id UUID NOT NULL REFERENCES children(id),
type sleep_type NOT NULL,
started_at TIMESTAMP,
ended_at TIMESTAMP,
duration_minutes INTEGER,
notes TEXT,
logged_at TIMESTAMP DEFAULT NOW() NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL
)
`).catch(() => {});
// Vaccinations table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS vaccinations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
child_id UUID NOT NULL REFERENCES children(id),
vaccine_name TEXT NOT NULL,
scheduled_date DATE NOT NULL,
given_date DATE,
status TEXT NOT NULL DEFAULT 'pending',
provider TEXT,
lot_number TEXT,
notes TEXT,
created_at TIMESTAMP DEFAULT NOW() NOT NULL
)
`).catch(() => {});
// Growth table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS growth (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
child_id UUID NOT NULL REFERENCES children(id),
measured_at TIMESTAMP NOT NULL,
weight_kg REAL,
height_cm REAL,
head_circumference_cm REAL,
notes TEXT,
created_at TIMESTAMP DEFAULT NOW() NOT NULL
)
`).catch(() => {});
// Medications table
await sql.unsafe(`
CREATE TABLE IF NOT EXISTS medications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
child_id UUID NOT NULL REFERENCES children(id),
name TEXT NOT NULL,
dosage TEXT,
frequency TEXT,
start_date DATE NOT NULL,
end_date DATE,
active BOOLEAN NOT NULL DEFAULT true,
notes TEXT,
created_at TIMESTAMP DEFAULT NOW() NOT NULL
)
`).catch(() => {});
return NextResponse.json({ success: true, message: "All tables created" });
} catch (error) {
console.error(error);
return NextResponse.json({ error: String(error) }, { status: 500 });
}
}