Files
backerup-website/frontend/lib/session.ts
T

82 lines
2.6 KiB
TypeScript

import { SignJWT, jwtVerify } from 'jose'
import { cookies } from 'next/headers'
export type Role = 'admin' | 'user'
export interface SessionPayload {
userId: string
username: string
role: Role
}
export const SESSION_COOKIE = 'backerup_session'
const SESSION_DURATION_MS = 7 * 24 * 60 * 60 * 1000 // 7 days
// Dev fallback — NOT safe for production (use AUTH_SECRET env var)
const DEV_FALLBACK_SECRET = 'dev-secret-NOT-for-production-change-me-32chars!!'
export function getSecretKey(): Uint8Array {
const secret = process.env.AUTH_SECRET
if (!secret) {
if (process.env.NODE_ENV !== 'production') {
return new TextEncoder().encode(DEV_FALLBACK_SECRET)
}
throw new Error('AUTH_SECRET environment variable is required in production')
}
if (secret.length < 32) {
throw new Error('AUTH_SECRET must be at least 32 characters long')
}
return new TextEncoder().encode(secret)
}
/** Creates and stores a session JWT in an httpOnly cookie. */
export async function createSession(payload: SessionPayload, secure?: boolean): Promise<void> {
const expiresAt = new Date(Date.now() + SESSION_DURATION_MS)
const token = await new SignJWT({ ...payload })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime(expiresAt)
.sign(getSecretKey())
// If the caller explicitly passes a value, honour it.
// Otherwise fall back to NODE_ENV (safe default for fully-HTTPS deploys).
const isSecure = secure !== undefined ? secure : process.env.NODE_ENV === 'production'
const cookieStore = await cookies()
cookieStore.set(SESSION_COOKIE, token, {
httpOnly: true,
secure: isSecure,
sameSite: 'lax',
expires: expiresAt,
path: '/',
})
}
/** Reads and validates the current session cookie. Returns null if missing/invalid. */
export async function getSession(): Promise<SessionPayload | null> {
const cookieStore = await cookies()
const token = cookieStore.get(SESSION_COOKIE)?.value
if (!token) return null
return verifyToken(token)
}
/** Deletes the session cookie. */
export async function deleteSession(): Promise<void> {
const cookieStore = await cookies()
cookieStore.delete(SESSION_COOKIE)
}
/** Verifies a JWT token string. Edge-runtime safe (uses jose). */
export async function verifyToken(token: string): Promise<SessionPayload | null> {
try {
const { payload } = await jwtVerify(token, getSecretKey(), { algorithms: ['HS256'] })
return {
userId: payload.userId as string,
username: payload.username as string,
role: payload.role as Role,
}
} catch {
return null
}
}