import { sql } from '@vercel/postgres'; import jwt from 'jsonwebtoken'; import { JWT_SECRET } from './auth-secret.js'; /** * Get user ID from request headers */ export async function getUserFromRequest(req) { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { // For development, return user ID 1 if no token (should be removed in production) console.warn('⚠️ Development mode: Using fallback user authentication'); return { userId: 1, email: 'admin@tcgvault.com', role: 'admin' }; } const token = authHeader.substring(7); const decoded = jwt.verify(token, JWT_SECRET); // Get user data from database const result = await sql` SELECT id, email, role FROM users WHERE id = ${decoded.userId} `; if (result.rows.length === 0) { return null; } return { userId: result.rows[0].id, email: result.rows[0].email, role: result.rows[0].role }; } catch (error) { console.error('Error getting user from request:', error); return null; } } /** * Check if user has permission to access a collection */ export async function checkCollectionPermission(collectionId, userId, requiredPermission = 'viewer') { try { // Get collection and user permission const result = await sql` SELECT c.id, c.user_id as owner_id, c.is_public, cp.role, cp.status FROM collections c LEFT JOIN collection_permissions cp ON c.id = cp.collection_id AND cp.user_id = ${userId} WHERE c.id = ${collectionId} `; if (result.rows.length === 0) { return { hasAccess: false, reason: 'Collection not found' }; } const collection = result.rows[0]; // Owner always has access if (collection.owner_id === userId) { return { hasAccess: true, role: 'owner', collection }; } // Public collections - everyone can view (but not edit) if (collection.is_public && requiredPermission === 'viewer') { return { hasAccess: true, role: 'viewer', collection }; } // Check explicit permissions if (collection.role && collection.status === 'active') { const hasRequiredPermission = checkRolePermission(collection.role, requiredPermission); if (hasRequiredPermission) { return { hasAccess: true, role: collection.role, collection }; } } // Collections without explicit permission return { hasAccess: false, reason: 'Access denied' }; } catch (error) { console.error('Error checking collection permission:', error); return { hasAccess: false, reason: 'Internal error' }; } } /** * Check if a role has the required permission level */ function checkRolePermission(userRole, requiredPermission) { const roleHierarchy = { viewer: 1, editor: 2, owner: 3 }; const userLevel = roleHierarchy[userRole] || 0; const requiredLevel = roleHierarchy[requiredPermission] || 0; return userLevel >= requiredLevel; } /** * Middleware to protect collection routes */ export function withCollectionPermission(requiredPermission = 'viewer') { return function(handler) { return async function(req, res) { try { const { id: collectionId } = req.query; if (!collectionId) { return res.status(400).json({ error: 'Collection ID is required' }); } const user = await getUserFromRequest(req); if (!user) { return res.status(401).json({ error: 'Authentication required' }); } const permission = await checkCollectionPermission(collectionId, user.userId, requiredPermission); if (!permission.hasAccess) { return res.status(403).json({ error: permission.reason || 'Access denied' }); } // Add user and permission info to request req.user = user; req.permission = permission; return handler(req, res); } catch (error) { console.error('Permission middleware error:', error); return res.status(500).json({ error: 'Internal server error' }); } }; }; } /** * Log collection activity */ export async function logCollectionActivity(collectionId, userId, action, details = {}) { try { await sql` INSERT INTO collection_activity (collection_id, user_id, action, details) VALUES (${collectionId}, ${userId}, ${action}, ${JSON.stringify(details)}) `; } catch (error) { console.error('Error logging collection activity:', error); } }