All files / lib auth.ts

93.93% Statements 31/33
77.77% Branches 7/9
100% Functions 5/5
93.93% Lines 31/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 471x 1x   1x   5x 5x 5x     5x 5x               1x 1x 1x   2x 2x 2x   2x 2x 2x 2x 2x 2x 2x   3x 3x 3x 1x 3x 2x 2x 3x   1x 1x  
import { SignJWT, jwtVerify, type JWTPayload } from 'jose';
import bcrypt from 'bcryptjs';
 
const SESSION_TTL_SECONDS = Number(process.env.SESSION_TTL_SECONDS ?? 28800);
 
function getSecret(): Uint8Array {
  const secret = process.env.JWT_SECRET;
  if (!secret || secret.length < 32) {
    throw new Error('JWT_SECRET is missing or too short (need at least 32 chars).');
  }
  return new TextEncoder().encode(secret);
}
 
export type SessionPayload = {
  sub: string;
  username: string;
  role: 'MASTER' | 'STAFF';
};
 
export async function hashPassword(plain: string): Promise<string> {
  return bcrypt.hash(plain, 10);
}
 
export async function verifyPassword(plain: string, hash: string): Promise<boolean> {
  return bcrypt.compare(plain, hash);
}
 
export async function signSession(payload: SessionPayload): Promise<string> {
  return new SignJWT(payload as unknown as JWTPayload)
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime(`${SESSION_TTL_SECONDS}s`)
    .sign(getSecret());
}
 
export async function verifySession(token: string): Promise<SessionPayload | null> {
  try {
    const { payload } = await jwtVerify(token, getSecret());
    return payload as unknown as SessionPayload;
  } catch {
    return null;
  }
}
 
export const SESSION_COOKIE = 'seo_session';
export const SESSION_MAX_AGE = SESSION_TTL_SECONDS;