79 lines
2.1 KiB
TypeScript
79 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() {
|
|
const secret = process.env.AUTH_SECRET || "dev-auth-secret";
|
|
return new TextEncoder().encode(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;";
|
|
}
|