- Phase 25.4: N shortcut for new exploration on dashboard (react-hotkeys-hook) - Phase 25.5: overflow-x-auto on tables, responsive padding (p-4 md:p-6) - Phase 26: SAML/OIDC/LDAP providers (build-fixed), TOTP/MFA service - Phase 26: KyselySSOConfigRepository + KyselyTOTPRepository - Phase 26: SSO HTTP controller (config CRUD + MFA setup/verify/disable) - Phase 26: Audit module index.ts + SSO module index.ts - Phase 26: Session management endpoints (findByUserId, deleteById, list/revoke) - Phase 26: SSO and audit routes feature-gated (auth:sso, audit:logs) - Phase 26: Frontend SSOSection (SAML/OIDC/LDAP config + TOTP setup) - Phase 26: Frontend SessionsSection (list/revoke active sessions) - Phase 26: Settings navigation updated with SSO & Sessions sections Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
318 lines
6.9 KiB
TypeScript
318 lines
6.9 KiB
TypeScript
import { Kysely, SqliteDialect } from 'kysely';
|
|
import SQLite from 'better-sqlite3';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
|
|
export interface SessionTable {
|
|
id: string;
|
|
url: string;
|
|
status: string;
|
|
seed: number;
|
|
max_states: number;
|
|
states_visited: number;
|
|
anomalies_found: number;
|
|
started_at: number;
|
|
finished_at: number | null;
|
|
config_json: string;
|
|
}
|
|
|
|
export interface StateTable {
|
|
id: string;
|
|
session_id: string;
|
|
url: string;
|
|
title: string;
|
|
dom_snapshot_path: string | null;
|
|
visit_count: number;
|
|
discovered_at: number;
|
|
}
|
|
|
|
export interface ActionTable {
|
|
id: string;
|
|
session_id: string;
|
|
state_id: string;
|
|
type: string;
|
|
selector: string | null;
|
|
value: string | null;
|
|
url: string | null;
|
|
seed: number;
|
|
executed_at: number;
|
|
sequence_order: number;
|
|
}
|
|
|
|
export interface AnomalyTable {
|
|
id: string;
|
|
session_id: string;
|
|
type: string;
|
|
severity: string;
|
|
description: string;
|
|
action_trace_json: string;
|
|
evidence_json: string;
|
|
screenshot_path: string | null;
|
|
dom_snapshot_path: string | null;
|
|
detected_at: number;
|
|
ai_enrichment_json: string | null;
|
|
ai_enriched_at: number | null;
|
|
browser: string | null;
|
|
browser_version: string | null;
|
|
}
|
|
|
|
export interface NotificationTable {
|
|
id: string;
|
|
anomaly_id: string;
|
|
channel: string;
|
|
status: string;
|
|
sent_at: number | null;
|
|
error: string | null;
|
|
}
|
|
|
|
export interface ScheduleTable {
|
|
id: string;
|
|
name: string;
|
|
url: string;
|
|
config_json: string;
|
|
cron_expression: string;
|
|
enabled: number;
|
|
last_run_at: number | null;
|
|
next_run_at: number | null;
|
|
created_at: number;
|
|
}
|
|
|
|
export interface VisualBaselineTable {
|
|
id: string;
|
|
state_id: string;
|
|
url: string;
|
|
screenshot_path: string;
|
|
approved_at: number;
|
|
approved_by: string | null;
|
|
width: number;
|
|
height: number;
|
|
}
|
|
|
|
export interface VisualComparisonTable {
|
|
id: string;
|
|
session_id: string;
|
|
state_id: string;
|
|
baseline_id: string | null;
|
|
current_screenshot_path: string;
|
|
diff_screenshot_path: string | null;
|
|
diff_pixels: number | null;
|
|
diff_percent: number | null;
|
|
status: string;
|
|
created_at: number;
|
|
}
|
|
|
|
export interface PerformanceMetricTable {
|
|
id: string;
|
|
session_id: string;
|
|
state_id: string;
|
|
url: string;
|
|
ttfb: number | null;
|
|
dom_content_loaded: number | null;
|
|
load_complete: number | null;
|
|
lcp: number | null;
|
|
cls: number | null;
|
|
fid: number | null;
|
|
inp: number | null;
|
|
total_requests: number | null;
|
|
failed_requests: number | null;
|
|
total_transfer_size: number | null;
|
|
captured_at: number;
|
|
}
|
|
|
|
export interface FindingTable {
|
|
id: string;
|
|
session_id: string;
|
|
type: string;
|
|
severity: string;
|
|
description: string;
|
|
status: string;
|
|
action_trace_json: string;
|
|
evidence_json: string;
|
|
screenshot_path: string | null;
|
|
dom_snapshot_path: string | null;
|
|
browser: string | null;
|
|
browser_version: string | null;
|
|
ai_enrichment_json: string | null;
|
|
created_at: number;
|
|
resolved_at: number | null;
|
|
}
|
|
|
|
export interface JobTable {
|
|
id: string;
|
|
type: string;
|
|
status: string;
|
|
payload: string;
|
|
result: string | null;
|
|
error: string | null;
|
|
attempts: number;
|
|
max_attempts: number;
|
|
priority: number;
|
|
run_at: string;
|
|
started_at: string | null;
|
|
completed_at: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface UserTable {
|
|
id: string;
|
|
email: string;
|
|
name: string;
|
|
password_hash: string;
|
|
role: string;
|
|
org_id: string | null;
|
|
created_at: number;
|
|
updated_at: number;
|
|
}
|
|
|
|
export interface OrganizationTable {
|
|
id: string;
|
|
name: string;
|
|
slug: string;
|
|
created_at: number;
|
|
}
|
|
|
|
export interface OrgMemberTable {
|
|
id: string;
|
|
org_id: string;
|
|
user_id: string;
|
|
role: string;
|
|
joined_at: number;
|
|
}
|
|
|
|
export interface ApiKeyTable {
|
|
id: string;
|
|
user_id: string;
|
|
org_id: string;
|
|
name: string;
|
|
key_hash: string;
|
|
key_prefix: string;
|
|
permissions: string;
|
|
expires_at: number | null;
|
|
last_used_at: number | null;
|
|
created_at: number;
|
|
}
|
|
|
|
export interface AuthSessionTable {
|
|
id: string;
|
|
user_id: string;
|
|
token: string;
|
|
expires_at: number;
|
|
created_at: number;
|
|
}
|
|
|
|
export interface ReportTable {
|
|
id: string;
|
|
title: string;
|
|
format: string;
|
|
status: string;
|
|
filters_json: string;
|
|
file_path: string | null;
|
|
error_message: string | null;
|
|
total_findings: number;
|
|
created_at: number;
|
|
completed_at: number | null;
|
|
}
|
|
|
|
export interface IntegrationTable {
|
|
id: string;
|
|
name: string;
|
|
type: string;
|
|
enabled: number;
|
|
config_json: string;
|
|
created_at: number;
|
|
}
|
|
|
|
export interface WebhookEndpointTable {
|
|
id: string;
|
|
url: string;
|
|
secret: string;
|
|
enabled: number;
|
|
created_at: number;
|
|
last_delivered_at: number | null;
|
|
last_status: number | null;
|
|
}
|
|
|
|
export interface WebhookDeliveryTable {
|
|
id: string;
|
|
endpoint_id: string;
|
|
event: string;
|
|
payload_json: string;
|
|
status: number;
|
|
attempted_at: number;
|
|
}
|
|
|
|
export interface SSOConfigTable {
|
|
id: string;
|
|
organization_id: string;
|
|
provider: string;
|
|
enabled: number;
|
|
config_json: string;
|
|
created_at: number;
|
|
}
|
|
|
|
export interface TOTPSecretTable {
|
|
id: string;
|
|
user_id: string;
|
|
secret: string;
|
|
verified: number;
|
|
created_at: number;
|
|
}
|
|
|
|
export interface AuditLogTable {
|
|
id: string;
|
|
user_id: string | null;
|
|
organization_id: string | null;
|
|
action: string;
|
|
resource: string;
|
|
resource_id: string | null;
|
|
ip_address: string | null;
|
|
user_agent: string | null;
|
|
details_json: string;
|
|
occurred_at: number;
|
|
}
|
|
|
|
export interface Database {
|
|
sessions: SessionTable;
|
|
states: StateTable;
|
|
actions: ActionTable;
|
|
anomalies: AnomalyTable;
|
|
notifications: NotificationTable;
|
|
schedules: ScheduleTable;
|
|
visual_baselines: VisualBaselineTable;
|
|
visual_comparisons: VisualComparisonTable;
|
|
performance_metrics: PerformanceMetricTable;
|
|
findings: FindingTable;
|
|
jobs: JobTable;
|
|
users: UserTable;
|
|
organizations: OrganizationTable;
|
|
org_members: OrgMemberTable;
|
|
api_keys: ApiKeyTable;
|
|
auth_sessions: AuthSessionTable;
|
|
reports: ReportTable;
|
|
integrations: IntegrationTable;
|
|
webhook_endpoints: WebhookEndpointTable;
|
|
webhook_deliveries: WebhookDeliveryTable;
|
|
sso_configs: SSOConfigTable;
|
|
totp_secrets: TOTPSecretTable;
|
|
audit_logs: AuditLogTable;
|
|
}
|
|
|
|
export function createDatabase(config: { driver: string; path: string; url?: string }): Kysely<Database> {
|
|
if (config.driver === 'postgres') {
|
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
const { Pool } = require('pg') as { Pool: new (opts: { connectionString?: string }) => unknown };
|
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
const { PostgresDialect } = require('kysely') as { PostgresDialect: new (opts: { pool: unknown }) => SqliteDialect };
|
|
return new Kysely<Database>({
|
|
dialect: new PostgresDialect({ pool: new Pool({ connectionString: config.url }) }),
|
|
});
|
|
}
|
|
|
|
fs.mkdirSync(path.dirname(config.path), { recursive: true });
|
|
|
|
return new Kysely<Database>({
|
|
dialect: new SqliteDialect({ database: new SQLite(config.path) }),
|
|
});
|
|
}
|