fase(25-26): keyboard shortcuts, mobile responsive, enterprise SSO/audit

- 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>
This commit is contained in:
debian
2026-03-08 13:38:25 -04:00
parent c3911bafe8
commit 08011d22d5
58 changed files with 2689 additions and 23 deletions

View File

@@ -0,0 +1,50 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.up = up;
exports.down = down;
const kysely_1 = require("kysely");
async function up(db) {
// SSO configurations per organization
await db.schema
.createTable('sso_configs')
.ifNotExists()
.addColumn('id', 'text', (c) => c.primaryKey())
.addColumn('organization_id', 'text', (c) => c.notNull())
.addColumn('provider', 'text', (c) => c.notNull())
.addColumn('enabled', 'integer', (c) => c.notNull().defaultTo(1))
.addColumn('config_json', 'text', (c) => c.notNull().defaultTo('{}'))
.addColumn('created_at', 'integer', (c) => c.notNull())
.execute();
// TOTP secrets for MFA
await db.schema
.createTable('totp_secrets')
.ifNotExists()
.addColumn('id', 'text', (c) => c.primaryKey())
.addColumn('user_id', 'text', (c) => c.notNull().unique())
.addColumn('secret', 'text', (c) => c.notNull())
.addColumn('verified', 'integer', (c) => c.notNull().defaultTo(0))
.addColumn('created_at', 'integer', (c) => c.notNull())
.execute();
// Audit logs
await db.schema
.createTable('audit_logs')
.ifNotExists()
.addColumn('id', 'text', (c) => c.primaryKey())
.addColumn('user_id', 'text')
.addColumn('organization_id', 'text')
.addColumn('action', 'text', (c) => c.notNull())
.addColumn('resource', 'text', (c) => c.notNull())
.addColumn('resource_id', 'text')
.addColumn('ip_address', 'text')
.addColumn('user_agent', 'text')
.addColumn('details_json', 'text', (c) => c.notNull().defaultTo('{}'))
.addColumn('occurred_at', 'integer', (c) => c.notNull())
.execute();
await (0, kysely_1.sql) `CREATE INDEX IF NOT EXISTS idx_audit_logs_user ON audit_logs (user_id)`.execute(db);
await (0, kysely_1.sql) `CREATE INDEX IF NOT EXISTS idx_audit_logs_occurred ON audit_logs (occurred_at)`.execute(db);
}
async function down(db) {
await db.schema.dropTable('audit_logs').ifExists().execute();
await db.schema.dropTable('totp_secrets').ifExists().execute();
await db.schema.dropTable('sso_configs').ifExists().execute();
}