File size: 3,212 Bytes
3353b25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
import { NextRequest } from 'next/server';
import { getUserById, getApiKeyByHash, updateApiKeyLastUsed } from './db';

const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production';
const JWT_EXPIRES_IN = '7d';

export interface JWTPayload {
    userId: string;
    email: string;
    type: 'user' | 'apikey';
}

// Password hashing
export async function hashPassword(password: string): Promise<string> {
    return bcrypt.hash(password, 10);
}

export async function verifyPassword(password: string, hash: string): Promise<boolean> {
    return bcrypt.compare(password, hash);
}

// JWT Token generation
export function generateToken(payload: JWTPayload): string {
    return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
}

export function verifyToken(token: string): JWTPayload | null {
    try {
        return jwt.verify(token, JWT_SECRET) as JWTPayload;
    } catch {
        return null;
    }
}

// API Key generation
export function generateApiKey(): string {
    const prefix = 'px_';
    const randomPart = Array.from(crypto.getRandomValues(new Uint8Array(32)))
        .map(b => b.toString(16).padStart(2, '0'))
        .join('');
    return `${prefix}${randomPart}`;
}

export async function hashApiKey(key: string): Promise<string> {
    // Use a simple hash for API keys (in production, use proper hashing)
    const encoder = new TextEncoder();
    const data = encoder.encode(key);
    const hashBuffer = await crypto.subtle.digest('SHA-256', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

// Authentication middleware
export async function authenticateRequest(req: NextRequest): Promise<{ userId: string; email?: string; apiKey?: any } | null> {
    // Try JWT token first (from Authorization header or cookie)
    const authHeader = req.headers.get('authorization');
    let token: string | null = null;

    if (authHeader?.startsWith('Bearer ')) {
        token = authHeader.substring(7);
    } else {
        // Try cookie
        token = req.cookies.get('auth_token')?.value || null;
    }

    if (token) {
        const payload = verifyToken(token);
        if (payload && payload.type === 'user') {
            const user = await getUserById(payload.userId);
            if (user) {
                return { userId: user.id, email: user.email };
            }
        }
    }

    // Try API key (X-API-Key header)
    const apiKey = req.headers.get('x-api-key');
    if (apiKey) {
        const keyHash = await hashApiKey(apiKey);
        const apiKeyRecord = await getApiKeyByHash(keyHash);
        if (apiKeyRecord && apiKeyRecord.is_active) {
            await updateApiKeyLastUsed(apiKeyRecord.id);
            return { userId: apiKeyRecord.user_id, apiKey: apiKeyRecord };
        }
    }

    return null;
}

// Require authentication
export async function requireAuth(req: NextRequest): Promise<{ userId: string; email?: string; apiKey?: any }> {
    const auth = await authenticateRequest(req);
    if (!auth) {
        throw new Error('UNAUTHORIZED');
    }
    return auth;
}