fixed API and stopping delay
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { randomBytes, createHash, timingSafeEqual } from 'node:crypto';
|
||||
import { getDb } from './db/index.js';
|
||||
|
||||
const SESSION_DAYS = 30;
|
||||
@@ -66,7 +66,82 @@ function appendSetCookieRaw(res, value) {
|
||||
else res.setHeader('Set-Cookie', [prev, value]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// API key auth
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function hashApiKey(key) {
|
||||
return createHash('sha256').update(key).digest('hex');
|
||||
}
|
||||
|
||||
export function createApiKey(userId, label = '') {
|
||||
const key = 'oradio_' + randomBytes(32).toString('hex');
|
||||
getDb().prepare('INSERT INTO api_keys (key_hash, label, user_id) VALUES (?, ?, ?)')
|
||||
.run(hashApiKey(key), label, userId);
|
||||
return key; // shown exactly once — never stored in plain text
|
||||
}
|
||||
|
||||
export function getUserByApiKey(key) {
|
||||
if (!key || typeof key !== 'string') return null;
|
||||
// Constant-time-safe: derive the hash first, then look it up by hash.
|
||||
const hash = hashApiKey(key);
|
||||
const row = getDb().prepare(`
|
||||
SELECT u.id, u.username, u.role, u.is_main, u.avatar_color, u.avatar_emoji,
|
||||
ak.id AS ak_id
|
||||
FROM api_keys ak JOIN users u ON u.id = ak.user_id
|
||||
WHERE ak.key_hash = ?
|
||||
`).get(hash);
|
||||
if (!row) return null;
|
||||
// Bump last_used_at asynchronously so auth stays fast
|
||||
setImmediate(() => {
|
||||
getDb().prepare('UPDATE api_keys SET last_used_at = datetime(\'now\') WHERE id = ?')
|
||||
.run(row.ak_id);
|
||||
});
|
||||
const { ak_id: _drop, ...user } = row;
|
||||
return user;
|
||||
}
|
||||
|
||||
export function listApiKeys() {
|
||||
return getDb().prepare(
|
||||
'SELECT ak.id, ak.label, ak.created_at, ak.last_used_at, u.username ' +
|
||||
'FROM api_keys ak JOIN users u ON u.id = ak.user_id ORDER BY ak.id'
|
||||
).all();
|
||||
}
|
||||
|
||||
export function revokeApiKey(id) {
|
||||
getDb().prepare('DELETE FROM api_keys WHERE id = ?').run(id);
|
||||
}
|
||||
|
||||
// Registers a raw key from the environment (e.g. ORADIO_API_KEY) so it can
|
||||
// be used immediately without going through the admin UI. Idempotent.
|
||||
export function ensureBootstrapApiKey(rawKey) {
|
||||
if (!rawKey) return;
|
||||
const db = getDb();
|
||||
const hash = hashApiKey(rawKey);
|
||||
if (db.prepare('SELECT 1 FROM api_keys WHERE key_hash = ?').get(hash)) return;
|
||||
const main = db.prepare('SELECT id FROM users WHERE is_main = 1').get()
|
||||
|| db.prepare('SELECT id FROM users ORDER BY id LIMIT 1').get();
|
||||
if (!main) return;
|
||||
db.prepare('INSERT INTO api_keys (key_hash, label, user_id) VALUES (?, ?, ?)')
|
||||
.run(hash, 'env-bootstrap', main.id);
|
||||
console.log('[auth] bootstrap API key registered from ORADIO_API_KEY');
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Middleware
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function authMiddleware(req, _res, next) {
|
||||
// 1. API key: X-Api-Key header or Authorization: Bearer <key>
|
||||
const rawAuth = req.headers.authorization || '';
|
||||
const apiKey = req.headers['x-api-key']
|
||||
|| (rawAuth.startsWith('Bearer ') ? rawAuth.slice(7) : null);
|
||||
if (apiKey) {
|
||||
req.session = {};
|
||||
req.user = getUserByApiKey(apiKey);
|
||||
return next();
|
||||
}
|
||||
// 2. Session cookie (browser UI)
|
||||
const token = readSessionToken(req);
|
||||
req.session = { token };
|
||||
req.user = getUserBySession(token);
|
||||
|
||||
Reference in New Issue
Block a user