File size: 3,536 Bytes
8e23875
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c0b84a6
8e23875
 
c0b84a6
86c1aaf
c0b84a6
8e23875
6bb9678
c0b84a6
 
6bb9678
c0b84a6
86c1aaf
c0b84a6
 
 
 
6bb9678
 
 
c0b84a6
8e23875
c0b84a6
6bb9678
8e23875
 
 
 
 
6bb9678
8e23875
 
e9b2cbc
8e23875
86c1aaf
 
 
 
 
 
 
 
 
 
8e23875
 
 
 
 
6bb9678
8e23875
 
 
6bb9678
8e23875
6bb9678
 
8e23875
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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 };
}