feat: Entra ID login + settings page for integrations

- Add SQLite settings table with getSetting/setSetting/getAllSettings helpers
- Implement Azure OAuth2 authorization code flow via @azure/msal-node
- Add public GET /api/auth/config endpoint for frontend activation check
- Add admin-only GET/PUT /api/settings API with masked secret fields
- CheckMK sync reads credentials from DB settings (env vars as fallback)
- New Settings.tsx: Entra ID and CheckMK configuration cards
- LoginPage: "Sign in with Microsoft" button, shown only when Azure is active
- App.tsx: OAuth callback handling (?token=/?auth_error=), Settings tab for admins
This commit is contained in:
Brückner
2026-06-03 16:02:47 +02:00
parent eed01b9665
commit d364aea4c1
5 changed files with 569 additions and 11 deletions

View File

@ -70,6 +70,12 @@ db.exec(`
createdBy TEXT,
createdAt TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TEXT DEFAULT (datetime('now'))
);
`);
// Lightweight migrations for columns added after the initial release.
@ -83,4 +89,32 @@ function ensureColumn(table: string, column: string, ddl: string) {
ensureColumn('devices', 'checkMkUrl', "checkMkUrl TEXT NOT NULL DEFAULT ''");
// Seed default settings (INSERT OR IGNORE = only if key absent)
const _insertDefault = db.prepare('INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)');
const _defaultSettings: [string, string][] = [
['azure_enabled', 'false'],
['azure_client_id', ''],
['azure_tenant_id', ''],
['azure_client_secret', ''],
['azure_redirect_uri', ''],
['checkmk_api_url', ''],
['checkmk_api_secret', ''],
['checkmk_sync_interval_ms', '60000'],
];
for (const [k, v] of _defaultSettings) _insertDefault.run(k, v);
export function getSetting(key: string): string {
const row = db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as { value: string } | undefined;
return row?.value ?? '';
}
export function setSetting(key: string, value: string): void {
db.prepare("INSERT OR REPLACE INTO settings (key, value, updated_at) VALUES (?, ?, datetime('now'))").run(key, value);
}
export function getAllSettings(): Record<string, string> {
const rows = db.prepare('SELECT key, value FROM settings').all() as { key: string; value: string }[];
return Object.fromEntries(rows.map(r => [r.key, r.value]));
}
export default db;