68 lines
2.1 KiB
TypeScript
68 lines
2.1 KiB
TypeScript
import { SignJWT, jwtVerify } from 'jose';
|
|
import { NextRequest } from 'next/server';
|
|
|
|
const ALGORITHM = 'HS256';
|
|
const TOKEN_EXP_HOURS = 24;
|
|
|
|
function getSecret() {
|
|
if (!process.env.AUTH_SECRET) {
|
|
throw new Error('AUTH_SECRET is not set');
|
|
}
|
|
return new TextEncoder().encode(process.env.AUTH_SECRET);
|
|
}
|
|
|
|
export async function signAccessToken(payload: { userId: string; role: string }) {
|
|
const secret = getSecret();
|
|
const exp = Math.floor(Date.now() / 1000) + TOKEN_EXP_HOURS * 3600;
|
|
return new SignJWT(payload).setProtectedHeader({ alg: ALGORITHM }).setExpirationTime(exp).sign(secret);
|
|
}
|
|
|
|
export async function verifyAccessToken(token: string) {
|
|
const secret = getSecret();
|
|
const { payload } = await jwtVerify(token, secret, { algorithms: [ALGORITHM] });
|
|
return payload as { userId: string; role: string };
|
|
}
|
|
|
|
export async function getAuthFromRequest(request: Request | NextRequest) {
|
|
let token: string | null = null;
|
|
|
|
const header = request.headers.get('authorization');
|
|
if (header?.startsWith('Bearer ')) {
|
|
token = header.slice('Bearer '.length);
|
|
}
|
|
|
|
if (!token) {
|
|
const cookieHeader = request.headers.get('cookie') ?? '';
|
|
const match = cookieHeader.split(';').map((c) => c.trim()).find((c) => c.startsWith('session_token='));
|
|
if (match) {
|
|
token = decodeURIComponent(match.split('=')[1]);
|
|
}
|
|
}
|
|
|
|
if (!token) {
|
|
return null;
|
|
}
|
|
try {
|
|
return await verifyAccessToken(token);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function requireAuth(request: Request | NextRequest) {
|
|
const auth = await getAuthFromRequest(request);
|
|
if (!auth) {
|
|
throw new Error('Unauthorized');
|
|
}
|
|
return auth;
|
|
}
|
|
|
|
export function buildSessionCookie(token: string) {
|
|
const secure = process.env.APP_URL?.startsWith('https://') || process.env.NODE_ENV === 'production';
|
|
const maxAge = TOKEN_EXP_HOURS * 3600;
|
|
return `session_token=${encodeURIComponent(token)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAge};${secure ? ' Secure;' : ''}`;
|
|
}
|
|
|
|
export function clearSessionCookie() {
|
|
return 'session_token=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0;';
|
|
}
|