82 lines
2.1 KiB
TypeScript
82 lines
2.1 KiB
TypeScript
import { SignJWT, jwtVerify } from "jose"
|
|
import { cookies } from "next/headers"
|
|
import { APP_CONFIG } from "./config"
|
|
|
|
const secretKey = APP_CONFIG.JWT_SECRET
|
|
const key = new TextEncoder().encode(secretKey)
|
|
|
|
export interface SessionPayload {
|
|
userId: string
|
|
username: string
|
|
role: string
|
|
expiresAt: Date
|
|
}
|
|
|
|
export async function encrypt(payload: SessionPayload) {
|
|
return await new SignJWT(payload)
|
|
.setProtectedHeader({ alg: "HS256" })
|
|
.setIssuedAt()
|
|
.setExpirationTime("24h")
|
|
.sign(key)
|
|
}
|
|
|
|
export async function decrypt(input: string): Promise<SessionPayload> {
|
|
const { payload } = await jwtVerify(input, key, {
|
|
algorithms: ["HS256"],
|
|
})
|
|
return payload as SessionPayload
|
|
}
|
|
|
|
export async function createSession(userId: string, username: string, role: string) {
|
|
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours
|
|
const session = await encrypt({ userId, username, role, expiresAt })
|
|
|
|
cookies().set("session", session, {
|
|
expires: expiresAt,
|
|
httpOnly: true,
|
|
secure: APP_CONFIG.NODE_ENV === "production",
|
|
sameSite: "lax",
|
|
path: "/",
|
|
})
|
|
}
|
|
|
|
export async function verifySession() {
|
|
const cookie = cookies().get("session")?.value
|
|
if (!cookie) return null
|
|
|
|
try {
|
|
const session = await decrypt(cookie)
|
|
if (new Date(session.expiresAt) < new Date()) {
|
|
return null
|
|
}
|
|
return session
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export async function deleteSession() {
|
|
cookies().delete("session")
|
|
}
|
|
|
|
// Rate limiting store (in production, use Redis)
|
|
const rateLimitStore = new Map<string, { count: number; resetTime: number }>()
|
|
|
|
export function rateLimit(identifier: string, limit = 5, windowMs: number = 15 * 60 * 1000) {
|
|
const now = Date.now()
|
|
const key = identifier
|
|
const record = rateLimitStore.get(key)
|
|
|
|
if (!record || now > record.resetTime) {
|
|
rateLimitStore.set(key, { count: 1, resetTime: now + windowMs })
|
|
return { success: true, remaining: limit - 1 }
|
|
}
|
|
|
|
if (record.count >= limit) {
|
|
return { success: false, remaining: 0, resetTime: record.resetTime }
|
|
}
|
|
|
|
record.count++
|
|
return { success: true, remaining: limit - record.count }
|
|
}
|