65 lines
1.7 KiB
TypeScript
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() });
|
|
}
|