opentriage-api / src /lib /auth.ts
KrishnaCosmic's picture
apply changes
e9b2cbc
import jwt from "jsonwebtoken";
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/db";
import { users } from "@/db/schema";
import { eq } from "drizzle-orm";
const JWT_SECRET = process.env.JWT_SECRET!;
/**
* Create a JWT token for a user.
*/
export function createJwtToken(userId: string, role: string | null): string {
const payload = {
user_id: userId,
role: role,
exp: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60, // 30 days
};
return jwt.sign(payload, JWT_SECRET, { algorithm: "HS256" });
}
/**
* Verify and decode a JWT token.
*/
export function verifyJwtToken(token: string): { user_id: string; role: string | null } {
try {
const payload = jwt.verify(token, JWT_SECRET, { algorithms: ["HS256"] }) as {
user_id: string;
role: string | null;
};
return payload;
} catch (error) {
throw new Error("Invalid or expired token");
}
}
/**
* Extract user from Authorization header or query param (for SSE).
*/
export async function getCurrentUser(request: NextRequest) {
let token: string | null = null;
// Try Authorization header first
const authHeader = request.headers.get("Authorization");
console.log("[getCurrentUser] Authorization header:", authHeader ? "Present" : "Missing");
if (authHeader && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7);
console.log("[getCurrentUser] Found token in Authorization header");
}
// Fallback to query param for SSE connections
if (!token) {
const url = new URL(request.url);
token = url.searchParams.get("token");
if (token) {
console.log("[getCurrentUser] Found token in query params");
}
}
if (!token) {
console.log("[getCurrentUser] No token found in header or query params");
return null;
}
try {
const payload = verifyJwtToken(token);
console.log("[getCurrentUser] Token verified, user_id:", payload.user_id);
// Fetch full user from database
// NOTE: sync_status columns excluded until Turso migration is applied
const userRecords = await db
.select({
id: users.id,
githubId: users.githubId,
username: users.username,
avatarUrl: users.avatarUrl,
role: users.role,
githubAccessToken: users.githubAccessToken,
createdAt: users.createdAt,
updatedAt: users.updatedAt,
})
.from(users)
.where(eq(users.id, payload.user_id))
.limit(1);
if (userRecords.length === 0) {
console.log("[getCurrentUser] User not found in database for user_id:", payload.user_id);
return null;
}
console.log("[getCurrentUser] User found:", userRecords[0].username);
return userRecords[0];
} catch (error: any) {
console.error("[getCurrentUser] Token verification failed:", error?.message);
return null;
}
}
/**
* Helper to require authentication on a route.
*/
export async function requireAuth(request: NextRequest) {
const user = await getCurrentUser(request);
if (!user) {
return {
user: null,
error: NextResponse.json(
{ error: "Unauthorized" },
{ status: 401 }
),
};
}
return { user, error: null };
}