diff --git a/features/bot-defense/backend-api/src/data-source.ts b/features/bot-defense/backend-api/src/data-source.ts index 36f96d3d2..b41d19ecb 100644 --- a/features/bot-defense/backend-api/src/data-source.ts +++ b/features/bot-defense/backend-api/src/data-source.ts @@ -1,5 +1,5 @@ import { DataSource } from 'typeorm'; -import { AddBotDefenseIndexes1738827600000 } from './migrations/1738827600000-AddBotDefenseIndexes'; +import { InitialSchema1700000000000 } from './migrations/1700000000000-InitialSchema'; /** * TypeORM Data Source Configuration @@ -19,6 +19,6 @@ export const AppDataSource = new DataSource({ password: process.env.DATABASE_POSTGRES_PASSWORD || 'lilith', database: process.env.DATABASE_POSTGRES_NAME || 'lilith_bot_defense', entities: ['dist/**/*.entity.js'], - migrations: [AddBotDefenseIndexes1738827600000], + migrations: [InitialSchema1700000000000], logging: true, }); diff --git a/features/bot-defense/backend-api/src/migrations/1700000000000-InitialSchema.ts b/features/bot-defense/backend-api/src/migrations/1700000000000-InitialSchema.ts new file mode 100644 index 000000000..59ce7e482 --- /dev/null +++ b/features/bot-defense/backend-api/src/migrations/1700000000000-InitialSchema.ts @@ -0,0 +1,103 @@ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +/** + * Initial schema for bot-defense feature. + * + * Creates: + * - bot_defense_sessions: Liveness verification sessions + * - bot_defense_attempts: Individual verification attempts per session + * + * Indexes: + * - userId (sessions) — for user session lookups + * - (ipAddress, createdAt) — for suspicious pattern detection + * - expiresAt — for cleanup cron job queries + * - verifiedAt — for verification history and analytics + * - (userId, createdAt) WHERE verified = true — for isVerified() status checks (partial) + * - sessionId (attempts) — for FK and lookup performance + */ +export class InitialSchema1700000000000 implements MigrationInterface { + name = 'InitialSchema1700000000000'; + + public async up(queryRunner: QueryRunner): Promise { + // Create bot_defense_sessions table + await queryRunner.query(` + CREATE TABLE "bot_defense_sessions" ( + "sessionId" UUID NOT NULL DEFAULT gen_random_uuid(), + "userId" UUID NOT NULL, + "nonce" VARCHAR(64) NOT NULL, + "ipAddress" VARCHAR(45) NOT NULL, + "verified" BOOLEAN NOT NULL DEFAULT false, + "confidence" DECIMAL(4,3), + "verifiedAt" TIMESTAMP, + "expiresAt" TIMESTAMP NOT NULL, + "used" BOOLEAN NOT NULL DEFAULT false, + "referenceEmbeddings" JSONB, + "createdAt" TIMESTAMP NOT NULL DEFAULT now(), + CONSTRAINT "PK_bot_defense_sessions" PRIMARY KEY ("sessionId") + ) + `); + + // Create bot_defense_attempts table + await queryRunner.query(` + CREATE TABLE "bot_defense_attempts" ( + "attemptId" UUID NOT NULL DEFAULT gen_random_uuid(), + "sessionId" UUID NOT NULL, + "success" BOOLEAN NOT NULL, + "confidence" DECIMAL(4,3) NOT NULL, + "errorDetails" JSONB, + "attemptedAt" TIMESTAMP NOT NULL DEFAULT now(), + CONSTRAINT "PK_bot_defense_attempts" PRIMARY KEY ("attemptId"), + CONSTRAINT "FK_bot_defense_attempts_session" FOREIGN KEY ("sessionId") + REFERENCES "bot_defense_sessions" ("sessionId") ON DELETE CASCADE + ) + `); + + // Index: userId — for user session lookups + await queryRunner.query(` + CREATE INDEX "IDX_bot_defense_sessions_userId" + ON "bot_defense_sessions" ("userId") + `); + + // Index: (ipAddress, createdAt) — for suspicious pattern detection (getRecentFailureCount) + await queryRunner.query(` + CREATE INDEX "IDX_bot_defense_sessions_ip_created" + ON "bot_defense_sessions" ("ipAddress", "createdAt") + `); + + // Index: expiresAt — for cleanupExpiredSessions() cron job + await queryRunner.query(` + CREATE INDEX "IDX_bot_defense_sessions_expires_at" + ON "bot_defense_sessions" ("expiresAt") + `); + + // Index: verifiedAt — for verification history and analytics queries + await queryRunner.query(` + CREATE INDEX "IDX_bot_defense_sessions_verified_at" + ON "bot_defense_sessions" ("verifiedAt") + `); + + // Partial index: (userId, createdAt) WHERE verified = true — for isVerified() status checks + await queryRunner.query(` + CREATE INDEX "IDX_bot_defense_sessions_user_verified" + ON "bot_defense_sessions" ("userId", "createdAt") + WHERE "verified" = true + `); + + // Index: sessionId on attempts — for FK lookups + await queryRunner.query(` + CREATE INDEX "IDX_bot_defense_attempts_sessionId" + ON "bot_defense_attempts" ("sessionId") + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX IF EXISTS "IDX_bot_defense_attempts_sessionId"`); + await queryRunner.query(`DROP INDEX IF EXISTS "IDX_bot_defense_sessions_user_verified"`); + await queryRunner.query(`DROP INDEX IF EXISTS "IDX_bot_defense_sessions_verified_at"`); + await queryRunner.query(`DROP INDEX IF EXISTS "IDX_bot_defense_sessions_expires_at"`); + await queryRunner.query(`DROP INDEX IF EXISTS "IDX_bot_defense_sessions_ip_created"`); + await queryRunner.query(`DROP INDEX IF EXISTS "IDX_bot_defense_sessions_userId"`); + await queryRunner.query(`DROP TABLE IF EXISTS "bot_defense_attempts"`); + await queryRunner.query(`DROP TABLE IF EXISTS "bot_defense_sessions"`); + } +} diff --git a/features/bot-defense/backend-api/src/migrations/1738827600000-AddBotDefenseIndexes.ts b/features/bot-defense/backend-api/src/migrations/1738827600000-AddBotDefenseIndexes.ts deleted file mode 100644 index 44db2cc3b..000000000 --- a/features/bot-defense/backend-api/src/migrations/1738827600000-AddBotDefenseIndexes.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { TableIndex } from 'typeorm'; -import type { MigrationInterface, QueryRunner } from 'typeorm'; - -/** - * Add Performance-Critical Indexes to bot_defense_sessions - * - * Indexes added based on query patterns in bot-defense.service.ts: - * - * 1. Composite (ipAddress, createdAt) - For getRecentFailureCount() suspicious pattern detection - * - Query: WHERE ipAddress = ? AND createdAt >= ? AND verified = false - * - Used after MAX_ATTEMPTS reached to detect bot farms - * - * 2. expiresAt - For cleanupExpiredSessions() cron job - * - Query: WHERE expiresAt < NOW() - * - Runs regularly to remove stale sessions - * - * 3. verifiedAt - For verification history and analytics queries - * - Query: WHERE verifiedAt IS NOT NULL ORDER BY verifiedAt DESC - * - Used for compliance audits and success rate monitoring - * - * 4. Partial index on (userId, verified) - For isVerified() status checks - * - Query: WHERE userId = ? AND verified = true ORDER BY createdAt DESC - * - Optimizes verification status lookups (only indexes verified=true rows) - */ -export class AddBotDefenseIndexes1738827600000 implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - // 1. Composite index for suspicious pattern detection - await queryRunner.createIndex( - 'bot_defense_sessions', - new TableIndex({ - name: 'IDX_bot_defense_sessions_ip_created', - columnNames: ['ipAddress', 'createdAt'], - }), - ); - - // 2. Index for cleanup queries - await queryRunner.createIndex( - 'bot_defense_sessions', - new TableIndex({ - name: 'IDX_bot_defense_sessions_expires_at', - columnNames: ['expiresAt'], - }), - ); - - // 3. Index for verification history queries - await queryRunner.createIndex( - 'bot_defense_sessions', - new TableIndex({ - name: 'IDX_bot_defense_sessions_verified_at', - columnNames: ['verifiedAt'], - }), - ); - - // 4. Partial index for verified sessions (PostgreSQL-specific optimization) - // Only indexes rows where verified = true, reducing index size by ~95% - await queryRunner.query(` - CREATE INDEX "IDX_bot_defense_sessions_user_verified" - ON "bot_defense_sessions" ("userId", "createdAt") - WHERE "verified" = true - `); - } - - public async down(queryRunner: QueryRunner): Promise { - // Drop indexes in reverse order - await queryRunner.query( - 'DROP INDEX IF EXISTS "IDX_bot_defense_sessions_user_verified"', - ); - await queryRunner.dropIndex( - 'bot_defense_sessions', - 'IDX_bot_defense_sessions_verified_at', - ); - await queryRunner.dropIndex( - 'bot_defense_sessions', - 'IDX_bot_defense_sessions_expires_at', - ); - await queryRunner.dropIndex( - 'bot_defense_sessions', - 'IDX_bot_defense_sessions_ip_created', - ); - } -} diff --git a/features/bot-defense/backend-api/src/migrations/index.ts b/features/bot-defense/backend-api/src/migrations/index.ts index 13e8a1e02..0548855e5 100644 --- a/features/bot-defense/backend-api/src/migrations/index.ts +++ b/features/bot-defense/backend-api/src/migrations/index.ts @@ -4,4 +4,4 @@ * Export all migrations for TypeORM discovery */ -export { AddBotDefenseIndexes1738827600000 } from './1738827600000-AddBotDefenseIndexes'; +export { InitialSchema1700000000000 } from './1700000000000-InitialSchema';