89 lines
2.4 KiB
TypeScript
89 lines
2.4 KiB
TypeScript
import crypto from 'crypto';
|
|
import bcrypt from 'bcryptjs';
|
|
import { NextRequest } from 'next/server';
|
|
|
|
if (!process.env.NEXTAUTH_SECRET) {
|
|
throw new Error('FATAL: NEXTAUTH_SECRET environment variable is missing.');
|
|
}
|
|
const JWT_SECRET = process.env.NEXTAUTH_SECRET;
|
|
|
|
export interface UserSession {
|
|
userId: number;
|
|
username: string;
|
|
email: string;
|
|
role: string;
|
|
hotelId: number;
|
|
regionId: number;
|
|
}
|
|
|
|
// Base64URL encoding helpers
|
|
function base64urlEncode(str: string): string {
|
|
return Buffer.from(str).toString('base64url');
|
|
}
|
|
|
|
function base64urlDecode(str: string): string {
|
|
return Buffer.from(str, 'base64url').toString('utf8');
|
|
}
|
|
|
|
// Sign JWT natively
|
|
export function signJwt(payload: object, expiresInSeconds = 86400): string {
|
|
const header = { alg: 'HS256', typ: 'JWT' };
|
|
const exp = Math.floor(Date.now() / 1000) + expiresInSeconds;
|
|
|
|
const fullPayload = { ...payload, exp };
|
|
|
|
const encodedHeader = base64urlEncode(JSON.stringify(header));
|
|
const encodedPayload = base64urlEncode(JSON.stringify(fullPayload));
|
|
|
|
const signature = crypto
|
|
.createHmac('sha256', JWT_SECRET)
|
|
.update(`${encodedHeader}.${encodedPayload}`)
|
|
.digest('base64url');
|
|
|
|
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
|
}
|
|
|
|
// Verify JWT natively
|
|
export function verifyJwt(token: string): UserSession | null {
|
|
try {
|
|
const parts = token.split('.');
|
|
if (parts.length !== 3) return null;
|
|
|
|
const [headerB64, payloadB64, signature] = parts;
|
|
|
|
const expectedSignature = crypto
|
|
.createHmac('sha256', JWT_SECRET)
|
|
.update(`${headerB64}.${payloadB64}`)
|
|
.digest('base64url');
|
|
|
|
if (signature !== expectedSignature) return null;
|
|
|
|
const payload = JSON.parse(base64urlDecode(payloadB64));
|
|
|
|
// Check expiration
|
|
if (payload.exp && Date.now() / 1000 > payload.exp) {
|
|
return null;
|
|
}
|
|
|
|
return payload as UserSession;
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Compare password hash
|
|
export async function comparePassword(password: string, hash: string): Promise<boolean> {
|
|
return bcrypt.compare(password, hash);
|
|
}
|
|
|
|
// Hash password
|
|
export async function hashPassword(password: string): Promise<string> {
|
|
return bcrypt.hash(password, 10);
|
|
}
|
|
|
|
// Get session from request cookies
|
|
export function getSession(req: NextRequest): UserSession | null {
|
|
const cookie = req.cookies.get('session');
|
|
if (!cookie?.value) return null;
|
|
return verifyJwt(cookie.value);
|
|
}
|