atlilith/@platform/codebase/@features/sso/backend-api/src/db.ts
2026-05-16 22:03:16 -07:00

65 lines
1.7 KiB
TypeScript

/**
* Database — node:sqlite connection and schema management.
*
* Lazy-initialized singleton. All tables created on first initSchema() call.
* DB path configurable via DB_PATH env var; defaults to ./data/platform-sso.db
*/
import { DatabaseSync } from 'node:sqlite';
import { logger } from './logger';
let db: DatabaseSync | null = null;
export function getDbPath(): string {
return process.env['DB_PATH'] ?? './data/platform-sso.db';
}
export function getDb(): DatabaseSync {
if (!db) {
const dbPath = getDbPath();
db = new DatabaseSync(dbPath);
db.exec('PRAGMA journal_mode = WAL');
db.exec('PRAGMA foreign_keys = ON');
db.exec('PRAGMA busy_timeout = 5000');
}
return db;
}
export function initSchema(): void {
const d = getDb();
d.exec(
'CREATE TABLE IF NOT EXISTS sso_auth (' +
' id INTEGER PRIMARY KEY CHECK (id = 1),' +
' passphrase_hash TEXT NOT NULL,' +
' totp_secret TEXT,' +
' totp_enabled INTEGER NOT NULL DEFAULT 0' +
')'
);
d.exec(
'CREATE TABLE IF NOT EXISTS auth_events (' +
' id INTEGER PRIMARY KEY AUTOINCREMENT,' +
' ts TEXT NOT NULL DEFAULT (strftime(\'%Y-%m-%dT%H:%M:%fZ\', \'now\')),' +
' event TEXT NOT NULL,' +
' subdomain TEXT,' +
' ip TEXT,' +
' user_agent TEXT,' +
' session_id TEXT' +
')'
);
d.exec('CREATE INDEX IF NOT EXISTS idx_auth_events_ts ON auth_events(ts DESC)');
d.exec('CREATE INDEX IF NOT EXISTS idx_auth_events_event ON auth_events(event)');
d.exec(
'CREATE TABLE IF NOT EXISTS lockout (' +
' ip TEXT PRIMARY KEY,' +
' fail_count INTEGER NOT NULL DEFAULT 0,' +
' locked_until TEXT,' +
' last_fail TEXT' +
')'
);
logger.info('SSO database schema initialized', { path: getDbPath() });
}