d4rkbot: API v1 mit Key-System, Env-Diät, Rebranding
- API-Keys (SHA-256-Hash, Scopes, last_used) — Verwaltung auf der Setup-Seite, Klartext-Key wird genau einmal angezeigt - /api/v1: message, dm, roles (add/remove), member/:id, stats — Bearer-Auth mit Scope-Prüfung, Embed-Sanitizing, README-Doku mit Python-Beispiel - Env-Diät: PUBLIC_URL + GITEA_URL jetzt Settings (Env nur Fallback), OAuth-Redirect dynamisch; Env enthält nur noch Secrets/Bootstrap - Rebranding ecobot → d4rkbot (Packages, Container, Cookies, README); Volume-Name bleibt ecobot_data (Datenerhalt), Portainer-Stack-Name bleibt Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
// SQLite-Anbindung (better-sqlite3, synchron & schnell) — Schema wird beim Start angelegt
|
||||
import Database from 'better-sqlite3';
|
||||
import crypto from 'node:crypto';
|
||||
import { mkdirSync, statSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { config } from './config.js';
|
||||
@@ -113,6 +114,56 @@ export function takeBugReport(repo, issueNumber) {
|
||||
return row ?? null;
|
||||
}
|
||||
|
||||
// API-Keys für externe Skripte (/api/v1/*) — nur der SHA-256-Hash wird gespeichert
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS api_keys (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
key_hash TEXT NOT NULL UNIQUE,
|
||||
scopes TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
last_used_at TEXT
|
||||
);
|
||||
`);
|
||||
|
||||
const insertApiKey = db.prepare(
|
||||
'INSERT INTO api_keys (name, key_hash, scopes) VALUES (?, ?, ?)'
|
||||
);
|
||||
const findApiKeyStmt = db.prepare('SELECT * FROM api_keys WHERE key_hash = ?');
|
||||
const touchApiKeyStmt = db.prepare(
|
||||
`UPDATE api_keys SET last_used_at = datetime('now') WHERE id = ?`
|
||||
);
|
||||
const listApiKeysStmt = db.prepare(
|
||||
'SELECT id, name, scopes, created_at, last_used_at FROM api_keys ORDER BY id'
|
||||
);
|
||||
const deleteApiKeyStmt = db.prepare('DELETE FROM api_keys WHERE id = ?');
|
||||
|
||||
const hashKey = (key) => crypto.createHash('sha256').update(key).digest('hex');
|
||||
|
||||
/** Neuen Key erzeugen — der Klartext-Key wird nur einmal zurückgegeben! */
|
||||
export function createApiKey(name, scopes) {
|
||||
const key = `d4rk_${crypto.randomBytes(24).toString('hex')}`;
|
||||
const info = insertApiKey.run(name, hashKey(key), scopes.join(','));
|
||||
return { id: info.lastInsertRowid, key };
|
||||
}
|
||||
|
||||
/** Key prüfen: gibt { id, name, scopes: [] } zurück oder null; aktualisiert last_used */
|
||||
export function verifyApiKey(key) {
|
||||
if (!key?.startsWith('d4rk_')) return null;
|
||||
const row = findApiKeyStmt.get(hashKey(key));
|
||||
if (!row) return null;
|
||||
touchApiKeyStmt.run(row.id);
|
||||
return { id: row.id, name: row.name, scopes: row.scopes.split(',') };
|
||||
}
|
||||
|
||||
export function listApiKeys() {
|
||||
return listApiKeysStmt.all();
|
||||
}
|
||||
|
||||
export function deleteApiKey(id) {
|
||||
return deleteApiKeyStmt.run(id).changes > 0;
|
||||
}
|
||||
|
||||
// Laufzeit-Einstellungen (Settings-Seite im Webinterface) — überschreiben Env-Defaults
|
||||
db.exec('CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)');
|
||||
const getSettingStmt = db.prepare('SELECT value FROM settings WHERE key = ?');
|
||||
|
||||
Reference in New Issue
Block a user