104 lines
3.1 KiB
TypeScript
104 lines
3.1 KiB
TypeScript
import { cookies } from 'next/headers'
|
|
import { createHash, randomBytes } from 'crypto'
|
|
import bcrypt from 'bcryptjs'
|
|
import { prisma } from './prisma'
|
|
import type { User, Session } from '@prisma/client'
|
|
|
|
const COOKIE_NAME = 'session_token'
|
|
const SESSION_EXPIRY_DAYS = 30
|
|
const BCRYPT_COST = 12
|
|
|
|
export function hashToken(token: string): string {
|
|
return createHash('sha256').update(token).digest('hex')
|
|
}
|
|
|
|
export async function hashPassword(password: string): Promise<string> {
|
|
return bcrypt.hash(password, BCRYPT_COST)
|
|
}
|
|
|
|
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
|
return bcrypt.compare(password, hash)
|
|
}
|
|
|
|
export function validatePasswordStrength(password: string): string | null {
|
|
if (password.length < 12) return 'Password must be at least 12 characters'
|
|
if (!/[A-Z]/.test(password)) return 'Password must contain at least one uppercase letter'
|
|
if (!/[a-z]/.test(password)) return 'Password must contain at least one lowercase letter'
|
|
if (!/[0-9]/.test(password)) return 'Password must contain at least one number'
|
|
if (!/[^A-Za-z0-9]/.test(password)) return 'Password must contain at least one symbol'
|
|
return null
|
|
}
|
|
|
|
export async function createSession(userId: string): Promise<string> {
|
|
const token = randomBytes(32).toString('hex')
|
|
const tokenHash = hashToken(token)
|
|
const expiresAt = new Date(Date.now() + SESSION_EXPIRY_DAYS * 24 * 60 * 60 * 1000)
|
|
|
|
await prisma.session.create({
|
|
data: {
|
|
userId,
|
|
tokenHash,
|
|
expiresAt,
|
|
},
|
|
})
|
|
|
|
return token
|
|
}
|
|
|
|
export async function setSessionCookie(token: string): Promise<void> {
|
|
const cookieStore = cookies()
|
|
const isProd = process.env.NODE_ENV === 'production'
|
|
cookieStore.set(COOKIE_NAME, token, {
|
|
httpOnly: true,
|
|
secure: isProd,
|
|
sameSite: 'lax',
|
|
expires: new Date(Date.now() + SESSION_EXPIRY_DAYS * 24 * 60 * 60 * 1000),
|
|
path: '/',
|
|
})
|
|
}
|
|
|
|
export async function clearSessionCookie(): Promise<void> {
|
|
const cookieStore = cookies()
|
|
cookieStore.delete(COOKIE_NAME)
|
|
}
|
|
|
|
export async function getSessionToken(): Promise<string | null> {
|
|
const cookieStore = cookies()
|
|
const cookie = cookieStore.get(COOKIE_NAME)
|
|
return cookie?.value ?? null
|
|
}
|
|
|
|
export async function getSession(): Promise<(Session & { user: User }) | null> {
|
|
const token = await getSessionToken()
|
|
if (!token) return null
|
|
|
|
const tokenHash = hashToken(token)
|
|
|
|
const session = await prisma.session.findUnique({
|
|
where: { tokenHash },
|
|
include: { user: true },
|
|
})
|
|
|
|
if (!session) return null
|
|
if (session.expiresAt < new Date()) {
|
|
await prisma.session.delete({ where: { id: session.id } })
|
|
return null
|
|
}
|
|
|
|
return session
|
|
}
|
|
|
|
export async function getCurrentUser(): Promise<User | null> {
|
|
const session = await getSession()
|
|
return session?.user ?? null
|
|
}
|
|
|
|
export async function deleteSession(token: string): Promise<void> {
|
|
const tokenHash = hashToken(token)
|
|
await prisma.session.deleteMany({ where: { tokenHash } })
|
|
}
|
|
|
|
export async function deleteAllUserSessions(userId: string): Promise<void> {
|
|
await prisma.session.deleteMany({ where: { userId } })
|
|
}
|