File size: 3,742 Bytes
fcf8749 | 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 | const crypto = require('crypto');
const prisma = require('../config/database');
// In-memory store for demo mode (when DB is unavailable)
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');
}
// Generate an API key
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 Prisma first, fall back to memory
try {
await prisma.apiKey.create({
data: { id, name, keyHash, userId, active: true }
});
} catch {
// Demo mode — store in memory
memoryKeys.set(keyHash, { ...record, keyHash });
}
// Return the actual key ONCE (never again)
res.status(201).json({ ...record, key, message: 'Save this key — it will not be shown again.' });
};
// List keys for a user (masked)
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 {
// Demo mode — return memory + some seed data
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]);
}
};
// Revoke a key
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' });
};
// Middleware: validate x-api-key on /v1/* routes
exports.validateApiKey = async (req, res, next) => {
const key = req.headers['x-api-key'];
// Allow demo key only in non-production
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) {
// Demo mode -- check memory store
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.' });
}
};
|