| const crypto = require('crypto'); |
| const prisma = require('../config/database'); |
|
|
| |
| const memoryKeys = new Map(); |
|
|
| function generateKey() { |
| const raw = crypto.randomBytes(24).toString('base64url'); |
| return `fr_live_${raw}`; |
| } |
|
|
| function hashKey(key) { |
| return crypto.createHash('sha256').update(key).digest('hex'); |
| } |
|
|
| |
| exports.createApiKey = async (req, res) => { |
| const { name = 'Default Key', userId = 'demo-user' } = req.body; |
| const key = generateKey(); |
| const keyHash = hashKey(key); |
| const id = crypto.randomUUID(); |
| const record = { |
| id, |
| name, |
| keyPreview: key.substring(0, 12) + '••••••••••••', |
| createdAt: new Date().toISOString(), |
| lastUsed: null, |
| active: true, |
| userId, |
| }; |
|
|
| |
| try { |
| await prisma.apiKey.create({ |
| data: { id, name, keyHash, userId, active: true } |
| }); |
| } catch { |
| |
| memoryKeys.set(keyHash, { ...record, keyHash }); |
| } |
|
|
| |
| res.status(201).json({ ...record, key, message: 'Save this key — it will not be shown again.' }); |
| }; |
|
|
| |
| exports.listApiKeys = async (req, res) => { |
| const { userId = 'demo-user' } = req.query; |
|
|
| try { |
| const keys = await prisma.apiKey.findMany({ |
| where: { userId, active: true }, |
| select: { id: true, name: true, keyPreview: true, createdAt: true, lastUsed: true, active: true } |
| }); |
| return res.json(keys); |
| } catch { |
| |
| const seedKeys = [ |
| { id: 'seed-001', name: 'Production Key', keyPreview: 'fr_live_••••••••', createdAt: new Date(Date.now() - 86400000 * 3).toISOString(), lastUsed: new Date().toISOString(), active: true }, |
| { id: 'seed-002', name: 'Development Key', keyPreview: 'fr_live_••••••••', createdAt: new Date(Date.now() - 86400000 * 7).toISOString(), lastUsed: null, active: true }, |
| ]; |
| const memKeys = Array.from(memoryKeys.values()).map(k => ({ |
| id: k.id, name: k.name, keyPreview: k.keyPreview, createdAt: k.createdAt, lastUsed: k.lastUsed, active: k.active |
| })); |
| return res.json([...seedKeys, ...memKeys]); |
| } |
| }; |
|
|
| |
| exports.revokeApiKey = async (req, res) => { |
| const { id } = req.params; |
| try { |
| await prisma.apiKey.update({ where: { id }, data: { active: false } }); |
| } catch { |
| memoryKeys.delete(id); |
| } |
| res.json({ success: true, message: 'Key revoked' }); |
| }; |
|
|
| |
| exports.validateApiKey = async (req, res, next) => { |
| const key = req.headers['x-api-key']; |
|
|
| |
| const IS_PROD = process.env.NODE_ENV === 'production'; |
| if (!IS_PROD && (key === 'fr_demo_key' || key === 'fr_live_demo')) return next(); |
|
|
| if (!key || !key.startsWith('fr_')) { |
| return res.status(401).json({ success: false, error: 'Missing or invalid API key. Pass your key in the x-api-key header.' }); |
| } |
|
|
| const keyHash = hashKey(key); |
|
|
| try { |
| const record = await prisma.apiKey.findUnique({ where: { keyHash } }); |
| if (!record || !record.active) throw new Error('Invalid key'); |
| await prisma.apiKey.update({ where: { keyHash }, data: { lastUsed: new Date() } }); |
| req.apiKeyRecord = record; |
| return next(); |
| } catch (dbErr) { |
| |
| const memRecord = memoryKeys.get(keyHash); |
| if (memRecord && memRecord.active) { |
| memRecord.lastUsed = new Date().toISOString(); |
| return next(); |
| } |
| return res.status(401).json({ success: false, error: 'Invalid API key.' }); |
| } |
| }; |
|
|