Use database sessions with cookie instead of localStorage

This commit is contained in:
Manohar Gupta 2026-05-10 23:40:10 +05:30
parent 1932d2ae6b
commit 57e852bfbc
4 changed files with 148 additions and 53 deletions

View file

@ -46,11 +46,21 @@ export function FamilyProvider({ children: providerChildren }: { children: React
useEffect(() => {
async function fetchFamilyData() {
try {
// Get family_id from localStorage (set during login)
const storedFamilyId = localStorage.getItem("family_id");
const familyIdToUse = storedFamilyId || "default";
// Get current session from database
const sessionRes = await fetch("/api/auth/signin");
const sessionData = await sessionRes.json();
const res = await fetch(`/api/children?familyId=${familyIdToUse}`);
if (!sessionData.authenticated) {
// Not logged in, use default
setFamilyId("default");
setLoading(false);
return;
}
const sessionFamilyId = sessionData.familyId || "default";
// Fetch children for this family
const res = await fetch(`/api/children?familyId=${sessionFamilyId}`);
const data = await res.json();
if (data.children?.length > 0) {
@ -66,16 +76,9 @@ export function FamilyProvider({ children: providerChildren }: { children: React
setChildId(childList[0].id);
}
setFamilyId(familyIdToUse);
// Get tier and limits from family
const familyRes = await fetch(`/api/family?familyId=${familyIdToUse}`);
const familyData = await familyRes.json();
if (familyData.family) {
setTier(familyData.family.tier || "free");
setMemberCount(familyData.family.max_members || 2);
}
setFamilyId(sessionFamilyId);
setTier(sessionData.tier || "free");
setMemberCount(2);
} catch (err) {
console.error("Failed to fetch family:", err);
} finally {
@ -86,10 +89,6 @@ export function FamilyProvider({ children: providerChildren }: { children: React
fetchFamilyData();
}, []);
// Check if can add more children/members based on tier limits
const canAddChild = () => memberCount < 2 || tier === "pro"; // free = 1 child, pro = unlimited
const canAddMember = () => memberCount < 2 && tier === "free" ? false : true;
return (
<FamilyContext.Provider
value={{

View file

@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { sql } from "@/db";
import { cookies } from "next/headers";
export async function POST(request: Request) {
const { email } = await request.json();
@ -23,8 +24,19 @@ export async function POST(request: Request) {
}
const user = users[0];
const userId = user.id;
const familyId = user.family_id;
// Create session in database
const sessionToken = crypto.randomUUID();
const expires = new Date();
expires.setDate(expires.getDate() + 30); // 30 days
await sql`
INSERT INTO sessions (session_token, user_id, expires)
VALUES ${sql(sessionToken, userId, expires)}
`;
// Get family info
let family = null;
if (familyId) {
@ -37,16 +49,73 @@ export async function POST(request: Request) {
}
}
// Return user and family info
return NextResponse.json({
// Create response with cookie
const response = NextResponse.json({
success: true,
userId: user.id,
email: user.email,
familyId: familyId,
family: family,
});
// Set session cookie (httpOnly, secure, sameSite)
response.cookies.set("session", sessionToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 60 * 60 * 24 * 30, // 30 days
path: "/",
});
return response;
} catch (error) {
console.error("Signin error:", error);
return NextResponse.json({ error: String(error) }, { status: 500 });
}
}
// GET current session
export async function GET() {
try {
const cookieStore = await cookies();
const sessionToken = cookieStore.get("session")?.value;
if (!sessionToken) {
return NextResponse.json({ authenticated: false });
}
// Look up session
const sessions = await sql`
SELECT s.user_id, s.expires, u.email
FROM sessions s
JOIN users u ON u.id = s.user_id
WHERE s.session_token = ${sessionToken}
AND s.expires > NOW()
`;
if (!sessions || sessions.length === 0) {
return NextResponse.json({ authenticated: false });
}
const session = sessions[0];
// Get family via family_members
const members = await sql`
SELECT fm.family_id, f.name as family_name, f.tier
FROM family_members fm
JOIN families f ON f.id = fm.family_id
WHERE fm.user_id = ${session.user_id}
`;
return NextResponse.json({
authenticated: true,
userId: session.user_id,
email: session.email,
familyId: members[0]?.family_id,
familyName: members[0]?.family_name,
tier: members[0]?.tier,
});
} catch (error) {
return NextResponse.json({ authenticated: false });
}
}

View file

@ -0,0 +1,31 @@
import { NextResponse } from "next/server";
import { sql } from "@/db";
import { cookies } from "next/headers";
export async function POST() {
try {
const cookieStore = await cookies();
const sessionToken = cookieStore.get("session")?.value;
if (sessionToken) {
// Delete session from database
await sql`
DELETE FROM sessions WHERE session_token = ${sessionToken}
`;
}
// Clear cookie
const response = NextResponse.json({ success: true });
response.cookies.set("session", "", {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 0,
path: "/",
});
return response;
} catch (error) {
return NextResponse.json({ error: String(error) }, { status: 500 });
}
}

View file

@ -1,18 +1,30 @@
"use client";
import { useState } from "react";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
export default function LoginPage() {
const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false);
const router = useRouter();
useEffect(() => {
// Check if already logged in via session cookie
async function checkSession() {
const res = await fetch("/api/auth/signin");
const data = await res.json();
if (data.authenticated) {
router.push("/");
}
}
checkSession();
}, [router]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
const form = e.target as HTMLFormElement;
const email = (form.elements.namedItem("email") as HTMLInputElement)?.value;
if (!email) return;
try {
const res = await fetch("/api/auth/signin", {
method: "POST",
headers: { "Content-Type": "application/json" },
@ -22,24 +34,10 @@ export default function LoginPage() {
const data = await res.json();
if (data.success) {
// Store user and family info
localStorage.setItem("user_id", data.userId);
localStorage.setItem("user_email", data.email);
if (data.familyId) {
localStorage.setItem("family_id", data.familyId);
}
if (data.family) {
localStorage.setItem("family", JSON.stringify(data.family));
}
router.push("/");
} else {
alert(data.error || "Sign in failed");
}
} catch (err) {
console.error(err);
}
setLoading(false);
};
return (
@ -50,19 +48,17 @@ export default function LoginPage() {
<form onSubmit={handleSubmit} className="space-y-4">
<input
name="email"
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full p-4 border rounded-2xl bg-white shadow-sm"
required
/>
<button
type="submit"
disabled={loading}
className="w-full p-4 bg-rose-400 text-white rounded-2xl font-medium"
>
{loading ? "Signing in..." : "Sign In"}
Sign In
</button>
</form>
</div>