feat: login page, session management, auth API routes en proxy guard

- lib/session.ts: token generatie, SHA-256 hashing, createSession/getCurrentUser/invalidateSession
- app/api/auth/login: bcrypt verificatie, session aanmaken, ops_session cookie (httpOnly, sameSite=strict, 24h TTL), rate-limit 5/min per IP
- app/api/auth/logout: session invalideren en cookie verwijderen
- app/login/page.tsx: login form (client component)
- proxy.ts: route-protectie – redirect naar /login zonder sessie (middleware.ts is deprecated in Next.js 16)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scrum4Me Agent 2026-05-13 17:10:07 +02:00
parent cce0f25419
commit be05724de0
5 changed files with 250 additions and 0 deletions

52
lib/session.ts Normal file
View file

@ -0,0 +1,52 @@
import 'server-only'
import { cookies } from 'next/headers'
import { createHash, randomBytes } from 'crypto'
import { prisma } from './prisma'
const COOKIE_NAME = 'ops_session'
const SESSION_TTL_MS = 24 * 60 * 60 * 1000
export function generateSessionToken(): string {
return randomBytes(32).toString('hex')
}
export function hashToken(token: string): string {
return createHash('sha256').update(token).digest('hex')
}
export async function createSession(userId: string, token: string): Promise<void> {
const expiresAt = new Date(Date.now() + SESSION_TTL_MS)
await prisma.session.create({
data: {
user_id: userId,
token_hash: hashToken(token),
expires_at: expiresAt,
},
})
}
export async function getCurrentUser() {
const cookieStore = await cookies()
const token = cookieStore.get(COOKIE_NAME)?.value
if (!token) return null
const session = await prisma.session.findUnique({
where: { token_hash: hashToken(token) },
include: { user: true },
})
if (!session) return null
if (session.expires_at < new Date()) {
await prisma.session.delete({ where: { id: session.id } })
return null
}
return session.user
}
export async function invalidateSession(token: string): Promise<void> {
await prisma.session.deleteMany({
where: { token_hash: hashToken(token) },
})
}